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