Depois de solicitar e receber as permissões necessárias, seu app poderá acessar o hardware nos óculos com áudio ou com tela. A chave para acessar o hardware dos óculos (em vez do hardware do smartphone) é usar um contexto projetado.
Há duas maneiras principais de conseguir um contexto projetado, dependendo de onde o código está sendo executado:
Receber um contexto projetado se o código estiver sendo executado em uma atividade projetada
Se o código do app estiver sendo executado na atividade projetada, o contexto da atividade já será um contexto projetado. Nesse cenário, as chamadas feitas nessa atividade já podem acessar o hardware dos óculos.
Receber um contexto projetado para o código em execução em um componente de app Telefone
Se uma parte do app fora da atividade projetada (como uma atividade
de smartphone ou um serviço) precisar acessar o hardware dos óculos, ela precisará receber explicitamente
um contexto projetado. Para fazer isso, use o
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...") } } } }
Verificar a validade
Una a chamada createProjectedDeviceContext ao
ProjectedContext.isProjectedDeviceConnected. Enquanto esse método retornar
true, o contexto projetado vai permanecer válido para o dispositivo conectado, e a
atividade no app Telefone ou o serviço (como um CameraManager) poderá acessar o hardware dos
óculos com IA.
Limpar ao desconectar
O contexto projetado está vinculado ao ciclo de vida do dispositivo conectado, então ele é
destruído quando o dispositivo se desconecta. Quando o dispositivo é desconectado,
ProjectedContext.isProjectedDeviceConnected retorna false. O app
precisa detectar essa mudança e limpar todos os serviços do sistema (como um
CameraManager) ou recursos que o app criou usando esse contexto
projetado.
Reinicializar ao reconectar
Quando os óculos se reconectarem, o app poderá receber outra instância de contexto projetado
usando createProjectedDeviceContext e, em seguida, reinicializar todos os
serviços ou recursos do sistema usando o novo contexto projetado.
Gravar áudio com o microfone dos óculos
É possível gravar áudio dos óculos usando o perfil de viva-voz (HFP, na sigla em inglês) do Bluetooth. Nesse modo, os óculos se conectam ao smartphone usando os perfis padrão de fone de ouvido e perfil de distribuição de áudio avançado (A2DP), funcionando como um periférico Bluetooth comum. Se o app já foi projetado para gravação Bluetooth padrão, use esse método para gravar áudio dos óculos sem nenhum desenvolvimento adicional.
Gravar áudio usando o HFP do Bluetooth
Para gravar áudio usando o HFP do Bluetooth, primeiro solicite as permissões de execução
necessárias e, em seguida, grave o áudio usando a API AudioManager, conforme
descrito nas seções a seguir.
Solicitar permissões
Como em qualquer dispositivo de áudio Bluetooth padrão, as permissões RECORD_AUDIO,
BLUETOOTH_CONNECT e outras relacionadas são controladas pelo
smartphone, e não pelo dispositivo conectado (como óculos com áudio ou óculos com tela).
Siga estas etapas para solicitar as permissões:
Declare as seguintes permissões no arquivo de manifesto do app:
Solicite as permissões
RECORD_AUDIOeBLUETOOTH_CONNECTno momento da execução usando o fluxo de permissões padrão do Android.
Usar o AudioManager para rotear áudio
Depois que o usuário conceder ao app as permissões de execução necessárias, use a
API AudioManager para definir o dispositivo de comunicação como
TYPE_BLUETOOTH_SCO para rotear o áudio pelo Bluetooth HFP. Isso
direciona o sistema para recuperar o áudio do 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()
Tirar uma foto com a câmera dos óculos
Para capturar uma imagem com a câmera dos óculos, configure e vincule o
ImageCapture caso de uso do CameraX à câmera dos óculos usando o contexto
correto para seu 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)) } } } }
Pontos principais sobre o código
- Acessa uma instância de
ProcessCameraProviderusando o contexto do dispositivo projetado. - No escopo do contexto projetado, a câmera principal dos óculos, que fica voltada para fora,
é mapeada para
DEFAULT_BACK_CAMERAao selecionar uma câmera. - Uma verificação de pré-vinculação usa
cameraProvider.hasCamera(cameraSelector)para verificar se a câmera selecionada está disponível no dispositivo antes de prosseguir. - Usa a interoperabilidade da Camera2 com
Camera2CameraInfopara ler oCameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAPsubjacente, o que pode ser útil para verificações avançadas em resoluções compatíveis. - Um
ResolutionSelectorpersonalizado é criado para controlar com precisão a resolução da imagem de saída paraImageCapture. - Cria um caso de uso de
ImageCaptureconfigurado com umResolutionSelectorpersonalizado. - Vincula o caso de uso
ImageCaptureao ciclo de vida da atividade. Isso gerencia automaticamente a abertura e o fechamento da câmera com base no estado da atividade (por exemplo, interrompendo a câmera quando a atividade é pausada). - Configuração para hardware de stream único:o pipeline da câmera dos óculos é
restrito a um único stream ativo por vez. Quando
ImageCaptureé vinculado sozinho, o CameraX anexa um stream de repetição interno (MeteringRepeating) para foco e medição por padrão, o que faz com que o pipeline de stream único seja interrompido. Para vincularImageCapturepor conta própria sem um stream de pré-lançamento, desative o stream de repetição forçada definindosetRepeatingStreamForced(false)emCameraXConfig.Builder(por exemplo, implementandoCameraXConfig.Providerna sua classeApplicationou configurandoProcessCameraProviderantes da inicialização).
Depois que a câmera dos óculos estiver configurada, você poderá capturar uma imagem com a classe
ImageCapture do CameraX. Consulte a documentação do CameraX para saber como usar
takePicture para capturar uma imagem.
Gravar um vídeo com a câmera dos óculos
Para capturar um vídeo em vez de uma imagem com a câmera dos óculos, substitua os
componentes ImageCapture pelos componentes VideoCapture correspondentes
e modifique a lógica de execução da captura.
As principais mudanças envolvem o uso de um caso de uso diferente, a criação de um arquivo de saída
diferente e o início da captura usando o método de gravação de vídeo apropriado.
Para mais informações sobre a API VideoCapture e como usá-la, consulte a
documentação de captura de vídeo do CameraX.
A tabela a seguir mostra a resolução e o frame rate recomendados de acordo com o caso de uso do app:
| Caso de uso | Resolução | Frame rate |
|---|---|---|
| Comunicação por vídeo | 1280 x 720 | 15 QPS |
| Visão computacional | 640 x 480 | 10 QPS |
| Streaming de vídeo com IA | 640 x 480 | 1 QPS |
Acessar o hardware de um smartphone em uma atividade projetada
Uma atividade projetada também pode acessar o hardware do smartphone (como a
câmera ou o microfone) usando createHostDeviceContext(context) para acessar
o contexto do dispositivo host (smartphone):
@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 } }
Ao acessar hardware ou recursos específicos do dispositivo host (smartphone) em um app híbrido (um app que contém experiências para dispositivos móveis e óculos), selecione explicitamente o contexto correto para garantir que o app possa acessar o hardware correto:
- Use o contexto
Activitydo smartphoneActivityou oProjectedContext.createHostDeviceContextpara acessar o contexto do smartphone. - Não use
getApplicationContextporque o contexto do aplicativo pode retornar incorretamente o contexto dos óculos se uma atividade projetada foi o componente iniciado mais recentemente.