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