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