Создайте первое приложение для аудио- и дисплейных очков.

Применимые устройства XR
Данное руководство поможет вам создавать приложения для устройств XR такого типа.
Аудио и
Очки для демонстрации

Расширенные возможности для аудио- и дисплейных очков основаны на существующем API фреймворка Android Activity и включают дополнительные концепции для поддержки уникальных особенностей этих очков. В отличие от XR-гарнитур, которые запускают полноценный APK-файл на устройстве, аудио- и дисплейные очки используют специальную активность, которая работает внутри существующего приложения вашего телефона. Эта активность проецируется с устройства-носителя на очки.

Для создания интерфейса вашего приложения для аудио- и дисплейных очков вы расширяете существующее мобильное приложение, создавая новую проекционную Activity . Эта активность служит основной точкой входа для запуска вашего приложения на очках. Такой подход упрощает разработку, поскольку вы можете совместно использовать и повторно применять бизнес-логику между интерфейсами для телефона и очков.

Совместимость версий

Проверьте требования к совместимости Android SDK для Jetpack XR SDK.

Зависимости

Добавьте следующие зависимости библиотек для аудио- и дисплейных очков :

Классный

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

Котлин

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

Укажите тип активности в манифесте вашего приложения.

Как и в случае с другими типами действий, вам необходимо объявить о своем действии в файле манифеста вашего приложения, чтобы система могла его увидеть и запустить.

<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>

Основные моменты, касающиеся кода.

  • Указывает атрибут android:requiredDisplayCategory android.hardware.display.category.XR_PROJECTED , чтобы сообщить системе, что это проецируемое действие, которое может проецироваться на аудио- и дисплейные очки.
  • android.intent.action.MAIN устанавливает это действие в качестве действия запуска по умолчанию.
  • android.intent.category.XR_PROJECTED_LAUNCHER — это специализированная категория, которая позволяет обнаруживать ваше проецируемое приложение с помощью голосовых команд Gemini.

    Когда пользователь отдает голосовую команду, используя название приложения (например, «Открыть пример из каталога ИИ», «Запустить пример из каталога ИИ» или «Запустить пример из каталога ИИ»), система использует эту категорию для поиска и запуска указанного действия на аудио- или дисплейных очках.

Создайте свою активность

Далее вам нужно будет создать небольшое приложение, которое будет отображать что-либо на очках с искусственным интеллектом всякий раз, когда дисплей включен.

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

Основные моменты, касающиеся кода.

Реализуйте составной модуль.

Созданное вами действие ссылается на составную функцию HomeScreen , которую вам необходимо реализовать. Следующий код использует Jetpack Compose Glimmer для определения составного элемента, который может отображать текст на дисплее очков:

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

Основные моменты, касающиеся кода.

  • Как вы уже указали в своем предыдущем задании, функция HomeScreen включает в себя составной контент, который пользователь видит, когда дисплей очков включен.
  • Компонент Jetpack Compose Glimmer Text отображает текст "Привет, очки с искусственным интеллектом!" на экране очков.
  • Кнопка Jetpack Compose Glimmer Button закрывает активность, вызывая finish() через onClose в проецируемой активности.

Проверьте, подключены ли аудиоочки или очки для просмотра изображения.

Чтобы определить, подключены ли аудио- или дисплейные очки пользователя к его телефону перед запуском вашего приложения, используйте метод ProjectedContext.isProjectedDeviceConnected . Этот метод возвращает Flow<Boolean> , который ваше приложение может отслеживать для получения обновлений о состоянии подключения в реальном времени.

Начать свою активность

Теперь, когда вы создали базовую активность, вы можете запустить её на своих очках. Для доступа к аппаратному обеспечению очков ваше приложение должно запустить активность с определёнными параметрами, которые указывают системе использовать проецируемый контекст , как показано в следующем коде:

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

Метод createProjectedActivityOptions в ProjectedContext генерирует необходимые параметры для запуска активности в проецируемом контексте. Параметр context может представлять собой контекст телефона или очков.

Следующие шаги

Теперь, когда вы создали свою первую активность для аудио- и дисплейных очков, изучите другие способы расширения ее функциональности: