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