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-alpha12"
    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-alpha12")
    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 コンポーネントは、AI グラスのディスプレイに「Hello, AI Glasses!」というテキストを表示します。
  • Jetpack Compose Glimmer の Button は、AI グラス アクティビティの onClose を介して finish() を呼び出すことでアクティビティを閉じます。

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

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

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

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

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

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

次のステップ

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