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