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