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