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