fun AlwaysOnApp()

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