오디오 글래스 및 디스플레이 글래스용 첫 번째 활동 만들기

적용 가능한 XR 기기
이 안내는 이러한 유형의 XR 기기용 환경을 구축하는 데 도움이 됩니다.
오디오 및
디스플레이 안경

오디오 글래스 및 디스플레이 글래스의 증강 환경은 기존 Android Activity 프레임워크 API를 기반으로 빌드되며 이러한 글래스의 고유한 측면을 지원하기 위한 추가 개념이 포함되어 있습니다. 기기에서 전체 APK를 실행하는 XR 헤드셋과 달리 오디오 안경과 디스플레이 안경은 휴대전화의 기존 앱 내에서 실행되는 전용 활동을 사용합니다. 이 활동은 호스트 기기에서 안경으로 투영됩니다.

오디오 글라스 및 디스플레이 글라스용 앱 환경을 만들려면 새로운 프로젝션 Activity를 만들어 기존 휴대전화 앱을 확장합니다. 이 활동은 안경에서 앱의 기본 실행 진입점 역할을 합니다. 이 접근 방식은 휴대전화 환경과 안경 환경 간에 비즈니스 로직을 공유하고 재사용할 수 있으므로 개발을 간소화합니다.

버전 호환성

Jetpack XR SDK의 Android SDK 호환성 요구사항을 확인하세요.

종속 항목

다음 오디오 글래스 및 디스플레이 글래스용 라이브러리 종속 항목을 추가합니다.

Groovy

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

Kotlin

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 음성 명령으로 내 예정된 활동을 검색할 수 있도록 하는 특수 카테고리입니다.

    사용자가 앱 이름을 사용하여 음성 명령을 실행하면('AI 카탈로그 샘플 열기', 'AI 카탈로그 샘플 실행', 'AI 카탈로그 샘플 시작' 등) 시스템은 이 카테고리를 사용하여 오디오 또는 디스플레이 글라스에서 지정된 활동을 찾아 시작합니다.

활동 만들기

다음으로 디스플레이가 켜질 때마다 AI 안경에 무언가를 표시할 수 있는 작은 활동을 만듭니다.

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

            val controller = ProjectedDisplayController.create(this@ProjectedMainActivity)
            displayController = controller
            val observer = GlassesLifecycleObserver(
                context = this@ProjectedMainActivity,
                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를 사용하도록 선택합니다.
  • ProjectedMainActivity는 모바일 개발에서 예상할 수 있듯이 ComponentActivity를 확장합니다.
  • 모든 글라스에 디스플레이가 있는 것은 아니므로 ProjectedDeviceController를 사용하여 기기에 디스플레이가 있는지 확인합니다.
  • onCreate 함수 내의 setContent 블록은 활동의 컴포저블 UI 트리의 루트를 정의합니다. Jetpack Compose Glimmer를 사용하여 HomeScreen 컴포저블을 구현합니다.
  • 활동의 onCreate 메서드 중에 UI를 초기화합니다 (투영된 활동 수명 주기 참고).
  • 글래스 하드웨어에 액세스하는 카메라 관련 기능을 준비하려면 권한 런처를 등록하고, hasCameraPermissionrequestHardwarePermissions 함수를 정의하고, 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, ProjectedMainActivity::class.java)
context.startActivity(intent, options.toBundle())

ProjectedContextcreateProjectedActivityOptions 메서드는 예측된 컨텍스트에서 활동을 시작하는 데 필요한 옵션을 생성합니다. context 매개변수는 휴대전화 또는 안경 기기의 컨텍스트일 수 있습니다.

다음 단계

이제 오디오 글라스와 디스플레이 글라스용 첫 번째 Activity를 만들었으니 기능을 확장할 수 있는 다른 방법을 살펴보세요.