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