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