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 requestState(State requestedState, int pulseReason)202 private void requestState(State requestedState, int pulseReason) { 203 Assert.isMainThread(); 204 if (DEBUG) { 205 Log.i(TAG, "request: current=" + mState + " req=" + requestedState, 206 new Throwable("here")); 207 } 208 209 boolean runNow = !isExecutingTransition(); 210 mQueuedRequests.add(requestedState); 211 if (runNow) { 212 mWakeLock.acquire(REASON_CHANGE_STATE); 213 for (int i = 0; i < mQueuedRequests.size(); i++) { 214 // Transitions in Parts can call back into requestState, which will 215 // cause mQueuedRequests to grow. 216 transitionTo(mQueuedRequests.get(i), pulseReason); 217 } 218 mQueuedRequests.clear(); 219 mWakeLock.release(REASON_CHANGE_STATE); 220 } 221 } 222 223 /** 224 * @return the current state. 225 * 226 * This must not be called during a transition. 227 */ 228 @MainThread getState()229 public State getState() { 230 Assert.isMainThread(); 231 if (isExecutingTransition()) { 232 throw new IllegalStateException("Cannot get state because there were pending " 233 + "transitions: " + mQueuedRequests.toString()); 234 } 235 return mState; 236 } 237 238 /** 239 * @return the current pulse reason. 240 * 241 * This is only valid if the machine is currently in one of the pulse states. 242 */ 243 @MainThread getPulseReason()244 public int getPulseReason() { 245 Assert.isMainThread(); 246 Preconditions.checkState(mState == State.DOZE_REQUEST_PULSE 247 || mState == State.DOZE_PULSING 248 || mState == State.DOZE_PULSING_BRIGHT 249 || mState == State.DOZE_PULSE_DONE, "must be in pulsing state, but is " + mState); 250 return mPulseReason; 251 } 252 253 /** Requests the PowerManager to wake up now. */ wakeUp()254 public void wakeUp() { 255 mDozeService.requestWakeUp(); 256 } 257 isExecutingTransition()258 public boolean isExecutingTransition() { 259 return !mQueuedRequests.isEmpty(); 260 } 261 transitionTo(State requestedState, int pulseReason)262 private void transitionTo(State requestedState, int pulseReason) { 263 State newState = transitionPolicy(requestedState); 264 265 if (DEBUG) { 266 Log.i(TAG, "transition: old=" + mState + " req=" + requestedState + " new=" + newState); 267 } 268 269 if (newState == mState) { 270 return; 271 } 272 273 validateTransition(newState); 274 275 State oldState = mState; 276 mState = newState; 277 278 mDozeLog.traceState(newState); 279 Trace.traceCounter(Trace.TRACE_TAG_APP, "doze_machine_state", newState.ordinal()); 280 281 updatePulseReason(newState, oldState, pulseReason); 282 performTransitionOnComponents(oldState, newState); 283 updateWakeLockState(newState); 284 285 resolveIntermediateState(newState); 286 } 287 updatePulseReason(State newState, State oldState, int pulseReason)288 private void updatePulseReason(State newState, State oldState, int pulseReason) { 289 if (newState == State.DOZE_REQUEST_PULSE) { 290 mPulseReason = pulseReason; 291 } else if (oldState == State.DOZE_PULSE_DONE) { 292 mPulseReason = DozeLog.PULSE_REASON_NONE; 293 } 294 } 295 performTransitionOnComponents(State oldState, State newState)296 private void performTransitionOnComponents(State oldState, State newState) { 297 for (Part p : mParts) { 298 p.transitionTo(oldState, newState); 299 } 300 301 switch (newState) { 302 case FINISH: 303 mDozeService.finish(); 304 break; 305 default: 306 } 307 } 308 validateTransition(State newState)309 private void validateTransition(State newState) { 310 try { 311 switch (mState) { 312 case FINISH: 313 Preconditions.checkState(newState == State.FINISH); 314 break; 315 case UNINITIALIZED: 316 Preconditions.checkState(newState == State.INITIALIZED); 317 break; 318 } 319 switch (newState) { 320 case UNINITIALIZED: 321 throw new IllegalArgumentException("can't transition to UNINITIALIZED"); 322 case INITIALIZED: 323 Preconditions.checkState(mState == State.UNINITIALIZED); 324 break; 325 case DOZE_PULSING: 326 Preconditions.checkState(mState == State.DOZE_REQUEST_PULSE); 327 break; 328 case DOZE_PULSE_DONE: 329 Preconditions.checkState( 330 mState == State.DOZE_REQUEST_PULSE || mState == State.DOZE_PULSING 331 || mState == State.DOZE_PULSING_BRIGHT); 332 break; 333 default: 334 break; 335 } 336 } catch (RuntimeException e) { 337 throw new IllegalStateException("Illegal Transition: " + mState + " -> " + newState, e); 338 } 339 } 340 transitionPolicy(State requestedState)341 private State transitionPolicy(State requestedState) { 342 if (mState == State.FINISH) { 343 return State.FINISH; 344 } 345 if (mDozeHost.isDozeSuppressed() && requestedState.isAlwaysOn()) { 346 Log.i(TAG, "Doze is suppressed. Suppressing state: " + requestedState); 347 mDozeLog.traceDozeSuppressed(requestedState); 348 return State.DOZE; 349 } 350 if ((mState == State.DOZE_AOD_PAUSED || mState == State.DOZE_AOD_PAUSING 351 || mState == State.DOZE_AOD || mState == State.DOZE 352 || mState == State.DOZE_AOD_DOCKED) && requestedState == State.DOZE_PULSE_DONE) { 353 Log.i(TAG, "Dropping pulse done because current state is already done: " + mState); 354 return mState; 355 } 356 if (requestedState == State.DOZE_AOD && mBatteryController.isAodPowerSave()) { 357 return State.DOZE; 358 } 359 if (requestedState == State.DOZE_REQUEST_PULSE && !mState.canPulse()) { 360 Log.i(TAG, "Dropping pulse request because current state can't pulse: " + mState); 361 return mState; 362 } 363 return requestedState; 364 } 365 updateWakeLockState(State newState)366 private void updateWakeLockState(State newState) { 367 boolean staysAwake = newState.staysAwake(); 368 if (mWakeLockHeldForCurrentState && !staysAwake) { 369 mWakeLock.release(REASON_HELD_FOR_STATE); 370 mWakeLockHeldForCurrentState = false; 371 } else if (!mWakeLockHeldForCurrentState && staysAwake) { 372 mWakeLock.acquire(REASON_HELD_FOR_STATE); 373 mWakeLockHeldForCurrentState = true; 374 } 375 } 376 resolveIntermediateState(State state)377 private void resolveIntermediateState(State state) { 378 switch (state) { 379 case INITIALIZED: 380 case DOZE_PULSE_DONE: 381 final State nextState; 382 @Wakefulness int wakefulness = mWakefulnessLifecycle.getWakefulness(); 383 if (state != State.INITIALIZED && (wakefulness == WAKEFULNESS_AWAKE 384 || wakefulness == WAKEFULNESS_WAKING)) { 385 nextState = State.FINISH; 386 } else if (mDockManager.isDocked()) { 387 nextState = mDockManager.isHidden() ? State.DOZE : State.DOZE_AOD_DOCKED; 388 } else if (mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT)) { 389 nextState = State.DOZE_AOD; 390 } else { 391 nextState = State.DOZE; 392 } 393 394 transitionTo(nextState, DozeLog.PULSE_REASON_NONE); 395 break; 396 default: 397 break; 398 } 399 } 400 401 /** Dumps the current state */ dump(PrintWriter pw)402 public void dump(PrintWriter pw) { 403 pw.print(" state="); pw.println(mState); 404 pw.print(" wakeLockHeldForCurrentState="); pw.println(mWakeLockHeldForCurrentState); 405 pw.print(" wakeLock="); pw.println(mWakeLock); 406 pw.println("Parts:"); 407 for (Part p : mParts) { 408 p.dump(pw); 409 } 410 } 411 412 /** A part of the DozeMachine that needs to be notified about state changes. */ 413 public interface Part { 414 /** 415 * Transition from {@code oldState} to {@code newState}. 416 * 417 * This method is guaranteed to only be called while a wake lock is held. 418 */ transitionTo(State oldState, State newState)419 void transitionTo(State oldState, State newState); 420 421 /** Dump current state. For debugging only. */ dump(PrintWriter pw)422 default void dump(PrintWriter pw) {} 423 424 /** Give the Part a chance to clean itself up. */ destroy()425 default void destroy() {} 426 } 427 428 /** A wrapper interface for {@link android.service.dreams.DreamService} */ 429 public interface Service { 430 /** Finish dreaming. */ finish()431 void finish(); 432 433 /** Request a display state. See {@link android.view.Display#STATE_DOZE}. */ setDozeScreenState(int state)434 void setDozeScreenState(int state); 435 436 /** Request waking up. */ requestWakeUp()437 void requestWakeUp(); 438 439 /** Set screen brightness */ setDozeScreenBrightness(int brightness)440 void setDozeScreenBrightness(int brightness); 441 442 class Delegate implements Service { 443 private final Service mDelegate; 444 Delegate(Service delegate)445 public Delegate(Service delegate) { 446 mDelegate = delegate; 447 } 448 449 @Override finish()450 public void finish() { 451 mDelegate.finish(); 452 } 453 454 @Override setDozeScreenState(int state)455 public void setDozeScreenState(int state) { 456 mDelegate.setDozeScreenState(state); 457 } 458 459 @Override requestWakeUp()460 public void requestWakeUp() { 461 mDelegate.requestWakeUp(); 462 } 463 464 @Override setDozeScreenBrightness(int brightness)465 public void setDozeScreenBrightness(int brightness) { 466 mDelegate.setDozeScreenBrightness(brightness); 467 } 468 } 469 } 470 } 471