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