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