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