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