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