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