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