Después de solicitar y obtener los permisos necesarios, tu app puede acceder al hardware de los lentes con audio o con pantalla. La clave para acceder al hardware de los lentes (en lugar del hardware del teléfono) es usar un contexto proyectado.
Existen dos formas principales de obtener un contexto proyectado, según dónde se ejecute tu código:
Obtener un contexto proyectado si tu código se ejecuta en una actividad proyectada
Si el código de tu app se ejecuta desde tu actividad proyectada, su propio contexto de actividad ya es un contexto proyectado. En esta situación, las llamadas que se realicen dentro de esa actividad ya pueden acceder al hardware de los lentes.
Obtener un contexto proyectado para el código que se ejecuta en un componente de la app de Teléfono
Si una parte de tu app fuera de la actividad proyectada (como una actividad de teléfono o un servicio) necesita acceder al hardware de los lentes, debe obtener de forma explícita un contexto proyectado. Para ello, usa el método createProjectedDeviceContext:
@RequiresApi(Build.VERSION_CODES.BAKLAVA) @OptIn(ExperimentalProjectedApi::class, ExperimentalCoroutinesApi::class) private fun monitorProjectedConnectivity(activity: ComponentActivity) { activity.lifecycleScope.launch { // Before creating a projected context, check to see if the projected device is connected. // While this method returns true, the projected context remains valid. ProjectedContext.isProjectedDeviceConnected(activity, coroutineContext) .collectLatest { isConnected -> if (isConnected) { // From a phone Activity or Service, get a context for the audio and display glasses. // Re-initialize on reconnect: Obtain another context instance. val projectedContext = try { ProjectedContext.createProjectedDeviceContext(activity) } catch (e: IllegalStateException) { Log.e(TAG, "Failed to create projected context", e) return@collectLatest } // Use the projectedContext to initialize system services (e.g., CameraManager). Log.i(TAG, "Projected device connected. Initializing hardware...") } else { // The projected context is destroyed when the device disconnects. // Clean up on disconnect: Listen for 'false' and release resources. Log.i(TAG, "Projected device disconnected. Cleaning up hardware resources...") } } } }
Verifica la validez
Une la llamada createProjectedDeviceContext dentro de ProjectedContext.isProjectedDeviceConnected. Mientras este método devuelva
true, el contexto proyectado seguirá siendo válido para el dispositivo conectado, y la
actividad o el servicio de tu app de Teléfono (como un CameraManager) podrá acceder al hardware de los lentes
con IA.
Limpiar al desconectar
El contexto proyectado está vinculado al ciclo de vida del dispositivo conectado, por lo que se destruye cuando el dispositivo se desconecta. Cuando el dispositivo se desconecta, ProjectedContext.isProjectedDeviceConnected devuelve false. Tu app
debe escuchar este cambio y limpiar cualquier servicio del sistema (como un
CameraManager) o los recursos que creó con ese contexto
proyectado.
Reinicializar cuando se vuelva a conectar
Cuando los lentes se vuelvan a conectar, tu app podrá obtener otra instancia de contexto proyectado
con createProjectedDeviceContext y, luego, volver a inicializar cualquier
servicio o recurso del sistema con el nuevo contexto proyectado.
Grabar audio con el micrófono de los lentes
Puedes grabar audio desde los lentes con el perfil de manos libres (HFP) de Bluetooth. En este modo, los lentes se conectan al teléfono con los perfiles estándar de auriculares y perfil de distribución de audio avanzado (A2DP), y funcionan como un periférico Bluetooth típico. Si tu app ya está diseñada para la grabación estándar por Bluetooth, puedes usar este método para grabar audio desde los lentes sin necesidad de realizar ningún desarrollo adicional.
Graba audio con el HFP de Bluetooth
Para grabar audio con el HFP de Bluetooth, primero solicita los permisos de tiempo de ejecución necesarios y, luego, graba el audio con la API de AudioManager, como se describe en las siguientes secciones.
Solicitar permisos
Al igual que con cualquier dispositivo de audio Bluetooth estándar, el teléfono, y no el dispositivo conectado (como los lentes con audio o con pantalla), controla los permisos RECORD_AUDIO, BLUETOOTH_CONNECT y otros relacionados.
Sigue estos pasos para solicitar los permisos:
Declara los siguientes permisos en el archivo de manifiesto de tu app:
Solicita los permisos
RECORD_AUDIOyBLUETOOTH_CONNECTen el tiempo de ejecución con el flujo de permisos estándar de Android.
Usa AudioManager para enrutar el audio
Después de que el usuario otorgue a tu app los permisos de tiempo de ejecución necesarios, usa la API de AudioManager para configurar el dispositivo de comunicación en TYPE_BLUETOOTH_SCO para enrutar el audio a través de Bluetooth HFP. Esto indica al sistema que recupere el audio del periférico Bluetooth.
val audioManager = context.getSystemService(AudioManager::class.java) ?: return val devices = audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS) val hfpDevice = devices.find { it.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO } hfpDevice?.let { device -> val audioRecord = AudioRecord.Builder() .setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION) .setAudioFormat(audioFormat) .setBufferSizeInBytes(bufferSize) .build() // Route recording to the Bluetooth device audioRecord.setPreferredDevice(device) audioManager.setCommunicationDevice(device) audioRecord.startRecording()
Captura una imagen con la cámara de los lentes
Para capturar una imagen con la cámara de los lentes, configura y vincula el caso de uso ImageCapture de CameraX a la cámara de los lentes con el contexto correcto para tu app:
private fun startCameraOnGlasses(activity: ComponentActivity) { activity.lifecycleScope.launch { // Before creating a projected context, check to see if the projected device is connected. ProjectedContext.isProjectedDeviceConnected(activity, coroutineContext) .collectLatest { isConnected -> if (isConnected) { // 1. Get the CameraProvider using the projected context. // When using the projected context, DEFAULT_BACK_CAMERA maps to the audio and display glasses' camera. val projectedContext = try { ProjectedContext.createProjectedDeviceContext(activity) } catch (e: IllegalStateException) { Log.e(TAG, "Projected context could not be created", e) return@collectLatest } val cameraProviderFuture = ProcessCameraProvider.getInstance(projectedContext) cameraProviderFuture.addListener({ val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get() val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA // 2. Check for the presence of a camera. if (!cameraProvider.hasCamera(cameraSelector)) { Log.w(TAG, "The selected camera is not available.") return@addListener } // 3. Query supported streaming resolutions using Camera2 Interop. val cameraInfo = cameraProvider.getCameraInfo(cameraSelector) val camera2CameraInfo = Camera2CameraInfo.from(cameraInfo) val cameraCharacteristics = camera2CameraInfo.getCameraCharacteristic( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP ) // 4. Define the resolution strategy. val targetResolution = Size(1920, 1080) val resolutionStrategy = ResolutionStrategy( targetResolution, ResolutionStrategy.FALLBACK_RULE_CLOSEST_LOWER ) val resolutionSelector = ResolutionSelector.Builder() .setResolutionStrategy(resolutionStrategy) .build() // 5. If you have other continuous use cases bound, such as Preview or ImageAnalysis, // you can use Camera2 Interop's CaptureRequestOptions to set the FPS val fpsRange = Range(30, 60) val captureRequestOptions = CaptureRequestOptions.Builder() .setCaptureRequestOption(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange) .build() // 6. Initialize the ImageCapture use case with options. val imageCapture = ImageCapture.Builder() // Optional: Configure resolution, format, etc. .setResolutionSelector(resolutionSelector) .build() try { // Unbind use cases before rebinding. cameraProvider.unbindAll() // Bind use cases to camera using the Activity as the LifecycleOwner. cameraProvider.bindToLifecycle( activity, cameraSelector, imageCapture ) } catch (exc: Exception) { Log.e(TAG, "Use case binding failed", exc) } }, ContextCompat.getMainExecutor(activity)) } } } }
Puntos clave sobre el código
- Obtiene una instancia de
ProcessCameraProvidercon el contexto del dispositivo proyectado. - Dentro del alcance del contexto proyectado, la cámara principal de los lentes, que apunta hacia afuera, se asigna a
DEFAULT_BACK_CAMERAcuando se selecciona una cámara. - Una verificación previa a la vinculación usa
cameraProvider.hasCamera(cameraSelector)para comprobar que la cámara seleccionada esté disponible en el dispositivo antes de continuar. - Usa la interoperabilidad de Camera2 con
Camera2CameraInfopara leer elCameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAPsubyacente, que puede ser útil para realizar verificaciones avanzadas de las resoluciones admitidas. - Se compila un
ResolutionSelectorpersonalizado para controlar con precisión la resolución de la imagen de salida paraImageCapture. - Crea un caso de uso de
ImageCaptureque se configura con unResolutionSelectorpersonalizado. - Vincula el caso de uso
ImageCaptureal ciclo de vida de la actividad. Esto administra automáticamente la apertura y el cierre de la cámara según el estado de la actividad (por ejemplo, detiene la cámara cuando se pausa la actividad). - Configuración para hardware de transmisión única: La canalización de la cámara de los lentes se limita a una sola transmisión activa a la vez. Cuando
ImageCapturese vincula solo, CameraX adjunta un flujo repetitivo interno (MeteringRepeating) para el enfoque y la medición de forma predeterminada, lo que hace que la canalización de transmisión única se detenga. Para vincularImageCapturepor sí solo sin un flujo de vista previa, inhabilita el flujo de repetición forzada configurandosetRepeatingStreamForced(false)enCameraXConfig.Builder(por ejemplo, implementandoCameraXConfig.Provideren tu claseApplicationo configurandoProcessCameraProviderantes de la inicialización).
Después de configurar la cámara de los lentes, puedes capturar una imagen con la clase ImageCapture de CameraX. Consulta la documentación de CameraX para obtener información sobre cómo usar takePicture para capturar una imagen.
Graba un video con la cámara de los lentes
Para capturar un video en lugar de una imagen con la cámara de los lentes, reemplaza los componentes ImageCapture por los componentes VideoCapture correspondientes y modifica la lógica de ejecución de la captura.
Los cambios principales implican usar un caso de uso diferente, crear un archivo de salida diferente y, luego, iniciar la captura con el método de grabación de video adecuado.
Para obtener más información sobre la API de VideoCapture y cómo usarla, consulta la
documentación de captura de video de CameraX.
En la siguiente tabla, se muestran la resolución y la velocidad de fotogramas recomendadas según el caso de uso de tu app:
| Caso de uso | Solución | Velocidad de fotogramas |
|---|---|---|
| Comunicación por video | 1280 × 720 | 15 FPS |
| Visión artificial | 640 × 480 | 10 FPS |
| Transmisión de video con IA | 640 × 480 | 1 FPS |
Cómo acceder al hardware de un teléfono desde una actividad proyectada
Una actividad proyectada también puede acceder al hardware del teléfono (como la cámara o el micrófono) con createHostDeviceContext(context) para obtener el contexto del dispositivo host (teléfono):
@OptIn(ExperimentalProjectedApi::class) private fun getPhoneContext(activity: ComponentActivity): Context? { return try { // From a projected Activity, get a context for the phone. ProjectedContext.createHostDeviceContext(activity) } catch (e: IllegalStateException) { Log.e(TAG, "Failed to create host device context", e) null } }
Cuando accedas al hardware o a los recursos específicos del dispositivo host (teléfono) en una app híbrida (una app que contiene experiencias para dispositivos móviles y lentes), debes seleccionar de forma explícita el contexto correcto para asegurarte de que la app pueda acceder al hardware correcto:
- Usa el contexto
Activitydel teléfonoActivityo elProjectedContext.createHostDeviceContextpara obtener el contexto del teléfono. - No uses
getApplicationContext, ya que el contexto de la aplicación puede devolver de forma incorrecta el contexto de los lentes si una actividad proyectada fue el componente que se inició más recientemente.