1 /*
<lambda>null2  * Copyright (C) 2022 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 
18 package com.android.systemui.power.domain.interactor
19 
20 import android.os.PowerManager
21 import com.android.systemui.classifier.FalsingCollector
22 import com.android.systemui.classifier.FalsingCollectorActual
23 import com.android.systemui.dagger.SysUISingleton
24 import com.android.systemui.plugins.statusbar.StatusBarStateController
25 import com.android.systemui.power.data.repository.PowerRepository
26 import com.android.systemui.power.shared.model.ScreenPowerState
27 import com.android.systemui.power.shared.model.WakeSleepReason
28 import com.android.systemui.power.shared.model.WakefulnessState
29 import com.android.systemui.statusbar.phone.ScreenOffAnimationController
30 import javax.inject.Inject
31 import kotlinx.coroutines.flow.Flow
32 import kotlinx.coroutines.flow.distinctUntilChanged
33 import kotlinx.coroutines.flow.map
34 
35 /** Hosts business logic for interacting with the power system. */
36 @SysUISingleton
37 class PowerInteractor
38 @Inject
39 constructor(
40     private val repository: PowerRepository,
41     @FalsingCollectorActual private val falsingCollector: FalsingCollector,
42     private val screenOffAnimationController: ScreenOffAnimationController,
43     private val statusBarStateController: StatusBarStateController,
44 ) {
45     /** Whether the screen is on or off. */
46     val isInteractive: Flow<Boolean> = repository.isInteractive
47 
48     /**
49      * Whether we're awake or asleep, along with additional information about why we're awake/asleep
50      * and whether the power button gesture has been triggered (a special case that affects
51      * wakefulness).
52      *
53      * Unless you need to respond differently to different [WakeSleepReason]s, you should use
54      * [isAwake].
55      */
56     val detailedWakefulness = repository.wakefulness
57 
58     /**
59      * Whether we're awake (screen is on and responding to user touch) or asleep (screen is off, or
60      * on AOD).
61      */
62     val isAwake =
63         repository.wakefulness
64             .map { it.isAwake() }
65             .distinctUntilChanged(checkEquivalentUnlessEmitDuplicatesUnderTest)
66 
67     /** Helper flow in case "isAsleep" reads better than "!isAwake". */
68     val isAsleep = isAwake.map { !it }
69 
70     val screenPowerState = repository.screenPowerState
71 
72     /**
73      * Notifies the power interactor that a user touch happened.
74      *
75      * @param noChangeLights If true, does not cause the keyboard backlight to turn on because of
76      *   this event. This is set when the power key is pressed. We want the device to stay on while
77      *   the button is down, but we're about to turn off the screen so we don't want the keyboard
78      *   backlight to turn on again. Otherwise the lights flash on and then off and it looks weird.
79      */
80     fun onUserTouch(noChangeLights: Boolean = false) =
81         repository.userTouch(noChangeLights = noChangeLights)
82 
83     /**
84      * Wakes up the device if the device was dozing.
85      *
86      * @param why a string explaining why we're waking the device for debugging purposes. Should be
87      *   in SCREAMING_SNAKE_CASE.
88      * @param wakeReason the PowerManager-based reason why we're waking the device.
89      */
90     fun wakeUpIfDozing(why: String, @PowerManager.WakeReason wakeReason: Int) {
91         if (
92             statusBarStateController.isDozing && screenOffAnimationController.allowWakeUpIfDozing()
93         ) {
94             repository.wakeUp(why, wakeReason)
95             falsingCollector.onScreenOnFromTouch()
96         }
97     }
98 
99     /**
100      * Wakes up the device if the device was dozing or going to sleep in order to display a
101      * full-screen intent.
102      */
103     fun wakeUpForFullScreenIntent() {
104         if (repository.wakefulness.value.isAsleep() || statusBarStateController.isDozing) {
105             repository.wakeUp(why = FSI_WAKE_WHY, wakeReason = PowerManager.WAKE_REASON_APPLICATION)
106         }
107     }
108 
109     /**
110      * Wakes up the device if dreaming with a screensaver.
111      *
112      * @param why a string explaining why we're waking the device for debugging purposes. Should be
113      *   in SCREAMING_SNAKE_CASE.
114      * @param wakeReason the PowerManager-based reason why we're waking the device.
115      */
116     fun wakeUpIfDreaming(why: String, @PowerManager.WakeReason wakeReason: Int) {
117         if (statusBarStateController.isDreaming) {
118             repository.wakeUp(why, wakeReason)
119         }
120     }
121 
122     /** Wakes up the device for the Side FPS acquisition event. */
123     fun wakeUpForSideFingerprintAcquisition() {
124         repository.wakeUp("SFPS_FP_ACQUISITION_STARTED", PowerManager.WAKE_REASON_BIOMETRIC)
125     }
126 
127     /**
128      * Called from [KeyguardService] to inform us that the device has started waking up. This is the
129      * canonical source of wakefulness information for System UI. This method should not be called
130      * from anywhere else.
131      *
132      * In tests, you should be able to use [setAwakeForTest] rather than calling this method
133      * directly.
134      */
135     fun onStartedWakingUp(
136         @PowerManager.WakeReason reason: Int,
137         powerButtonLaunchGestureTriggeredOnWakeUp: Boolean,
138     ) {
139         // If the launch gesture was previously detected, either via onCameraLaunchGestureDetected
140         // or onFinishedGoingToSleep(), carry that state forward. It will be reset by the next
141         // onStartedGoingToSleep.
142         val powerButtonLaunchGestureTriggered =
143             powerButtonLaunchGestureTriggeredOnWakeUp ||
144                 repository.wakefulness.value.powerButtonLaunchGestureTriggered
145 
146         repository.updateWakefulness(
147             rawState = WakefulnessState.STARTING_TO_WAKE,
148             lastWakeReason = WakeSleepReason.fromPowerManagerWakeReason(reason),
149             powerButtonLaunchGestureTriggered = powerButtonLaunchGestureTriggered,
150         )
151     }
152 
153     /**
154      * Called from [KeyguardService] to inform us that the device has finished waking up. This is
155      * the canonical source of wakefulness information for System UI. This method should not be
156      * called from anywhere else.
157      *
158      * In tests, you should be able to use [setAwakeForTest] rather than calling this method
159      * directly.
160      */
161     fun onFinishedWakingUp() {
162         repository.updateWakefulness(rawState = WakefulnessState.AWAKE)
163     }
164 
165     /**
166      * Called from [KeyguardService] to inform us that the device is going to sleep. This is the
167      * canonical source of wakefulness information for System UI. This method should not be called
168      * from anywhere else.
169      *
170      * In tests, you should be able to use [setAsleepForTest] rather than calling this method
171      * directly.
172      */
173     fun onStartedGoingToSleep(@PowerManager.GoToSleepReason reason: Int) {
174         repository.updateWakefulness(
175             rawState = WakefulnessState.STARTING_TO_SLEEP,
176             lastSleepReason = WakeSleepReason.fromPowerManagerSleepReason(reason),
177             powerButtonLaunchGestureTriggered = false,
178         )
179     }
180 
181     /**
182      * Called from [KeyguardService] to inform us that the device has gone to sleep. This is the
183      * canonical source of wakefulness information for System UI. This method should not be called
184      * from anywhere else.
185      *
186      * In tests, you should be able to use [setAsleepForTest] rather than calling this method
187      * directly.
188      */
189     fun onFinishedGoingToSleep(
190         powerButtonLaunchGestureTriggeredDuringSleep: Boolean,
191     ) {
192         // If the launch gesture was previously detected via onCameraLaunchGestureDetected, carry
193         // that state forward. It will be reset by the next onStartedGoingToSleep.
194         val powerButtonLaunchGestureTriggered =
195             powerButtonLaunchGestureTriggeredDuringSleep ||
196                 repository.wakefulness.value.powerButtonLaunchGestureTriggered
197 
198         repository.updateWakefulness(
199             rawState = WakefulnessState.ASLEEP,
200             powerButtonLaunchGestureTriggered = powerButtonLaunchGestureTriggered,
201         )
202     }
203 
204     fun onScreenPowerStateUpdated(state: ScreenPowerState) {
205         repository.setScreenPowerState(state)
206     }
207 
208     fun onCameraLaunchGestureDetected() {
209         repository.updateWakefulness(powerButtonLaunchGestureTriggered = true)
210     }
211 
212     companion object {
213         private const val FSI_WAKE_WHY = "full_screen_intent"
214 
215         /**
216          * If true, [isAwake] and [isAsleep] will emit the next value even if it's not distinct.
217          * This is useful for setting up tests.
218          */
219         private var emitDuplicateWakefulnessValue = false
220 
221         /**
222          * Returns whether old == new unless we want to emit duplicate values, in which case we
223          * reset that flag and then return false.
224          */
225         private val checkEquivalentUnlessEmitDuplicatesUnderTest: (Boolean, Boolean) -> Boolean =
226             { old, new ->
227                 if (emitDuplicateWakefulnessValue) {
228                     emitDuplicateWakefulnessValue = false
229                     false
230                 } else {
231                     old == new
232                 }
233             }
234 
235         /**
236          * Helper method for tests to simulate the device waking up.
237          *
238          * If [forceEmit] is true, forces [isAwake] to emit true, even if the PowerInteractor in the
239          * test was already awake. This is useful for the first setAwakeForTest call in a test,
240          * since otherwise, tests would need to set the PowerInteractor asleep first to ensure
241          * [isAwake] emits, which can cause superfluous interactions with mocks.
242          *
243          * This is also preferred to calling [onStartedWakingUp]/[onFinishedWakingUp] directly, as
244          * we want to keep the started/finished concepts internal to keyguard as much as possible.
245          */
246         @JvmOverloads
247         fun PowerInteractor.setAwakeForTest(
248             @PowerManager.WakeReason reason: Int = PowerManager.WAKE_REASON_UNKNOWN,
249             forceEmit: Boolean = false
250         ) {
251             emitDuplicateWakefulnessValue = forceEmit
252 
253             this.onStartedWakingUp(
254                 reason = reason,
255                 powerButtonLaunchGestureTriggeredOnWakeUp = false,
256             )
257             this.onFinishedWakingUp()
258         }
259 
260         /**
261          * Helper method for tests to simulate the device sleeping.
262          *
263          * If [forceEmit] is true, forces [isAsleep] to emit true, even if the PowerInteractor in
264          * the test was already asleep. This is useful for the first setAsleepForTest call in a
265          * test, since otherwise, tests would need to set the PowerInteractor awake first to ensure
266          * [isAsleep] emits, but that can cause superfluous interactions with mocks.
267          *
268          * This is also preferred to calling [onStartedGoingToSleep]/[onFinishedGoingToSleep]
269          * directly, as we want to keep the started/finished concepts internal to keyguard as much
270          * as possible.
271          */
272         @JvmOverloads
273         fun PowerInteractor.setAsleepForTest(
274             @PowerManager.GoToSleepReason sleepReason: Int = PowerManager.GO_TO_SLEEP_REASON_MIN,
275             forceEmit: Boolean = false,
276         ) {
277             emitDuplicateWakefulnessValue = forceEmit
278 
279             this.onStartedGoingToSleep(reason = sleepReason)
280             this.onFinishedGoingToSleep(
281                 powerButtonLaunchGestureTriggeredDuringSleep = false,
282             )
283         }
284 
285         /** Helper method for tests to simulate the device screen state change event. */
286         fun PowerInteractor.setScreenPowerState(screenPowerState: ScreenPowerState) {
287             this.onScreenPowerStateUpdated(screenPowerState)
288         }
289     }
290 }
291