• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
StatusBarStateControllerImpl()105     public StatusBarStateControllerImpl() {
106         for (int i = 0; i < HISTORY_SIZE; i++) {
107             mHistoricalRecords[i] = new HistoricalState();
108         }
109     }
110 
111     @Override
getState()112     public int getState() {
113         return mState;
114     }
115 
116     @Override
setState(int state)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
isDozing()147     public boolean isDozing() {
148         return mIsDozing;
149     }
150 
151     @Override
getDozeAmount()152     public float getDozeAmount() {
153         return mDozeAmount;
154     }
155 
156     @Override
getInterpolatedDozeAmount()157     public float getInterpolatedDozeAmount() {
158         return mDozeInterpolator.getInterpolation(mDozeAmount);
159     }
160 
161     @Override
setIsDozing(boolean isDozing)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
setDozeAmount(float dozeAmount, boolean animated)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 
startDozeAnimation()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 
setDozeAmountInternal(float dozeAmount)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
goingToFullShade()219     public boolean goingToFullShade() {
220         return mState == StatusBarState.SHADE && mLeaveOpenOnKeyguardHide;
221     }
222 
223     @Override
setLeaveOpenOnKeyguardHide(boolean leaveOpen)224     public void setLeaveOpenOnKeyguardHide(boolean leaveOpen) {
225         mLeaveOpenOnKeyguardHide = leaveOpen;
226     }
227 
228     @Override
leaveOpenOnKeyguardHide()229     public boolean leaveOpenOnKeyguardHide() {
230         return mLeaveOpenOnKeyguardHide;
231     }
232 
233     @Override
fromShadeLocked()234     public boolean fromShadeLocked() {
235         return mLastState == StatusBarState.SHADE_LOCKED;
236     }
237 
238     @Override
addCallback(StateListener listener)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
addCallback(StateListener listener, @SbStateListenerRank int rank)257     public void addCallback(StateListener listener, @SbStateListenerRank int rank) {
258         synchronized (mListeners) {
259             addListenerInternalLocked(listener, rank);
260         }
261     }
262 
263     @GuardedBy("mListeners")
addListenerInternalLocked(StateListener listener, int rank)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
removeCallback(StateListener listener)279     public void removeCallback(StateListener listener) {
280         synchronized (mListeners) {
281             mListeners.removeIf((it) -> it.mListener.equals(listener));
282         }
283     }
284 
285     @Override
setKeyguardRequested(boolean keyguardRequested)286     public void setKeyguardRequested(boolean keyguardRequested) {
287         mKeyguardRequested = keyguardRequested;
288     }
289 
290     @Override
isKeyguardRequested()291     public boolean isKeyguardRequested() {
292         return mKeyguardRequested;
293     }
294 
295     /**
296      * Returns String readable state of status bar from {@link StatusBarState}
297      */
describe(int state)298     public static String describe(int state) {
299         return StatusBarState.toShortString(state);
300     }
301 
302     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)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 
recordHistoricalState(int currentState, int lastState)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
toString()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