Skip to content

Commit a1b2902

Browse files
trambui09devbridie
andauthored
add snippets on how to request projected permissions from phone activity (#969)
* add snippets on how to request projected permissions from phone activity * address gemini code review feedback * addressed Dereck's feedback and renamed classes to Projected instead of Glasses * remove the unused display category string --------- Co-authored-by: Dereck Bridie <devbridie@users.noreply.github.com>
1 parent f37dcf3 commit a1b2902

5 files changed

Lines changed: 103 additions & 8 deletions

File tree

xr/src/main/AndroidManifest.xml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,12 @@
2424
android:label="XR"
2525
tools:ignore="MissingApplicationIcon">
2626
<activity
27-
android:name="com.example.xr.projected.GlassesMainActivity"
27+
android:name="com.example.xr.projected.ProjectedMainActivity"
2828
android:exported="true"
29-
android:requiredDisplayCategory="@string/display_category_xr_projected">
29+
android:requiredDisplayCategory="android.hardware.display.category.XR_PROJECTED">
3030
<intent-filter>
3131
<action android:name="android.intent.action.MAIN" />
32+
<category android:name="android.intent.category.XR_PROJECTED_LAUNCHER"/>
3233
</intent-filter>
3334
</activity>
3435
<activity
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Copyright 2026 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.example.xr.projected
18+
19+
import android.Manifest
20+
import android.content.pm.PackageManager
21+
import android.os.Build
22+
import androidx.activity.ComponentActivity
23+
import androidx.annotation.RequiresApi
24+
import androidx.core.content.ContextCompat
25+
import androidx.xr.projected.ProjectedContext
26+
import androidx.xr.projected.experimental.ExperimentalProjectedApi
27+
28+
/**
29+
* Demonstrates how to request glasses-specific hardware permissions
30+
* from an Activity running on the host phone.
31+
*/
32+
class PermissionsFromPhoneActivity : ComponentActivity() {
33+
34+
private var projectedDeviceId: Int = -1
35+
36+
// [START androidxr_projected_permissions_from_phone_activity_permission_result_callback]
37+
private companion object {
38+
// REQUEST_CODE_GLASSES_CAMERA is a developer-defined constant.
39+
const val REQUEST_CODE_GLASSES_CAMERA = 1001
40+
}
41+
42+
@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
43+
override fun onRequestPermissionsResult(
44+
requestCode: Int,
45+
permissions: Array<out String>,
46+
grantResults: IntArray,
47+
deviceId: Int
48+
) {
49+
super.onRequestPermissionsResult(requestCode, permissions, grantResults, deviceId)
50+
51+
// Handle the result of the permission request
52+
if (requestCode == REQUEST_CODE_GLASSES_CAMERA && deviceId == projectedDeviceId) {
53+
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
54+
// Proceed with glasses camera features
55+
} else {
56+
// Handle glasses permission denied
57+
}
58+
}
59+
}
60+
// [END androidxr_projected_permissions_from_phone_activity_permission_result_callback]
61+
62+
@OptIn(ExperimentalProjectedApi::class)
63+
@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
64+
private fun checkAndRequestProjectedCameraPermission() {
65+
66+
try {
67+
val projectedContext = ProjectedContext.createProjectedDeviceContext(this)
68+
projectedDeviceId = projectedContext.deviceId
69+
70+
// [START androidxr_projected_permissions_from_phone_activity_has_permission]
71+
// Pass the projected context to check if projected permissions are granted
72+
val hasPermission = ContextCompat.checkSelfPermission(
73+
projectedContext,
74+
Manifest.permission.CAMERA
75+
) == PackageManager.PERMISSION_GRANTED
76+
// [END androidxr_projected_permissions_from_phone_activity_has_permission]
77+
78+
if (hasPermission) {
79+
// Proceed with camera features
80+
} else {
81+
// [START androidxr_projected_permissions_from_phone_activity_request_permission]
82+
// Request the projected permission from phone activity
83+
requestPermissions(
84+
arrayOf(Manifest.permission.CAMERA),
85+
// REQUEST_CODE_GLASSES_CAMERA is a developer-defined constant
86+
REQUEST_CODE_GLASSES_CAMERA,
87+
projectedDeviceId
88+
)
89+
// [END androidxr_projected_permissions_from_phone_activity_request_permission]
90+
}
91+
} catch (e: IllegalStateException) {
92+
// Handle the case where the projected device is not found
93+
}
94+
}
95+
}

xr/src/main/java/com/example/xr/projected/PhoneMainActivity.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ fun ConnectionScreen() {
188188
// [START androidxr_projected_start_glasses_activity]
189189

190190
val options = ProjectedContext.createProjectedActivityOptions(context)
191-
val intent = Intent(context, GlassesMainActivity::class.java)
191+
val intent = Intent(context, ProjectedMainActivity::class.java)
192192
context.startActivity(intent, options.toBundle())
193193

194194
// [END androidxr_projected_start_glasses_activity]

xr/src/main/java/com/example/xr/projected/GlassesMainActivity.kt renamed to xr/src/main/java/com/example/xr/projected/ProjectedMainActivity.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ import kotlinx.coroutines.launch
5050

5151
// [START androidxr_projected_ai_glasses_activity]
5252
@OptIn(ExperimentalProjectedApi::class)
53-
class GlassesMainActivity : ComponentActivity() {
53+
class ProjectedMainActivity : ComponentActivity() {
5454

5555
private var displayController: ProjectedDisplayController? = null
5656
private var isVisualUiSupported by mutableStateOf(false)
@@ -106,14 +106,14 @@ class GlassesMainActivity : ComponentActivity() {
106106
lifecycleScope.launch {
107107
// [START androidxr_projected_device_capabilities_check]
108108
// Check device capabilities
109-
val projectedDeviceController = ProjectedDeviceController.create(this@GlassesMainActivity)
109+
val projectedDeviceController = ProjectedDeviceController.create(this@ProjectedMainActivity)
110110
isVisualUiSupported = projectedDeviceController.capabilities.contains(CAPABILITY_VISUAL_UI)
111111
// [END androidxr_projected_device_capabilities_check]
112112

113-
val controller = ProjectedDisplayController.create(this@GlassesMainActivity)
113+
val controller = ProjectedDisplayController.create(this@ProjectedMainActivity)
114114
displayController = controller
115115
val observer = GlassesLifecycleObserver(
116-
context = this@GlassesMainActivity,
116+
context = this@ProjectedMainActivity,
117117
controller = controller,
118118
onVisualsChanged = { visualsOn -> areVisualsOn = visualsOn }
119119
)

xr/src/main/res/values/strings.xml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,4 @@
1515
limitations under the License.
1616
-->
1717
<resources>
18-
<string name="display_category_xr_projected">xr_projected</string>
1918
</resources>

0 commit comments

Comments
 (0)