オーディオ グラスとディスプレイ グラスの拡張エクスペリエンスは、既存の Android Activity フレームワーク API をベースに構築されており、これらのグラスの独自性をサポートするための追加のコンセプトが含まれています。デバイス上で完全な APK を実行する XR ヘッドセットとは異なり、オーディオ グラスとディスプレイ グラスは、スマートフォンの既存のアプリ内で実行される専用のアクティビティを使用します。このアクティビティは、ホストデバイスからグラスに投影されます。
オーディオ グラスとディスプレイ グラス用のアプリ エクスペリエンスを作成するには、新しい投影 Activity を作成して、既存のスマートフォン アプリを拡張します。このアクティビティは、グラス上のアプリのメインの起動エントリ ポイントとして機能します。このアプローチでは、スマートフォンとメガネのエクスペリエンス間でビジネス ロジックを共有して再利用できるため、開発が簡素化されます。
バージョンの互換性
Jetpack XR SDK の Android SDK の互換性要件を確認してください。
依存関係
オーディオ グラスとディスプレイ グラスのライブラリ依存関係を以下のように追加します。
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")
}
アプリのマニフェストでアクティビティを宣言する
他のタイプのアクティビティと同様に、システムがアクティビティを認識して実行できるように、アプリのマニフェスト ファイルでアクティビティを宣言する必要があります。
<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>
コードに関する主なポイント
android:requiredDisplayCategory属性にxr_projectedを指定して、このアクティビティが投影されたコンテキストを使用してコネクテッド デバイスからハードウェアにアクセスする必要があることをシステムに伝えます。
アクティビティを作成する
次に、ディスプレイがオンになるたびに AI グラスに何かを表示できる小さなアクティビティを作成します。
@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)) } }
コードに関する主なポイント
- Jetpack Projected ライブラリのオプトイン API の使用をオプトインします。
GlassesMainActivityは、モバイル開発で想定されるとおりにComponentActivityを拡張します。- すべてのメガネにディスプレイが搭載されているわけではないため、
ProjectedDeviceControllerを使用してデバイスにディスプレイが搭載されているかどうかを確認します。 onCreate関数内のsetContentブロックは、アクティビティのコンポーザブル UI ツリーのルートを定義します。Jetpack Compose Glimmer を使用してHomeScreenコンポーザブルを実装します。- アクティビティの
onCreateメソッドで UI を初期化します(投影されたアクティビティのライフサイクルを参照)。 - メガネのハードウェアにアクセスするカメラ関連の機能に備えるため、権限ランチャーを登録し、
hasCameraPermission関数とrequestHardwarePermissions関数を定義し、initializeGlassesFeaturesを呼び出す前に権限が付与されているかどうかを確認することで、ハードウェアの権限をリクエストします。
コンポーザブルを実装する
作成したアクティビティは、実装する必要がある 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コンポーネントは、メガネのディスプレイに「Hello, AI Glasses!」というテキストを表示します。 - Jetpack Compose Glimmer
Buttonは、投影されたアクティビティのonCloseを介してfinish()を呼び出すことでアクティビティを閉じます。
オーディオ グラスまたはディスプレイ グラスが接続されているかどうかを確認する
アクティビティを起動する前に、ユーザーのオーディオ グラスまたはディスプレイ グラスがスマートフォンに接続されているかどうかを判断するには、ProjectedContext.isProjectedDeviceConnected メソッドを使用します。このメソッドは、アプリが接続ステータスのリアルタイム更新を取得するために監視できる Flow<Boolean> を返します。
アクティビティを開始する
基本的なアクティビティを作成したので、グラスで起動できます。メガネのハードウェアにアクセスするには、次のコードに示すように、投影コンテキストを使用するようにシステムに指示する特定のオプションを指定して、アクティビティを開始する必要があります。
val options = ProjectedContext.createProjectedActivityOptions(context) val intent = Intent(context, GlassesMainActivity::class.java) context.startActivity(intent, options.toBundle())
ProjectedContext の createProjectedActivityOptions メソッドは、投影されたコンテキストでアクティビティを開始するために必要なオプションを生成します。context パラメータは、スマートフォンまたはメガネ型デバイスのコンテキストにすることができます。
次のステップ
オーディオ グラスとディスプレイ グラスの最初のアクティビティを作成したので、その機能を拡張する他の方法を見てみましょう。
- Text to Speech を使用してオーディオ出力を処理する
- 自動音声認識を使用して音声入力を処理する
- Jetpack Compose Glimmer で UI を構築する
- オーディオ グラスとディスプレイ グラスのハードウェアにアクセスする