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