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