Criar sua primeira atividade para óculos de áudio e óculos de exibição

Dispositivos XR relevantes
Estas orientações ajudam você a criar experiências para esses tipos de dispositivos XR.
Óculos com áudio e
tela

As experiências de realidade aumentada para óculos com áudio e óculos com tela virtual são criadas na API do framework Activity do Android e incluem outros conceitos para oferecer suporte aos aspectos exclusivos desses óculos. Ao contrário dos headsets XR que executam um APK completo no dispositivo, os óculos com áudio e tela usam uma atividade dedicada que é executada no app do smartphone. Essa atividade é projetada do dispositivo host para os óculos.

Para criar a experiência do app para óculos com áudio e tela, você estende o app do smartphone criando um novo Activity projetado. Essa atividade serve como o principal ponto de entrada de inicialização do app nos óculos. Essa abordagem simplifica o desenvolvimento porque você pode compartilhar e reutilizar a lógica de negócios entre as experiências de smartphone e óculos.

Compatibilidade de versões

Confira os requisitos de compatibilidade do SDK do Android para o SDK do Jetpack XR.

Dependências

Adicione as seguintes dependências de biblioteca para óculos com áudio e tela óculos:

Groovy

dependencies {
    implementation "androidx.xr.runtime:runtime:1.0.0-alpha15"
    implementation "androidx.xr.glimmer:glimmer:1.0.0-alpha13"
    implementation "androidx.xr.glimmer:glimmer-google-fonts:1.0.0-alpha13"
    implementation "androidx.xr.projected:projected:1.0.0-alpha08"
    implementation "androidx.xr.arcore:arcore:1.0.0-alpha14"
}

Kotlin

dependencies {
    implementation("androidx.xr.runtime:runtime:1.0.0-alpha15")
    implementation("androidx.xr.glimmer:glimmer:1.0.0-alpha13")
    implementation("androidx.xr.glimmer:glimmer-google-fonts:1.0.0-alpha13")
    implementation("androidx.xr.projected:projected:1.0.0-alpha08")
    implementation("androidx.xr.arcore:arcore:1.0.0-alpha14")
}

Declarar sua atividade no manifesto do app

Assim como outros tipos de atividades, é necessário declarar sua atividade no arquivo de manifesto do app para que o sistema a veja e a execute.

<application>
  <activity
      android:name="com.example.xr.projected.ProjectedMainActivity"
      android:exported="true"
      android:requiredDisplayCategory="android.hardware.display.category.XR_PROJECTED"
      android:label="Example activity for audio glasses and display glasses">
      <intent-filter>
          <action android:name="android.intent.action.MAIN" />
          <category android:name="android.intent.category.XR_PROJECTED_LAUNCHER"/>
      </intent-filter>
  </activity>
</application>

Principais pontos sobre o código

  • Especifica android.hardware.display.category.XR_PROJECTED para o atributo android:requiredDisplayCategory para informar ao sistema que essa é uma atividade projetada e pode ser projetada em óculos com tela virtual e áudio.
  • android.intent.action.MAIN define essa atividade como a atividade de inicialização padrão.
  • android.intent.category.XR_PROJECTED_LAUNCHER é uma categoria especializada que torna sua atividade projetada detectável por comandos de voz do Gemini.

    Quando um usuário emite um comando de voz usando o nome do app (por exemplo, "Abrir o exemplo de catálogo de IA", "Iniciar o exemplo de catálogo de IA" ou "Começar o exemplo de catálogo de IA"), o sistema usa essa categoria para localizar e iniciar a atividade designada nos óculos com áudio ou tela.

Criar sua atividade

Em seguida, você vai criar uma pequena atividade que pode mostrar algo nos óculos de IA sempre que a tela estiver ativada.

@OptIn(ExperimentalProjectedApi::class)
class ProjectedMainActivity : ComponentActivity() {

    private var displayController: ProjectedDisplayController? = null
    private var isVisualUiSupported by mutableStateOf(false)
    private var areVisualsOn by mutableStateOf(true)
    private var isPermissionDenied by mutableStateOf(false)

    // Register the permissions launcher using the ProjectedPermissionsResultContract.
    private val requestPermissionLauncher: ActivityResultLauncher<List<ProjectedPermissionsRequestParams>> =
        registerForActivityResult(ProjectedPermissionsResultContract()) { results ->
            if (results[Manifest.permission.CAMERA] == true) {
                isPermissionDenied = false
                initializeGlassesFeatures()
            } else {
                // Handle permission denial.
                isPermissionDenied = true
            }
        }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        lifecycle.addObserver(object : DefaultLifecycleObserver {
            override fun onDestroy(owner: LifecycleOwner) {
                displayController?.close()
                displayController = null
            }
        })

        if (hasCameraPermission()) {
            initializeGlassesFeatures()
        } else {
            requestHardwarePermissions()
        }

        setContent {
            GlimmerTheme {
                HomeScreen(
                    areVisualsOn = areVisualsOn,
                    isVisualUiSupported = isVisualUiSupported,
                    isPermissionDenied = isPermissionDenied,
                    onRetryPermission = { requestHardwarePermissions() },
                    onClose = { finish() }
                )
            }
        }
    }

    private fun initializeGlassesFeatures() {
        lifecycleScope.launch {
            // Check device capabilities
            val projectedDeviceController = ProjectedDeviceController.create(this@ProjectedMainActivity)
            isVisualUiSupported = projectedDeviceController.capabilities.contains(CAPABILITY_VISUAL_UI)

            val controller = ProjectedDisplayController.create(this@ProjectedMainActivity)
            displayController = controller
            val observer = GlassesLifecycleObserver(
                context = this@ProjectedMainActivity,
                controller = controller,
                onVisualsChanged = { visualsOn -> areVisualsOn = visualsOn }
            )
            lifecycle.addObserver(observer)
        }
    }

    private fun hasCameraPermission(): Boolean {
        return ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) ==
                PackageManager.PERMISSION_GRANTED
    }

    private fun requestHardwarePermissions() {
        val params = ProjectedPermissionsRequestParams(
            permissions = listOf(Manifest.permission.CAMERA),
            rationale = "Camera access is required to overlay digital content on your physical environment."
        )
        requestPermissionLauncher.launch(listOf(params))
    }
}

Principais pontos sobre o código

Implementar o elemento combinável

A atividade criada faz referência a uma função combinável HomeScreen que você precisa implementar. O código a seguir usa Jetpack Compose Glimmer para definir um elemento combinável que pode mostrar algum texto na tela dos óculos:

@Composable
fun HomeScreen(
    areVisualsOn: Boolean,
    isVisualUiSupported: Boolean,
    isPermissionDenied: Boolean,
    onRetryPermission: () -> Unit,
    onClose: () -> Unit,
    modifier: Modifier = Modifier
) {
    Box(
        modifier = modifier
            .surface()
            .focusable(false)
            .fillMaxSize(),
        contentAlignment = Alignment.Center
    ) {
        if (isPermissionDenied) {
            Card(
                title = { Text("Permission Required") },
                action = { Button(onClick = onClose) { Text("Exit") } }
            ) {
                Text("Camera access is needed to use AI glasses features.")
                Button(onClick = onRetryPermission) { Text("Retry") }
            }
        } else if (isVisualUiSupported) {
            Card(
                title = { Text("Android XR") },
                action = {
                    Button(onClick = onClose) {
                        Text("Close")
                    }
                }
            ) {
                if (areVisualsOn) {
                    Text("Hello, AI Glasses!")
                } else {
                    Text("Display is off. Audio guidance active.")
                }
            }
        } else {
            Text("Audio Guidance Mode Active")
        }
    }
}

Principais pontos sobre o código

  • Como você definiu na atividade anterior, a função HomeScreen inclui o conteúdo combinável que o usuário vê quando a tela dos óculos está ativada.
  • O componente Text do Jetpack Compose Glimmer mostra o texto "Olá, óculos de IA!" na tela dos óculos.
  • O Button do Jetpack Compose Glimmer fecha a atividade chamando finish() por onClose na atividade projetada.

Verificar se os óculos com áudio ou tela estão conectados

Para determinar se os óculos com áudio ou óculos com tela virtual de um usuário estão conectados ao smartphone antes de iniciar a atividade, use o ProjectedContext.isProjectedDeviceConnected método. Esse método retorna um Flow<Boolean> que o app pode observar para receber atualizações em tempo real sobre o status da conexão.

Iniciar sua atividade

Agora que você criou uma atividade básica, é possível iniciá-la nos óculos. Para acessar o hardware dos óculos, o app precisa iniciar a atividade com opções específicas que informam ao sistema para usar um contexto projetado, conforme mostrado no código a seguir:

val options = ProjectedContext.createProjectedActivityOptions(context)
val intent = Intent(context, ProjectedMainActivity::class.java)
context.startActivity(intent, options.toBundle())

O método createProjectedActivityOptions em ProjectedContext gera as opções necessárias para iniciar a atividade em um contexto projetado. O parâmetro context pode ser um contexto do smartphone ou do dispositivo dos óculos.

Próximas etapas

Agora que você criou sua primeira atividade para óculos com áudio e tela, explore outras maneiras de estender a funcionalidade dela: