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