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 
requestState(State requestedState, int pulseReason)202     private void requestState(State requestedState, int pulseReason) {
203         Assert.isMainThread();
204         if (DEBUG) {
205             Log.i(TAG, "request: current=" + mState + " req=" + requestedState,
206                     new Throwable("here"));
207         }
208 
209         boolean runNow = !isExecutingTransition();
210         mQueuedRequests.add(requestedState);
211         if (runNow) {
212             mWakeLock.acquire(REASON_CHANGE_STATE);
213             for (int i = 0; i < mQueuedRequests.size(); i++) {
214                 // Transitions in Parts can call back into requestState, which will
215                 // cause mQueuedRequests to grow.
216                 transitionTo(mQueuedRequests.get(i), pulseReason);
217             }
218             mQueuedRequests.clear();
219             mWakeLock.release(REASON_CHANGE_STATE);
220         }
221     }
222 
223     /**
224      * @return the current state.
225      *
226      * This must not be called during a transition.
227      */
228     @MainThread
getState()229     public State getState() {
230         Assert.isMainThread();
231         if (isExecutingTransition()) {
232             throw new IllegalStateException("Cannot get state because there were pending "
233                     + "transitions: " + mQueuedRequests.toString());
234         }
235         return mState;
236     }
237 
238     /**
239      * @return the current pulse reason.
240      *
241      * This is only valid if the machine is currently in one of the pulse states.
242      */
243     @MainThread
getPulseReason()244     public int getPulseReason() {
245         Assert.isMainThread();
246         Preconditions.checkState(mState == State.DOZE_REQUEST_PULSE
247                 || mState == State.DOZE_PULSING
248                 || mState == State.DOZE_PULSING_BRIGHT
249                 || mState == State.DOZE_PULSE_DONE, "must be in pulsing state, but is " + mState);
250         return mPulseReason;
251     }
252 
253     /** Requests the PowerManager to wake up now. */
wakeUp()254     public void wakeUp() {
255         mDozeService.requestWakeUp();
256     }
257 
isExecutingTransition()258     public boolean isExecutingTransition() {
259         return !mQueuedRequests.isEmpty();
260     }
261 
transitionTo(State requestedState, int pulseReason)262     private void transitionTo(State requestedState, int pulseReason) {
263         State newState = transitionPolicy(requestedState);
264 
265         if (DEBUG) {
266             Log.i(TAG, "transition: old=" + mState + " req=" + requestedState + " new=" + newState);
267         }
268 
269         if (newState == mState) {
270             return;
271         }
272 
273         validateTransition(newState);
274 
275         State oldState = mState;
276         mState = newState;
277 
278         mDozeLog.traceState(newState);
279         Trace.traceCounter(Trace.TRACE_TAG_APP, "doze_machine_state", newState.ordinal());
280 
281         updatePulseReason(newState, oldState, pulseReason);
282         performTransitionOnComponents(oldState, newState);
283         updateWakeLockState(newState);
284 
285         resolveIntermediateState(newState);
286     }
287 
updatePulseReason(State newState, State oldState, int pulseReason)288     private void updatePulseReason(State newState, State oldState, int pulseReason) {
289         if (newState == State.DOZE_REQUEST_PULSE) {
290             mPulseReason = pulseReason;
291         } else if (oldState == State.DOZE_PULSE_DONE) {
292             mPulseReason = DozeLog.PULSE_REASON_NONE;
293         }
294     }
295 
performTransitionOnComponents(State oldState, State newState)296     private void performTransitionOnComponents(State oldState, State newState) {
297         for (Part p : mParts) {
298             p.transitionTo(oldState, newState);
299         }
300 
301         switch (newState) {
302             case FINISH:
303                 mDozeService.finish();
304                 break;
305             default:
306         }
307     }
308 
validateTransition(State newState)309     private void validateTransition(State newState) {
310         try {
311             switch (mState) {
312                 case FINISH:
313                     Preconditions.checkState(newState == State.FINISH);
314                     break;
315                 case UNINITIALIZED:
316                     Preconditions.checkState(newState == State.INITIALIZED);
317                     break;
318             }
319             switch (newState) {
320                 case UNINITIALIZED:
321                     throw new IllegalArgumentException("can't transition to UNINITIALIZED");
322                 case INITIALIZED:
323                     Preconditions.checkState(mState == State.UNINITIALIZED);
324                     break;
325                 case DOZE_PULSING:
326                     Preconditions.checkState(mState == State.DOZE_REQUEST_PULSE);
327                     break;
328                 case DOZE_PULSE_DONE:
329                     Preconditions.checkState(
330                             mState == State.DOZE_REQUEST_PULSE || mState == State.DOZE_PULSING
331                                     || mState == State.DOZE_PULSING_BRIGHT);
332                     break;
333                 default:
334                     break;
335             }
336         } catch (RuntimeException e) {
337             throw new IllegalStateException("Illegal Transition: " + mState + " -> " + newState, e);
338         }
339     }
340 
transitionPolicy(State requestedState)341     private State transitionPolicy(State requestedState) {
342         if (mState == State.FINISH) {
343             return State.FINISH;
344         }
345         if (mDozeHost.isDozeSuppressed() && requestedState.isAlwaysOn()) {
346             Log.i(TAG, "Doze is suppressed. Suppressing state: " + requestedState);
347             mDozeLog.traceDozeSuppressed(requestedState);
348             return State.DOZE;
349         }
350         if ((mState == State.DOZE_AOD_PAUSED || mState == State.DOZE_AOD_PAUSING
351                 || mState == State.DOZE_AOD || mState == State.DOZE
352                 || mState == State.DOZE_AOD_DOCKED) && requestedState == State.DOZE_PULSE_DONE) {
353             Log.i(TAG, "Dropping pulse done because current state is already done: " + mState);
354             return mState;
355         }
356         if (requestedState == State.DOZE_AOD && mBatteryController.isAodPowerSave()) {
357             return State.DOZE;
358         }
359         if (requestedState == State.DOZE_REQUEST_PULSE && !mState.canPulse()) {
360             Log.i(TAG, "Dropping pulse request because current state can't pulse: " + mState);
361             return mState;
362         }
363         return requestedState;
364     }
365 
updateWakeLockState(State newState)366     private void updateWakeLockState(State newState) {
367         boolean staysAwake = newState.staysAwake();
368         if (mWakeLockHeldForCurrentState && !staysAwake) {
369             mWakeLock.release(REASON_HELD_FOR_STATE);
370             mWakeLockHeldForCurrentState = false;
371         } else if (!mWakeLockHeldForCurrentState && staysAwake) {
372             mWakeLock.acquire(REASON_HELD_FOR_STATE);
373             mWakeLockHeldForCurrentState = true;
374         }
375     }
376 
resolveIntermediateState(State state)377     private void resolveIntermediateState(State state) {
378         switch (state) {
379             case INITIALIZED:
380             case DOZE_PULSE_DONE:
381                 final State nextState;
382                 @Wakefulness int wakefulness = mWakefulnessLifecycle.getWakefulness();
383                 if (state != State.INITIALIZED && (wakefulness == WAKEFULNESS_AWAKE
384                         || wakefulness == WAKEFULNESS_WAKING)) {
385                     nextState = State.FINISH;
386                 } else if (mDockManager.isDocked()) {
387                     nextState = mDockManager.isHidden() ? State.DOZE : State.DOZE_AOD_DOCKED;
388                 } else if (mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT)) {
389                     nextState = State.DOZE_AOD;
390                 } else {
391                     nextState = State.DOZE;
392                 }
393 
394                 transitionTo(nextState, DozeLog.PULSE_REASON_NONE);
395                 break;
396             default:
397                 break;
398         }
399     }
400 
401     /** Dumps the current state */
dump(PrintWriter pw)402     public void dump(PrintWriter pw) {
403         pw.print(" state="); pw.println(mState);
404         pw.print(" wakeLockHeldForCurrentState="); pw.println(mWakeLockHeldForCurrentState);
405         pw.print(" wakeLock="); pw.println(mWakeLock);
406         pw.println("Parts:");
407         for (Part p : mParts) {
408             p.dump(pw);
409         }
410     }
411 
412     /** A part of the DozeMachine that needs to be notified about state changes. */
413     public interface Part {
414         /**
415          * Transition from {@code oldState} to {@code newState}.
416          *
417          * This method is guaranteed to only be called while a wake lock is held.
418          */
transitionTo(State oldState, State newState)419         void transitionTo(State oldState, State newState);
420 
421         /** Dump current state. For debugging only. */
dump(PrintWriter pw)422         default void dump(PrintWriter pw) {}
423 
424         /** Give the Part a chance to clean itself up. */
destroy()425         default void destroy() {}
426     }
427 
428     /** A wrapper interface for {@link android.service.dreams.DreamService} */
429     public interface Service {
430         /** Finish dreaming. */
finish()431         void finish();
432 
433         /** Request a display state. See {@link android.view.Display#STATE_DOZE}. */
setDozeScreenState(int state)434         void setDozeScreenState(int state);
435 
436         /** Request waking up. */
requestWakeUp()437         void requestWakeUp();
438 
439         /** Set screen brightness */
setDozeScreenBrightness(int brightness)440         void setDozeScreenBrightness(int brightness);
441 
442         class Delegate implements Service {
443             private final Service mDelegate;
444 
Delegate(Service delegate)445             public Delegate(Service delegate) {
446                 mDelegate = delegate;
447             }
448 
449             @Override
finish()450             public void finish() {
451                 mDelegate.finish();
452             }
453 
454             @Override
setDozeScreenState(int state)455             public void setDozeScreenState(int state) {
456                 mDelegate.setDozeScreenState(state);
457             }
458 
459             @Override
requestWakeUp()460             public void requestWakeUp() {
461                 mDelegate.requestWakeUp();
462             }
463 
464             @Override
setDozeScreenBrightness(int brightness)465             public void setDozeScreenBrightness(int brightness) {
466                 mDelegate.setDozeScreenBrightness(brightness);
467             }
468         }
469     }
470 }
471