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