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.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 
     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 
     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 
     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 
    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. */
    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
    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
    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 
    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
    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
    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. */
    218     public void wakeUp() {
    219         mDozeService.requestWakeUp();
    220     }
    221 
    222     public boolean isExecutingTransition() {
    223         return !mQueuedRequests.isEmpty();
    224     }
    225 
    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 
    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 
    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 
    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 
    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 
    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 
    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 */
    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          */
    364         void transitionTo(State oldState, State newState);
    365 
    366         /** Dump current state. For debugging only. */
    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. */
    373         void finish();
    374 
    375         /** Request a display state. See {@link android.view.Display#STATE_DOZE}. */
    376         void setDozeScreenState(int state);
    377 
    378         /** Request waking up. */
    379         void requestWakeUp();
    380 
    381         /** Set screen brightness */
    382         void setDozeScreenBrightness(int brightness);
    383 
    384         class Delegate implements Service {
    385             private final Service mDelegate;
    386 
    387             public Delegate(Service delegate) {
    388                 mDelegate = delegate;
    389             }
    390 
    391             @Override
    392             public void finish() {
    393                 mDelegate.finish();
    394             }
    395 
    396             @Override
    397             public void setDozeScreenState(int state) {
    398                 mDelegate.setDozeScreenState(state);
    399             }
    400 
    401             @Override
    402             public void requestWakeUp() {
    403                 mDelegate.requestWakeUp();
    404             }
    405 
    406             @Override
    407             public void setDozeScreenBrightness(int brightness) {
    408                 mDelegate.setDozeScreenBrightness(brightness);
    409             }
    410         }
    411     }
    412 }
    413