Home | History | Annotate | Download | only in statusbar
      1 /*
      2  * Copyright (C) 2019 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.statusbar;
     18 
     19 import android.animation.ObjectAnimator;
     20 import android.animation.ValueAnimator;
     21 import android.text.format.DateFormat;
     22 import android.util.FloatProperty;
     23 import android.view.animation.Interpolator;
     24 
     25 import com.android.internal.annotations.GuardedBy;
     26 import com.android.systemui.Dumpable;
     27 import com.android.systemui.Interpolators;
     28 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
     29 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
     30 import com.android.systemui.statusbar.policy.CallbackController;
     31 
     32 import java.io.FileDescriptor;
     33 import java.io.PrintWriter;
     34 import java.util.ArrayList;
     35 import java.util.Comparator;
     36 
     37 import javax.inject.Inject;
     38 import javax.inject.Singleton;
     39 
     40 /**
     41  * Tracks and reports on {@link StatusBarState}.
     42  */
     43 @Singleton
     44 public class StatusBarStateControllerImpl implements SysuiStatusBarStateController,
     45         CallbackController<StateListener>, Dumpable {
     46     private static final String TAG = "SbStateController";
     47     // Must be a power of 2
     48     private static final int HISTORY_SIZE = 32;
     49 
     50     private static final int MAX_STATE = StatusBarState.FULLSCREEN_USER_SWITCHER;
     51     private static final int MIN_STATE = StatusBarState.SHADE;
     52 
     53     private static final Comparator<RankedListener> sComparator =
     54             Comparator.comparingInt(o -> o.mRank);
     55     private static final FloatProperty<StatusBarStateControllerImpl> SET_DARK_AMOUNT_PROPERTY =
     56             new FloatProperty<StatusBarStateControllerImpl>("mDozeAmount") {
     57 
     58                 @Override
     59                 public void setValue(StatusBarStateControllerImpl object, float value) {
     60                     object.setDozeAmountInternal(value);
     61                 }
     62 
     63                 @Override
     64                 public Float get(StatusBarStateControllerImpl object) {
     65                     return object.mDozeAmount;
     66                 }
     67             };
     68 
     69     private final ArrayList<RankedListener> mListeners = new ArrayList<>();
     70     private int mState;
     71     private int mLastState;
     72     private boolean mLeaveOpenOnKeyguardHide;
     73     private boolean mKeyguardRequested;
     74 
     75     // Record the HISTORY_SIZE most recent states
     76     private int mHistoryIndex = 0;
     77     private HistoricalState[] mHistoricalRecords = new HistoricalState[HISTORY_SIZE];
     78 
     79     /**
     80      * If the device is currently dozing or not.
     81      */
     82     private boolean mIsDozing;
     83 
     84     /**
     85      * Current {@link #mDozeAmount} animator.
     86      */
     87     private ValueAnimator mDarkAnimator;
     88 
     89     /**
     90      * Current doze amount in this frame.
     91      */
     92     private float mDozeAmount;
     93 
     94     /**
     95      * Where the animator will stop.
     96      */
     97     private float mDozeAmountTarget;
     98 
     99     /**
    100      * The type of interpolator that should be used to the doze animation.
    101      */
    102     private Interpolator mDozeInterpolator = Interpolators.FAST_OUT_SLOW_IN;
    103 
    104     @Inject
    105     public StatusBarStateControllerImpl() {
    106         for (int i = 0; i < HISTORY_SIZE; i++) {
    107             mHistoricalRecords[i] = new HistoricalState();
    108         }
    109     }
    110 
    111     @Override
    112     public int getState() {
    113         return mState;
    114     }
    115 
    116     @Override
    117     public boolean setState(int state) {
    118         if (state > MAX_STATE || state < MIN_STATE) {
    119             throw new IllegalArgumentException("Invalid state " + state);
    120         }
    121         if (state == mState) {
    122             return false;
    123         }
    124 
    125         // Record the to-be mState and mLastState
    126         recordHistoricalState(state, mState);
    127 
    128         synchronized (mListeners) {
    129             for (RankedListener rl : new ArrayList<>(mListeners)) {
    130                 rl.mListener.onStatePreChange(mState, state);
    131             }
    132             mLastState = mState;
    133             mState = state;
    134             for (RankedListener rl : new ArrayList<>(mListeners)) {
    135                 rl.mListener.onStateChanged(mState);
    136             }
    137 
    138             for (RankedListener rl : new ArrayList<>(mListeners)) {
    139                 rl.mListener.onStatePostChange();
    140             }
    141         }
    142 
    143         return true;
    144     }
    145 
    146     @Override
    147     public boolean isDozing() {
    148         return mIsDozing;
    149     }
    150 
    151     @Override
    152     public float getDozeAmount() {
    153         return mDozeAmount;
    154     }
    155 
    156     @Override
    157     public float getInterpolatedDozeAmount() {
    158         return mDozeInterpolator.getInterpolation(mDozeAmount);
    159     }
    160 
    161     @Override
    162     public boolean setIsDozing(boolean isDozing) {
    163         if (mIsDozing == isDozing) {
    164             return false;
    165         }
    166 
    167         mIsDozing = isDozing;
    168 
    169         synchronized (mListeners) {
    170             for (RankedListener rl : new ArrayList<>(mListeners)) {
    171                 rl.mListener.onDozingChanged(isDozing);
    172             }
    173         }
    174 
    175         return true;
    176     }
    177 
    178     @Override
    179     public void setDozeAmount(float dozeAmount, boolean animated) {
    180         if (mDarkAnimator != null && mDarkAnimator.isRunning()) {
    181             if (animated && mDozeAmountTarget == dozeAmount) {
    182                 return;
    183             } else {
    184                 mDarkAnimator.cancel();
    185             }
    186         }
    187 
    188         mDozeAmountTarget = dozeAmount;
    189         if (animated) {
    190             startDozeAnimation();
    191         } else {
    192             setDozeAmountInternal(dozeAmount);
    193         }
    194     }
    195 
    196     private void startDozeAnimation() {
    197         if (mDozeAmount == 0f || mDozeAmount == 1f) {
    198             mDozeInterpolator = mIsDozing
    199                     ? Interpolators.FAST_OUT_SLOW_IN
    200                     : Interpolators.TOUCH_RESPONSE_REVERSE;
    201         }
    202         mDarkAnimator = ObjectAnimator.ofFloat(this, SET_DARK_AMOUNT_PROPERTY, mDozeAmountTarget);
    203         mDarkAnimator.setInterpolator(Interpolators.LINEAR);
    204         mDarkAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_WAKEUP);
    205         mDarkAnimator.start();
    206     }
    207 
    208     private void setDozeAmountInternal(float dozeAmount) {
    209         mDozeAmount = dozeAmount;
    210         float interpolatedAmount = mDozeInterpolator.getInterpolation(dozeAmount);
    211         synchronized (mListeners) {
    212             for (RankedListener rl : new ArrayList<>(mListeners)) {
    213                 rl.mListener.onDozeAmountChanged(mDozeAmount, interpolatedAmount);
    214             }
    215         }
    216     }
    217 
    218     @Override
    219     public boolean goingToFullShade() {
    220         return mState == StatusBarState.SHADE && mLeaveOpenOnKeyguardHide;
    221     }
    222 
    223     @Override
    224     public void setLeaveOpenOnKeyguardHide(boolean leaveOpen) {
    225         mLeaveOpenOnKeyguardHide = leaveOpen;
    226     }
    227 
    228     @Override
    229     public boolean leaveOpenOnKeyguardHide() {
    230         return mLeaveOpenOnKeyguardHide;
    231     }
    232 
    233     @Override
    234     public boolean fromShadeLocked() {
    235         return mLastState == StatusBarState.SHADE_LOCKED;
    236     }
    237 
    238     @Override
    239     public void addCallback(StateListener listener) {
    240         synchronized (mListeners) {
    241             addListenerInternalLocked(listener, Integer.MAX_VALUE);
    242         }
    243     }
    244 
    245     /**
    246      * Add a listener and a rank based on the priority of this message
    247      * @param listener the listener
    248      * @param rank the order in which you'd like to be called. Ranked listeners will be
    249      * notified before unranked, and we will sort ranked listeners from low to high
    250      *
    251      * @deprecated This method exists only to solve latent inter-dependencies from refactoring
    252      * StatusBarState out of StatusBar.java. Any new listeners should be built not to need ranking
    253      * (i.e., they are non-dependent on the order of operations of StatusBarState listeners).
    254      */
    255     @Deprecated
    256     @Override
    257     public void addCallback(StateListener listener, @SbStateListenerRank int rank) {
    258         synchronized (mListeners) {
    259             addListenerInternalLocked(listener, rank);
    260         }
    261     }
    262 
    263     @GuardedBy("mListeners")
    264     private void addListenerInternalLocked(StateListener listener, int rank) {
    265         // Protect against double-subscribe
    266         for (RankedListener rl : mListeners) {
    267             if (rl.mListener.equals(listener)) {
    268                 return;
    269             }
    270         }
    271 
    272         RankedListener rl = new SysuiStatusBarStateController.RankedListener(listener, rank);
    273         mListeners.add(rl);
    274         mListeners.sort(sComparator);
    275     }
    276 
    277 
    278     @Override
    279     public void removeCallback(StateListener listener) {
    280         synchronized (mListeners) {
    281             mListeners.removeIf((it) -> it.mListener.equals(listener));
    282         }
    283     }
    284 
    285     @Override
    286     public void setKeyguardRequested(boolean keyguardRequested) {
    287         mKeyguardRequested = keyguardRequested;
    288     }
    289 
    290     @Override
    291     public boolean isKeyguardRequested() {
    292         return mKeyguardRequested;
    293     }
    294 
    295     /**
    296      * Returns String readable state of status bar from {@link StatusBarState}
    297      */
    298     public static String describe(int state) {
    299         return StatusBarState.toShortString(state);
    300     }
    301 
    302     @Override
    303     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    304         pw.println("StatusBarStateController: ");
    305         pw.println(" mState=" + mState + " (" + describe(mState) + ")");
    306         pw.println(" mLastState=" + mLastState + " (" + describe(mLastState) + ")");
    307         pw.println(" mLeaveOpenOnKeyguardHide=" + mLeaveOpenOnKeyguardHide);
    308         pw.println(" mKeyguardRequested=" + mKeyguardRequested);
    309         pw.println(" mIsDozing=" + mIsDozing);
    310         pw.println(" Historical states:");
    311         // Ignore records without a timestamp
    312         int size = 0;
    313         for (int i = 0; i < HISTORY_SIZE; i++) {
    314             if (mHistoricalRecords[i].mTimestamp != 0) size++;
    315         }
    316         for (int i = mHistoryIndex + HISTORY_SIZE;
    317                 i >= mHistoryIndex + HISTORY_SIZE - size + 1; i--) {
    318             pw.println("  (" + (mHistoryIndex + HISTORY_SIZE - i + 1) + ")"
    319                     + mHistoricalRecords[i & (HISTORY_SIZE - 1)]);
    320         }
    321     }
    322 
    323     private void recordHistoricalState(int currentState, int lastState) {
    324         mHistoryIndex = (mHistoryIndex + 1) % HISTORY_SIZE;
    325         HistoricalState state = mHistoricalRecords[mHistoryIndex];
    326         state.mState = currentState;
    327         state.mLastState = lastState;
    328         state.mTimestamp = System.currentTimeMillis();
    329     }
    330 
    331     /**
    332      * For keeping track of our previous state to help with debugging
    333      */
    334     private static class HistoricalState {
    335         int mState;
    336         int mLastState;
    337         long mTimestamp;
    338 
    339         @Override
    340         public String toString() {
    341             if (mTimestamp != 0) {
    342                 StringBuilder sb = new StringBuilder();
    343                 sb.append("state=").append(mState)
    344                         .append(" (").append(describe(mState)).append(")");
    345                 sb.append("lastState=").append(mLastState).append(" (").append(describe(mLastState))
    346                         .append(")");
    347                 sb.append("timestamp=")
    348                         .append(DateFormat.format("MM-dd HH:mm:ss", mTimestamp));
    349 
    350                 return sb.toString();
    351             }
    352             return "Empty " + getClass().getSimpleName();
    353         }
    354     }
    355 }
    356