in AlwaysOnKotlin/compose/src/main/java/com/example/android/wearable/wear/alwayson/AlwaysOnApp.kt [67:200]
fun AlwaysOnApp(
ambientState: AmbientState,
ambientUpdateTimestamp: Instant,
clock: Clock,
activeDispatcher: CoroutineDispatcher
) {
val ambientUpdateAlarmManager = rememberAlarmManager()
val context = LocalContext.current
/**
* Retrieves a PendingIntent that will perform a broadcast. You could also use getActivity()
* to retrieve a PendingIntent that will start a new activity, but be aware that actually
* triggers onNewIntent() which causes lifecycle changes (onPause() and onResume()) which
* might trigger code to be re-executed more often than you want.
*
* If you do end up using getActivity(), also make sure you have set activity launchMode to
* singleInstance in the manifest.
*
* Otherwise, it is easy for the AlarmManager launch Intent to open a new activity
* every time the Alarm is triggered rather than reusing this Activity.
*/
val ambientUpdatePendingIntent = remember(context) {
PendingIntent.getBroadcast(
context,
0,
ambientUpdateIntent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
}
/**
* A ping used to set up a loopback side-effect loop, to continuously update the time.
*/
var updateDataTrigger by remember { mutableStateOf(0L) }
/**
* The current instant to display
*/
var currentInstant by remember { mutableStateOf(Instant.now(clock)) }
/**
* The current time to display
*/
var currentTime by remember { mutableStateOf(LocalTime.now(clock)) }
/**
* The number of times the current time and instant have been updated
*/
var drawCount by remember { mutableStateOf(0) }
fun updateData() {
updateDataTrigger++
currentInstant = Instant.now(clock)
currentTime = LocalTime.now(clock)
drawCount++
}
val lifecycleOwner = LocalLifecycleOwner.current
/**
* Construct a boolean indicating if we are resumed.
*/
val isResumed by produceState(initialValue = false) {
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
value = true
try {
awaitCancellation()
} finally {
value = false
}
}
}
if (isResumed) {
when (ambientState) {
is AmbientState.Ambient -> {
// When we are resumed and ambient, setup the broadcast receiver
SystemBroadcastReceiver(systemAction = AMBIENT_UPDATE_ACTION) {
updateData()
}
DisposableEffect(ambientUpdateAlarmManager, ambientUpdatePendingIntent) {
onDispose {
// Upon leaving resumption or ambient, cancel any ongoing pending intents
ambientUpdateAlarmManager.cancel(ambientUpdatePendingIntent)
}
}
}
AmbientState.Interactive -> Unit
}
// Whenever we change ambient state (and initially) update the data.
LaunchedEffect(ambientState) {
updateData()
}
// Then, setup a ping to refresh data again: either via the alarm manager, or simply
// after a delay
LaunchedEffect(updateDataTrigger, ambientState) {
when (ambientState) {
is AmbientState.Ambient -> {
val triggerTime = currentInstant.getNextInstantWithInterval(
AMBIENT_INTERVAL
)
ambientUpdateAlarmManager.setExact(
AlarmManager.RTC_WAKEUP,
triggerTime.toEpochMilli(),
ambientUpdatePendingIntent
)
}
AmbientState.Interactive -> {
val delay = currentInstant.getDelayToNextInstantWithInterval(
ACTIVE_INTERVAL
)
withContext(activeDispatcher) {
// Delay on the active dispatcher for testability
delay(delay.toMillis())
}
updateData()
}
}
}
}
MaterialTheme {
AlwaysOnScreen(
ambientState = ambientState,
ambientUpdateTimestamp = ambientUpdateTimestamp,
drawCount = drawCount,
currentInstant = currentInstant,
currentTime = currentTime
)
}
}