Different types of audio glasses and display glasses have different capabilities. After planning how you'll support different types of AI devices, you can check for device capabilities at runtime to provide the best experience for a user's device.
Check whether a device has a display
Display glasses can show UIs built with Jetpack Compose Glimmer. The following example shows how to check whether a glasses device has a display:
// Check device capabilities val projectedDeviceController = ProjectedDeviceController.create(this@ProjectedMainActivity) isVisualUiSupported = projectedDeviceController.capabilities.contains(CAPABILITY_VISUAL_UI)
React to display state changes
On display glasses, the display can time out or the user can turn off the
display. To design activities that run whether the display is on or off, use
addPresentationModeChangedListener to be notified when the display state
changes. You can tune your activity for the appropriate amount of audio
information depending on display state.
ProjectedDisplayController.create(activity).addPresentationModeChangedListener {
presentationModeFlags ->
val areVisualsOff = !presentationModeFlags.hasPresentationMode(VISUALS_ON)
}
Keep the display on
On display glasses, you can request that the system keep the screen on and
prevent the screen from timing out using addLayoutParamsFlags.
var projectedDisplayController = ProjectedDisplayController.create(activity)
projectedDisplayController.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
Monitor battery state changes
To track the real-time battery status of connected audio or display glasses, use
the addBatteryStateChangedListener method provided by the
ProjectedDeviceController. This listener notifies your app whenever the
battery level or charging state changes, allowing you to optimize app
performance, enable power-savings modes, or update your user interface.
@OptIn(ExperimentalProjectedApi::class) private var deviceController: ProjectedDeviceController? = null private val batteryListener = { batteryState: BatteryState -> val batteryLevel = batteryState.batteryLevel val isCharging = batteryState.isCharging if (batteryLevel <= 15 && !isCharging) { switchToLowPowerMode() } updateBatteryUi(batteryLevel, isCharging) } // ... @OptIn(ExperimentalProjectedApi::class) private fun monitorBatteryStatus() { lifecycleScope.launch { try { if (deviceController == null) { deviceController = ProjectedDeviceController.create(this@PhoneMainActivity) } if (lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) { deviceController?.addBatteryStateChangedListener(coroutineContext, batteryListener) } } catch (e: Exception) { Log.e("BatteryMonitor", "Unexpected error: ${e.message}") } } }
When you are done monitoring the device or when your component is being
destroyed, call close on the ProjectedDeviceController to release underlying
resources properly.
@OptIn(ExperimentalProjectedApi::class) override fun onDestroy() { super.onDestroy() // Unregisters the active listeners deviceController?.close() deviceController = null }
You can also unregister a listener explicitly before the scope ends by using
removeBatteryStateChangedListener.
@OptIn(ExperimentalProjectedApi::class) override fun onPause() { super.onPause() // Explicitly unregister the listener when it is no longer needed deviceController?.removeBatteryStateChangedListener(batteryListener) }