Crea tu primera actividad para anteojos de audio y anteojos de visualización

Dispositivos de realidad extendida correspondientes
Esta guía te ayuda a crear experiencias para estos tipos de dispositivos de realidad extendida.
Lentes de audio y
pantalla

Las experiencias aumentadas para anteojos de audio y anteojos con pantalla se basan en la API del framework de Activity existente de Android y se incluyen conceptos adicionales para admitir los aspectos únicos de estos anteojos. A diferencia de los visores de RE que ejecutan un APK completo en el dispositivo, los lentes de audio y los lentes con pantalla usan una actividad dedicada que se ejecuta dentro de la app existente de tu teléfono. Esta actividad se proyecta desde el dispositivo host hacia los lentes.

Para crear la experiencia de tu app para anteojos de audio y anteojos con pantalla, debes extender tu app para teléfonos existente creando un nuevo Activity proyectado. Esta actividad sirve como el punto de entrada principal para iniciar tu app en los lentes. Este enfoque simplifica el desarrollo porque puedes compartir y reutilizar la lógica empresarial entre las experiencias del teléfono y los lentes.

Compatibilidad de versiones

Consulta los requisitos de compatibilidad del SDK de Android para el SDK de XR de Jetpack.

Dependencias

Agrega las siguientes dependencias de biblioteca para anteojos de audio y anteojos con pantalla:

Groovy

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

Kotlin

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

Declara tu actividad en el manifiesto de tu app

Al igual que con otros tipos de actividades, debes declarar tu actividad en el archivo de manifiesto de la app para que el sistema la vea y la ejecute.

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

Puntos clave sobre el código

  • Especifica xr_projected para el atributo android:requiredDisplayCategory para indicarle al sistema que esta actividad debe usar un contexto proyectado para acceder al hardware de un dispositivo conectado.

Crea tu actividad

A continuación, crearás una pequeña actividad que pueda mostrar algo en los lentes con IA cada vez que se encienda la pantalla.

@OptIn(ExperimentalProjectedApi::class)
class GlassesMainActivity : 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@GlassesMainActivity)
            isVisualUiSupported = projectedDeviceController.capabilities.contains(CAPABILITY_VISUAL_UI)

            val controller = ProjectedDisplayController.create(this@GlassesMainActivity)
            displayController = controller
            val observer = GlassesLifecycleObserver(
                context = this@GlassesMainActivity,
                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))
    }
}

Puntos clave sobre el código

Implementa el elemento componible

La actividad que creaste hace referencia a una función de componibilidad HomeScreen que debes implementar. El siguiente código usa Jetpack Compose Glimmer para definir un elemento componible que puede mostrar texto en la pantalla de los lentes:

@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")
        }
    }
}

Puntos clave sobre el código

  • Como definiste en tu actividad anteriormente, la función HomeScreen incluye el contenido componible que el usuario ve cuando la pantalla de los lentes está encendida.
  • El componente Text de Glimmer de Jetpack Compose muestra el texto "Hola, lentes con IA" en la pantalla de los lentes.
  • El Button de Glimmer de Jetpack Compose cierra la actividad llamando a finish() a través de onClose en la actividad proyectada.

Comprueba si los lentes de audio o de pantalla están conectados

Para determinar si los lentes de audio o de pantalla de un usuario están conectados a su teléfono antes de iniciar tu actividad, usa el método ProjectedContext.isProjectedDeviceConnected. Este método devuelve un Flow<Boolean> que tu app puede observar para obtener actualizaciones en tiempo real sobre el estado de la conexión.

Cómo iniciar tu actividad

Ahora que creaste una actividad básica, puedes lanzarla en tus lentes. Para acceder al hardware de los lentes, tu app debe iniciar la actividad con opciones específicas que le indiquen al sistema que use un contexto proyectado, como se muestra en el siguiente código:

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

El método createProjectedActivityOptions en ProjectedContext genera las opciones necesarias para iniciar tu actividad en un contexto proyectado. El parámetro context puede ser un contexto del teléfono o del dispositivo de anteojos.

Próximos pasos

Ahora que creaste tu primera actividad para anteojos de audio y anteojos con pantalla, explora otras formas de extender su funcionalidad: