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