1 /* <lambda>null2 * Copyright (C) 2023 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 * http://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.android.systemui.flags 18 19 import android.util.Log 20 import com.android.systemui.dagger.qualifiers.Application 21 import com.android.systemui.dagger.qualifiers.Background 22 import com.android.systemui.flags.ConditionalRestarter.Condition 23 import java.util.concurrent.TimeUnit 24 import javax.inject.Inject 25 import javax.inject.Named 26 import kotlin.coroutines.CoroutineContext 27 import kotlinx.coroutines.CoroutineScope 28 import kotlinx.coroutines.delay 29 import kotlinx.coroutines.flow.Flow 30 import kotlinx.coroutines.flow.combine 31 import kotlinx.coroutines.flow.first 32 import kotlinx.coroutines.flow.transformLatest 33 import kotlinx.coroutines.launch 34 35 /** Restarts the process after all passed in [Condition]s are true. */ 36 class ConditionalRestarter 37 @Inject 38 constructor( 39 private val systemExitRestarter: SystemExitRestarter, 40 private val conditions: Set<@JvmSuppressWildcards Condition>, 41 @Named(RESTART_DELAY) private val restartDelaySec: Long, 42 @Application private val applicationScope: CoroutineScope, 43 @Background private val backgroundDispatcher: CoroutineContext, 44 ) : Restarter { 45 46 private var pendingReason = "" 47 private var androidRestartRequested = false 48 49 override fun restartSystemUI(reason: String) { 50 Log.d(FeatureFlagsClassicDebug.TAG, "SystemUI Restart requested. Restarting when idle.") 51 scheduleRestart(reason) 52 } 53 54 override fun restartAndroid(reason: String) { 55 Log.d(FeatureFlagsClassicDebug.TAG, "Android Restart requested. Restarting when idle.") 56 androidRestartRequested = true 57 scheduleRestart(reason) 58 } 59 60 private fun scheduleRestart(reason: String = "") { 61 pendingReason = if (reason.isEmpty()) pendingReason else reason 62 63 applicationScope.launch(backgroundDispatcher) { 64 combine(conditions.map { condition -> condition.canRestartNow }) { it.all { it } } 65 // Once all conditions are met, delay. 66 .transformLatest { allConditionsMet -> 67 if (allConditionsMet) { 68 delay(TimeUnit.SECONDS.toMillis(restartDelaySec)) 69 emit(Unit) 70 } 71 } 72 // Once we have successfully delayed _once_, continue to restart. 73 .first() 74 75 restartNow() 76 } 77 } 78 79 private fun restartNow() { 80 if (androidRestartRequested) { 81 systemExitRestarter.restartAndroid(pendingReason) 82 } else { 83 systemExitRestarter.restartSystemUI(pendingReason) 84 } 85 } 86 87 interface Condition { 88 /** 89 * Should return true if the system is ready to restart. 90 * 91 * A call to this function means that we want to restart and are waiting for this condition 92 * to return true. 93 * 94 * retryFn should be cached if it is _not_ ready to restart, and later called when it _is_ 95 * ready to restart. At that point, this method will be called again to verify that the 96 * system is ready. 97 * 98 * Multiple calls to an instance of this method may happen for a single restart attempt if 99 * multiple [Condition]s are being checked. If any one [Condition] returns false, all the 100 * [Condition]s will need to be rechecked on the next restart attempt. 101 */ 102 val canRestartNow: Flow<Boolean> 103 } 104 105 companion object { 106 const val RESTART_DELAY = "restarter_restart_delay" 107 } 108 } 109