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