AI グラス用の最初のアクティビティを作成する

対応する XR デバイス
このガイダンスは、次のようなタイプの XR デバイス向けのエクスペリエンスを構築する際に役立ちます。
AI グラス

AI グラスのエクスペリエンスは、既存の Android Activity フレームワーク API を基盤として構築されており、AI グラスの固有の側面をサポートするための追加のコンセプトが含まれています。デバイスで完全な APK を実行する XR ヘッドセットとは異なり、AI グラスはスマートフォンの既存のアプリ内で実行される専用の Activity を使用します。この Activity は、ホストデバイスから AI グラスに投影されます。

アプリの AI グラスのエクスペリエンスを作成するには、AI グラス用の新しい投影された Activity を作成して、既存のスマートフォン アプリ を拡張します。このアクティビティは、AI グラスでのアプリのメインの起動エントリ ポイントとして機能します。このアプローチでは、スマートフォンと AI グラスのエクスペリエンス間でビジネス ロジックを共有して再利用できるため、開発が簡素化されます。

バージョンの互換性

Jetpack XR SDK の Android SDK の互換性要件を確認してください。

依存関係

AI グラス用の次の ライブラリ依存関係 を追加します。

Groovy

dependencies {
    implementation "androidx.xr.runtime:runtime:1.0.0-alpha11"
    implementation "androidx.xr.glimmer:glimmer:1.0.0-alpha08"
    implementation "androidx.xr.projected:projected:1.0.0-alpha05"
    implementation "androidx.xr.arcore:arcore:1.0.0-alpha11"
}

Kotlin

dependencies {
    implementation("androidx.xr.runtime:runtime:1.0.0-alpha11")
    implementation("androidx.xr.glimmer:glimmer:1.0.0-alpha08")
    implementation("androidx.xr.projected:projected:1.0.0-alpha05")
    implementation("androidx.xr.arcore:arcore:1.0.0-alpha11")
}

アプリのマニフェストでアクティビティを宣言する

他のタイプのアクティビティと同様に、システムがアクティビティを認識して実行できるようにするには、アプリのマニフェスト ファイルでアクティビティを宣言する必要があります。

<application>
  <activity
      android:name="com.example.xr.projected.GlassesMainActivity"
      android:exported="true"
      android:requiredDisplayCategory="xr_projected"
      android:label="Example AI Glasses activity">
      <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))
    }
}

コードに関する主なポイント

コンポーザブルを実装する

作成したアクティビティは、実装する必要がある HomeScreen コンポーズ可能な関数を参照します。次のコードでは、Jetpack Compose Glimmer を 使用して、AI グラスのディスプレイにテキストを表示できるコンポーザブルを定義します。

@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 関数には、AI グラスのディスプレイがオンのときにユーザーに表示されるコンポーズ可能なコンテンツが含まれています。
  • Jetpack Compose Glimmer Text コンポーネントは、グラスのディスプレイに「Hello, AI Glasses!」というテキストを表示します。
  • Jetpack Compose Glimmer の Button は、AI グラスのアクティビティの onClose を介して finish() を呼び出してアクティビティを閉じます。

AI グラスが接続されているかどうかを確認する

アクティビティを起動する前に、ユーザーの AI グラスがスマートフォンに接続されているかどうかを確認するには、 メソッドを使用します。ProjectedContext.isProjectedDeviceConnectedこのメソッドは、アプリが接続ステータスのリアルタイム更新を取得するために監視できる Flow<Boolean> を返します。

アクティビティを開始する

基本的なアクティビティを作成したので、グラスで起動できます。 グラスのハードウェアにアクセスするには、次のコードに示すように、投影されたコンテキストを使用するようにシステムに指示する特定の オプションを使用して、アプリがアクティビティを開始する必要があります。

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

ProjectedContextcreateProjectedActivityOptions メソッドは、投影されたコンテキストでアクティビティを開始するために必要なオプションを生成します。 context パラメータには、スマートフォンまたはグラス デバイスのコンテキストを指定できます。

次のステップ

AI グラス用のアクティビティを初めて作成したので、その機能を拡張する他の方法を調べてみましょう。