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