Home | History | Annotate | Download | only in doze
      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 
     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 
     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 
     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 
    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. */
    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
    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
    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 
    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
    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
    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. */
    205     public void wakeUp() {
    206         mDozeService.requestWakeUp();
    207     }
    208 
    209     private boolean isExecutingTransition() {
    210         return !mQueuedRequests.isEmpty();
    211     }
    212 
    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 
    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 
    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 
    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 
    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 
    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 
    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 */
    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          */
    349         void transitionTo(State oldState, State newState);
    350 
    351         /** Dump current state. For debugging only. */
    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. */
    358         void finish();
    359 
    360         /** Request a display state. See {@link android.view.Display#STATE_DOZE}. */
    361         void setDozeScreenState(int state);
    362 
    363         /** Request waking up. */
    364         void requestWakeUp();
    365 
    366         /** Set screen brightness */
    367         void setDozeScreenBrightness(int brightness);
    368 
    369         class Delegate implements Service {
    370             private final Service mDelegate;
    371 
    372             public Delegate(Service delegate) {
    373                 mDelegate = delegate;
    374             }
    375 
    376             @Override
    377             public void finish() {
    378                 mDelegate.finish();
    379             }
    380 
    381             @Override
    382             public void setDozeScreenState(int state) {
    383                 mDelegate.setDozeScreenState(state);
    384             }
    385 
    386             @Override
    387             public void requestWakeUp() {
    388                 mDelegate.requestWakeUp();
    389             }
    390 
    391             @Override
    392             public void setDozeScreenBrightness(int brightness) {
    393                 mDelegate.setDozeScreenBrightness(brightness);
    394             }
    395         }
    396     }
    397 }
    398