1 /* 2 * Copyright (C) 2016 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.doze; 18 19 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE; 20 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING; 21 22 import android.annotation.MainThread; 23 import android.content.res.Configuration; 24 import android.hardware.display.AmbientDisplayConfiguration; 25 import android.util.Log; 26 import android.view.Display; 27 28 import com.android.app.tracing.coroutines.TrackTracer; 29 import com.android.internal.util.Preconditions; 30 import com.android.systemui.dock.DockManager; 31 import com.android.systemui.doze.dagger.DozeScope; 32 import com.android.systemui.doze.dagger.WrappedService; 33 import com.android.systemui.keyguard.WakefulnessLifecycle; 34 import com.android.systemui.keyguard.WakefulnessLifecycle.Wakefulness; 35 import com.android.systemui.settings.UserTracker; 36 import com.android.systemui.statusbar.phone.DozeParameters; 37 import com.android.systemui.util.Assert; 38 import com.android.systemui.util.wakelock.WakeLock; 39 40 import java.io.PrintWriter; 41 import java.util.ArrayList; 42 43 import javax.inject.Inject; 44 45 /** 46 * Orchestrates all things doze. 47 * 48 * DozeMachine implements a state machine that orchestrates how the UI and triggers work and 49 * interfaces with the power and screen states. 50 * 51 * During state transitions and in certain states, DozeMachine holds a wake lock. 52 */ 53 @DozeScope 54 public class DozeMachine { 55 56 static final String TAG = "DozeMachine"; 57 static final boolean DEBUG = DozeService.DEBUG; 58 private final DozeLog mDozeLog; 59 private static final String REASON_CHANGE_STATE = "DozeMachine#requestState"; 60 private static final String REASON_HELD_FOR_STATE = "DozeMachine#heldForState"; 61 62 public enum State { 63 /** Default state. Transition to INITIALIZED to get Doze going. */ 64 UNINITIALIZED, 65 /** Doze components are set up. Followed by transition to DOZE or DOZE_AOD. */ 66 INITIALIZED, 67 /** Regular doze. Device is asleep and listening for pulse triggers. */ 68 DOZE, 69 /** Deep doze. Device is asleep and is not listening for pulse triggers. */ 70 DOZE_SUSPEND_TRIGGERS, 71 /** Always-on doze. Device is asleep, showing UI and listening for pulse triggers. */ 72 DOZE_AOD, 73 /** Pulse has been requested. Device is awake and preparing UI */ 74 DOZE_REQUEST_PULSE, 75 /** Pulse is showing. Device is awake and showing UI. */ 76 DOZE_PULSING, 77 /** Pulse is showing with bright wallpaper. Device is awake and showing UI. */ 78 DOZE_PULSING_BRIGHT, 79 /** Pulse is done showing. Followed by transition to DOZE or DOZE_AOD. */ 80 DOZE_PULSE_DONE, 81 /** Doze is done. DozeService is finished. */ 82 FINISH, 83 /** AOD, but the display is temporarily off. */ 84 DOZE_AOD_PAUSED, 85 /** AOD, prox is near, transitions to DOZE_AOD_PAUSED after a timeout. */ 86 DOZE_AOD_PAUSING, 87 /** Always-on doze. Device is awake, showing docking UI and listening for pulse triggers. */ 88 DOZE_AOD_DOCKED; 89 canPulse()90 boolean canPulse() { 91 switch (this) { 92 case DOZE: 93 case DOZE_AOD: 94 case DOZE_AOD_PAUSED: 95 case DOZE_AOD_PAUSING: 96 case DOZE_AOD_DOCKED: 97 return true; 98 default: 99 return false; 100 } 101 } 102 staysAwake()103 boolean staysAwake() { 104 switch (this) { 105 case DOZE_REQUEST_PULSE: 106 case DOZE_PULSING: 107 case DOZE_PULSING_BRIGHT: 108 case DOZE_AOD_DOCKED: 109 return true; 110 default: 111 return false; 112 } 113 } 114 isAlwaysOn()115 boolean isAlwaysOn() { 116 return this == DOZE_AOD || this == DOZE_AOD_DOCKED; 117 } 118 screenState(DozeParameters parameters)119 int screenState(DozeParameters parameters) { 120 switch (this) { 121 case UNINITIALIZED: 122 case INITIALIZED: 123 return parameters.shouldControlScreenOff() ? Display.STATE_ON 124 : Display.STATE_OFF; 125 case DOZE_REQUEST_PULSE: 126 return parameters.getDisplayNeedsBlanking() ? Display.STATE_OFF 127 : Display.STATE_ON; 128 case DOZE_AOD_PAUSED: 129 case DOZE: 130 case DOZE_SUSPEND_TRIGGERS: 131 return Display.STATE_OFF; 132 case DOZE_PULSING: 133 case DOZE_PULSING_BRIGHT: 134 case DOZE_AOD_DOCKED: 135 return Display.STATE_ON; 136 case DOZE_AOD: 137 case DOZE_AOD_PAUSING: 138 return Display.STATE_DOZE_SUSPEND; 139 default: 140 return Display.STATE_UNKNOWN; 141 } 142 } 143 } 144 145 private final Service mDozeService; 146 private final WakeLock mWakeLock; 147 private final AmbientDisplayConfiguration mAmbientDisplayConfig; 148 private final WakefulnessLifecycle mWakefulnessLifecycle; 149 private final DozeHost mDozeHost; 150 private final DockManager mDockManager; 151 private final Part[] mParts; 152 private final UserTracker mUserTracker; 153 private final ArrayList<State> mQueuedRequests = new ArrayList<>(); 154 private State mState = State.UNINITIALIZED; 155 private int mPulseReason; 156 private boolean mWakeLockHeldForCurrentState = false; 157 private int mUiModeType = Configuration.UI_MODE_TYPE_NORMAL; 158 159 @Inject DozeMachine(@rappedService Service service, AmbientDisplayConfiguration ambientDisplayConfig, WakeLock wakeLock, WakefulnessLifecycle wakefulnessLifecycle, DozeLog dozeLog, DockManager dockManager, DozeHost dozeHost, Part[] parts, UserTracker userTracker)160 public DozeMachine(@WrappedService Service service, 161 AmbientDisplayConfiguration ambientDisplayConfig, 162 WakeLock wakeLock, WakefulnessLifecycle wakefulnessLifecycle, 163 DozeLog dozeLog, DockManager dockManager, 164 DozeHost dozeHost, Part[] parts, UserTracker userTracker) { 165 mDozeService = service; 166 mAmbientDisplayConfig = ambientDisplayConfig; 167 mWakefulnessLifecycle = wakefulnessLifecycle; 168 mWakeLock = wakeLock; 169 mDozeLog = dozeLog; 170 mDockManager = dockManager; 171 mDozeHost = dozeHost; 172 mParts = parts; 173 mUserTracker = userTracker; 174 for (Part part : parts) { 175 part.setDozeMachine(this); 176 } 177 } 178 179 /** 180 * Clean ourselves up. 181 */ destroy()182 public void destroy() { 183 for (Part part : mParts) { 184 part.destroy(); 185 } 186 } 187 188 /** 189 * Notifies the {@link DozeMachine} that {@link Configuration} has changed. 190 */ onConfigurationChanged(Configuration newConfiguration)191 public void onConfigurationChanged(Configuration newConfiguration) { 192 int newUiModeType = newConfiguration.uiMode & Configuration.UI_MODE_TYPE_MASK; 193 if (mUiModeType == newUiModeType) return; 194 mUiModeType = newUiModeType; 195 for (Part part : mParts) { 196 part.onUiModeTypeChanged(mUiModeType); 197 } 198 } 199 200 /** 201 * Requests transitioning to {@code requestedState}. 202 * 203 * This can be called during a state transition, in which case it will be queued until all 204 * queued state transitions are done. 205 * 206 * A wake lock is held while the transition is happening. 207 * 208 * Note that {@link #transitionPolicy} can modify what state will be transitioned to. 209 */ 210 @MainThread requestState(State requestedState)211 public void requestState(State requestedState) { 212 Preconditions.checkArgument(requestedState != State.DOZE_REQUEST_PULSE); 213 requestState(requestedState, DozeLog.PULSE_REASON_NONE); 214 } 215 216 @MainThread requestPulse(int pulseReason)217 public void requestPulse(int pulseReason) { 218 // Must not be called during a transition. There's no inherent problem with that, 219 // but there's currently no need to execute from a transition and it simplifies the 220 // code to not have to worry about keeping the pulseReason in mQueuedRequests. 221 Preconditions.checkState(!isExecutingTransition()); 222 requestState(State.DOZE_REQUEST_PULSE, pulseReason); 223 } 224 225 /** 226 * @return true if {@link DozeMachine} is currently in either {@link State#UNINITIALIZED} 227 * or {@link State#FINISH} 228 */ isUninitializedOrFinished()229 public boolean isUninitializedOrFinished() { 230 return mState == State.UNINITIALIZED || mState == State.FINISH; 231 } 232 onScreenState(int state)233 void onScreenState(int state) { 234 for (Part part : mParts) { 235 part.onScreenState(state); 236 } 237 } 238 requestState(State requestedState, int pulseReason)239 private void requestState(State requestedState, int pulseReason) { 240 Assert.isMainThread(); 241 if (DEBUG) { 242 Log.i(TAG, "request: current=" + mState + " req=" + requestedState, 243 new Throwable("here")); 244 } 245 246 boolean runNow = !isExecutingTransition(); 247 mQueuedRequests.add(requestedState); 248 if (runNow) { 249 mWakeLock.acquire(REASON_CHANGE_STATE); 250 for (int i = 0; i < mQueuedRequests.size(); i++) { 251 // Transitions in Parts can call back into requestState, which will 252 // cause mQueuedRequests to grow. 253 transitionTo(mQueuedRequests.get(i), pulseReason); 254 } 255 mQueuedRequests.clear(); 256 mWakeLock.release(REASON_CHANGE_STATE); 257 } 258 } 259 260 /** 261 * @return the current state. 262 * 263 * This must not be called during a transition. 264 */ 265 @MainThread getState()266 public State getState() { 267 Assert.isMainThread(); 268 if (isExecutingTransition()) { 269 throw new IllegalStateException("Cannot get state because there were pending " 270 + "transitions: " + mQueuedRequests); 271 } 272 return mState; 273 } 274 275 /** 276 * @return the current pulse reason. 277 * 278 * This is only valid if the machine is currently in one of the pulse states. 279 */ 280 @MainThread getPulseReason()281 public int getPulseReason() { 282 Assert.isMainThread(); 283 Preconditions.checkState(mState == State.DOZE_REQUEST_PULSE 284 || mState == State.DOZE_PULSING 285 || mState == State.DOZE_PULSING_BRIGHT 286 || mState == State.DOZE_PULSE_DONE, "must be in pulsing state, but is " + mState); 287 return mPulseReason; 288 } 289 290 /** Requests the PowerManager to wake up now. 291 * @param reason {@link DozeLog.Reason} that woke up the device.*/ wakeUp(@ozeLog.Reason int reason)292 public void wakeUp(@DozeLog.Reason int reason) { 293 mDozeService.requestWakeUp(reason); 294 } 295 isExecutingTransition()296 public boolean isExecutingTransition() { 297 return !mQueuedRequests.isEmpty(); 298 } 299 transitionTo(State requestedState, int pulseReason)300 private void transitionTo(State requestedState, int pulseReason) { 301 State newState = transitionPolicy(requestedState); 302 303 if (DEBUG) { 304 Log.i(TAG, "transition: old=" + mState + " req=" + requestedState + " new=" + newState); 305 } 306 307 if (newState == mState) { 308 return; 309 } 310 311 validateTransition(newState); 312 313 State oldState = mState; 314 mState = newState; 315 316 mDozeLog.traceState(newState); 317 TrackTracer.instantForGroup("keyguard", "doze_machine_state", newState.ordinal()); 318 319 updatePulseReason(newState, oldState, pulseReason); 320 performTransitionOnComponents(oldState, newState); 321 updateWakeLockState(newState); 322 323 resolveIntermediateState(newState); 324 } 325 updatePulseReason(State newState, State oldState, int pulseReason)326 private void updatePulseReason(State newState, State oldState, int pulseReason) { 327 if (newState == State.DOZE_REQUEST_PULSE) { 328 mPulseReason = pulseReason; 329 } else if (oldState == State.DOZE_PULSE_DONE) { 330 mPulseReason = DozeLog.PULSE_REASON_NONE; 331 } 332 } 333 performTransitionOnComponents(State oldState, State newState)334 private void performTransitionOnComponents(State oldState, State newState) { 335 for (Part p : mParts) { 336 p.transitionTo(oldState, newState); 337 } 338 mDozeLog.traceDozeStateSendComplete(newState); 339 340 if (newState == State.FINISH) { 341 mDozeService.finish(); 342 } 343 } 344 validateTransition(State newState)345 private void validateTransition(State newState) { 346 try { 347 switch (mState) { 348 case FINISH: 349 Preconditions.checkState(newState == State.FINISH); 350 break; 351 case UNINITIALIZED: 352 Preconditions.checkState(newState == State.INITIALIZED); 353 break; 354 } 355 switch (newState) { 356 case UNINITIALIZED: 357 throw new IllegalArgumentException("can't transition to UNINITIALIZED"); 358 case INITIALIZED: 359 Preconditions.checkState(mState == State.UNINITIALIZED); 360 break; 361 case DOZE_PULSING: 362 Preconditions.checkState(mState == State.DOZE_REQUEST_PULSE); 363 break; 364 case DOZE_PULSE_DONE: 365 Preconditions.checkState( 366 mState == State.DOZE_REQUEST_PULSE || mState == State.DOZE_PULSING 367 || mState == State.DOZE_PULSING_BRIGHT); 368 break; 369 default: 370 break; 371 } 372 } catch (RuntimeException e) { 373 throw new IllegalStateException("Illegal Transition: " + mState + " -> " + newState, e); 374 } 375 } 376 transitionPolicy(State requestedState)377 private State transitionPolicy(State requestedState) { 378 if (mState == State.FINISH) { 379 return State.FINISH; 380 } 381 if (mUiModeType == Configuration.UI_MODE_TYPE_CAR 382 && (requestedState.canPulse() || requestedState.staysAwake())) { 383 Log.i(TAG, "Doze is suppressed with all triggers disabled as car mode is active"); 384 mDozeLog.traceCarModeStarted(); 385 return State.DOZE_SUSPEND_TRIGGERS; 386 } 387 if (mDozeHost.isAlwaysOnSuppressed() && requestedState.isAlwaysOn()) { 388 Log.i(TAG, "Doze is suppressed by an app. Suppressing state: " + requestedState); 389 mDozeLog.traceAlwaysOnSuppressed(requestedState, "app"); 390 return State.DOZE; 391 } 392 if (mDozeHost.isPowerSaveActive() && requestedState.isAlwaysOn()) { 393 Log.i(TAG, "Doze is suppressed by battery saver. Suppressing state: " + requestedState); 394 mDozeLog.traceAlwaysOnSuppressed(requestedState, "batterySaver"); 395 return State.DOZE; 396 } 397 if ((mState == State.DOZE_AOD_PAUSED || mState == State.DOZE_AOD_PAUSING 398 || mState == State.DOZE_AOD || mState == State.DOZE 399 || mState == State.DOZE_AOD_DOCKED || mState == State.DOZE_SUSPEND_TRIGGERS) 400 && requestedState == State.DOZE_PULSE_DONE) { 401 Log.i(TAG, "Dropping pulse done because current state is already done: " + mState); 402 return mState; 403 } 404 if (requestedState == State.DOZE_REQUEST_PULSE && !mState.canPulse()) { 405 Log.i(TAG, "Dropping pulse request because current state can't pulse: " + mState); 406 return mState; 407 } 408 return requestedState; 409 } 410 updateWakeLockState(State newState)411 private void updateWakeLockState(State newState) { 412 boolean staysAwake = newState.staysAwake(); 413 if (mWakeLockHeldForCurrentState && !staysAwake) { 414 mWakeLock.release(REASON_HELD_FOR_STATE); 415 mWakeLockHeldForCurrentState = false; 416 } else if (!mWakeLockHeldForCurrentState && staysAwake) { 417 mWakeLock.acquire(REASON_HELD_FOR_STATE); 418 mWakeLockHeldForCurrentState = true; 419 } 420 } 421 resolveIntermediateState(State state)422 private void resolveIntermediateState(State state) { 423 switch (state) { 424 case INITIALIZED: 425 case DOZE_PULSE_DONE: 426 final State nextState; 427 @Wakefulness int wakefulness = mWakefulnessLifecycle.getWakefulness(); 428 if (state != State.INITIALIZED && (wakefulness == WAKEFULNESS_AWAKE 429 || wakefulness == WAKEFULNESS_WAKING)) { 430 nextState = State.FINISH; 431 } else if (mDockManager.isDocked()) { 432 nextState = mDockManager.isHidden() ? State.DOZE : State.DOZE_AOD_DOCKED; 433 } else if (mAmbientDisplayConfig.alwaysOnEnabled(mUserTracker.getUserId())) { 434 nextState = State.DOZE_AOD; 435 } else { 436 nextState = State.DOZE; 437 } 438 439 transitionTo(nextState, DozeLog.PULSE_REASON_NONE); 440 break; 441 default: 442 break; 443 } 444 } 445 446 /** Dumps the current state */ dump(PrintWriter pw)447 public void dump(PrintWriter pw) { 448 pw.print(" state="); pw.println(mState); 449 pw.print(" mUiModeType="); pw.println(mUiModeType); 450 pw.print(" wakeLockHeldForCurrentState="); pw.println(mWakeLockHeldForCurrentState); 451 pw.print(" wakeLock="); pw.println(mWakeLock); 452 pw.println("Parts:"); 453 for (Part p : mParts) { 454 p.dump(pw); 455 } 456 } 457 458 /** A part of the DozeMachine that needs to be notified about state changes. */ 459 public interface Part { 460 /** 461 * Transition from {@code oldState} to {@code newState}. 462 * 463 * This method is guaranteed to only be called while a wake lock is held. 464 */ transitionTo(State oldState, State newState)465 void transitionTo(State oldState, State newState); 466 467 /** Dump current state. For debugging only. */ dump(PrintWriter pw)468 default void dump(PrintWriter pw) {} 469 470 /** Give the Part a chance to clean itself up. */ destroy()471 default void destroy() {} 472 473 /** 474 * Alerts that the screenstate is being changed. 475 * Note: This may be called from within a call to transitionTo, so local DozeState may not 476 * be accurate nor match with the new displayState. 477 */ onScreenState(int displayState)478 default void onScreenState(int displayState) {} 479 480 /** Sets the {@link DozeMachine} when this Part is associated with one. */ setDozeMachine(DozeMachine dozeMachine)481 default void setDozeMachine(DozeMachine dozeMachine) {} 482 483 /** 484 * Notifies the Part about a change in {@link Configuration#uiMode}. 485 * 486 * @param newUiModeType {@link Configuration#UI_MODE_TYPE_NORMAL}, 487 * {@link Configuration#UI_MODE_TYPE_DESK}, 488 * {@link Configuration#UI_MODE_TYPE_CAR}, 489 * {@link Configuration#UI_MODE_TYPE_TELEVISION}, 490 * {@link Configuration#UI_MODE_TYPE_APPLIANCE}, 491 * {@link Configuration#UI_MODE_TYPE_WATCH}, 492 * or {@link Configuration#UI_MODE_TYPE_VR_HEADSET} 493 */ onUiModeTypeChanged(int newUiModeType)494 default void onUiModeTypeChanged(int newUiModeType) {} 495 } 496 497 /** A wrapper interface for {@link android.service.dreams.DreamService} */ 498 public interface Service { 499 /** Finish dreaming. */ finish()500 void finish(); 501 502 /** Request a display state. See {@link android.view.Display#STATE_DOZE}. */ setDozeScreenState(int state)503 void setDozeScreenState(int state); 504 505 /** Request waking up. */ requestWakeUp(@ozeLog.Reason int reason)506 void requestWakeUp(@DozeLog.Reason int reason); 507 508 /** Set screen brightness between 1 and 255 */ setDozeScreenBrightness(int brightness)509 void setDozeScreenBrightness(int brightness); 510 511 /** Set screen brightness between {@link PowerManager#BRIGHTNESS_MIN} and 512 * {@link PowerManager#BRIGHTNESS_MAX} */ setDozeScreenBrightnessFloat(float brightness)513 void setDozeScreenBrightnessFloat(float brightness); 514 515 class Delegate implements Service { 516 private final Service mDelegate; 517 Delegate(Service delegate)518 public Delegate(Service delegate) { 519 mDelegate = delegate; 520 } 521 522 @Override finish()523 public void finish() { 524 mDelegate.finish(); 525 } 526 527 @Override setDozeScreenState(int state)528 public void setDozeScreenState(int state) { 529 mDelegate.setDozeScreenState(state); 530 } 531 532 @Override requestWakeUp(@ozeLog.Reason int reason)533 public void requestWakeUp(@DozeLog.Reason int reason) { 534 mDelegate.requestWakeUp(reason); 535 } 536 537 @Override setDozeScreenBrightness(int brightness)538 public void setDozeScreenBrightness(int brightness) { 539 mDelegate.setDozeScreenBrightness(brightness); 540 } 541 542 @Override setDozeScreenBrightnessFloat(float brightness)543 public void setDozeScreenBrightnessFloat(float brightness) { 544 mDelegate.setDozeScreenBrightnessFloat(brightness); 545 } 546 } 547 } 548 } 549