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 static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_FROM_AOD;
20 import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_TO_AOD;
21 import static com.android.systemui.keyguard.shared.model.KeyguardState.GONE;
22 import static com.android.systemui.util.kotlin.JavaAdapterKt.combineFlows;
23 
24 import android.animation.Animator;
25 import android.animation.AnimatorListenerAdapter;
26 import android.animation.ObjectAnimator;
27 import android.animation.ValueAnimator;
28 import android.os.SystemProperties;
29 import android.os.Trace;
30 import android.text.format.DateFormat;
31 import android.util.FloatProperty;
32 import android.util.Log;
33 import android.view.Choreographer;
34 import android.view.View;
35 import android.view.animation.Interpolator;
36 
37 import androidx.annotation.NonNull;
38 
39 import com.android.app.animation.Interpolators;
40 import com.android.compose.animation.scene.SceneKey;
41 import com.android.internal.annotations.GuardedBy;
42 import com.android.internal.annotations.VisibleForTesting;
43 import com.android.internal.jank.InteractionJankMonitor;
44 import com.android.internal.jank.InteractionJankMonitor.Configuration;
45 import com.android.internal.logging.UiEventLogger;
46 import com.android.keyguard.KeyguardClockSwitch;
47 import com.android.systemui.DejankUtils;
48 import com.android.systemui.dagger.SysUISingleton;
49 import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor;
50 import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus;
51 import com.android.systemui.keyguard.MigrateClocksToBlueprint;
52 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor;
53 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
54 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
55 import com.android.systemui.res.R;
56 import com.android.systemui.scene.domain.interactor.SceneInteractor;
57 import com.android.systemui.scene.shared.flag.SceneContainerFlag;
58 import com.android.systemui.scene.shared.model.Scenes;
59 import com.android.systemui.shade.domain.interactor.ShadeInteractor;
60 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
61 import com.android.systemui.statusbar.policy.CallbackController;
62 import com.android.systemui.util.Compile;
63 import com.android.systemui.util.kotlin.JavaAdapter;
64 
65 import com.google.common.base.Preconditions;
66 
67 import dagger.Lazy;
68 
69 import java.io.PrintWriter;
70 import java.util.ArrayList;
71 import java.util.Comparator;
72 import java.util.Map;
73 
74 import javax.inject.Inject;
75 
76 /**
77  * Tracks and reports on {@link StatusBarState}.
78  */
79 @SysUISingleton
80 public class StatusBarStateControllerImpl implements
81         SysuiStatusBarStateController,
82         CallbackController<StateListener> {
83     private static final String TAG = "SbStateController";
84     private static final boolean DEBUG_IMMERSIVE_APPS =
85             SystemProperties.getBoolean("persist.debug.immersive_apps", false);
86 
87     // Must be a power of 2
88     private static final int HISTORY_SIZE = 32;
89 
90     private static final int MAX_STATE = StatusBarState.SHADE_LOCKED;
91     private static final int MIN_STATE = StatusBarState.SHADE;
92 
93     private static final Comparator<RankedListener> sComparator =
94             Comparator.comparingInt(o -> o.mRank);
95     private static final FloatProperty<StatusBarStateControllerImpl> SET_DARK_AMOUNT_PROPERTY =
96             new FloatProperty<StatusBarStateControllerImpl>("mDozeAmount") {
97 
98                 @Override
99                 public void setValue(StatusBarStateControllerImpl object, float value) {
100                     object.setDozeAmountInternal(value);
101                 }
102 
103                 @Override
104                 public Float get(StatusBarStateControllerImpl object) {
105                     return object.mDozeAmount;
106                 }
107             };
108 
109     private final ArrayList<RankedListener> mListeners = new ArrayList<>();
110     private final UiEventLogger mUiEventLogger;
111     private final Lazy<InteractionJankMonitor> mInteractionJankMonitorLazy;
112     private final JavaAdapter mJavaAdapter;
113     private final Lazy<KeyguardTransitionInteractor> mKeyguardTransitionInteractorLazy;
114     private final Lazy<ShadeInteractor> mShadeInteractorLazy;
115     private final Lazy<DeviceUnlockedInteractor> mDeviceUnlockedInteractorLazy;
116     private final Lazy<SceneInteractor> mSceneInteractorLazy;
117     private final Lazy<KeyguardClockInteractor> mKeyguardClockInteractorLazy;
118     private int mState;
119     private int mLastState;
120     private int mUpcomingState;
121     private boolean mLeaveOpenOnKeyguardHide;
122     private boolean mKeyguardRequested;
123 
124     // Record the HISTORY_SIZE most recent states
125     private int mHistoryIndex = 0;
126     private HistoricalState[] mHistoricalRecords = new HistoricalState[HISTORY_SIZE];
127     // These views are used by InteractionJankMonitor to get callback from HWUI.
128     private View mView;
129     private KeyguardClockSwitch mClockSwitchView;
130 
131     /**
132      * If any of the system bars is hidden.
133      */
134     private boolean mIsFullscreen = false;
135 
136     /**
137      * If the device is currently pulsing (AOD2).
138      */
139     private boolean mPulsing;
140 
141     /**
142      * If the device is currently dozing or not.
143      */
144     private boolean mIsDozing;
145 
146     /**
147      * If the device is currently dreaming or not.
148      */
149     private boolean mIsDreaming;
150 
151     /**
152      * If the status bar is currently expanded or not.
153      */
154     private boolean mIsExpanded;
155 
156     /**
157      * Current {@link #mDozeAmount} animator.
158      */
159     private ValueAnimator mDarkAnimator;
160 
161     /**
162      * Current doze amount in this frame.
163      */
164     private float mDozeAmount;
165 
166     /**
167      * Where the animator will stop.
168      */
169     private float mDozeAmountTarget;
170 
171     /**
172      * The type of interpolator that should be used to the doze animation.
173      */
174     private Interpolator mDozeInterpolator = Interpolators.FAST_OUT_SLOW_IN;
175 
176     @Inject
StatusBarStateControllerImpl( UiEventLogger uiEventLogger, Lazy<InteractionJankMonitor> interactionJankMonitorLazy, JavaAdapter javaAdapter, Lazy<KeyguardTransitionInteractor> keyguardTransitionInteractor, Lazy<ShadeInteractor> shadeInteractorLazy, Lazy<DeviceUnlockedInteractor> deviceUnlockedInteractorLazy, Lazy<SceneInteractor> sceneInteractorLazy, Lazy<KeyguardClockInteractor> keyguardClockInteractorLazy)177     public StatusBarStateControllerImpl(
178             UiEventLogger uiEventLogger,
179             Lazy<InteractionJankMonitor> interactionJankMonitorLazy,
180             JavaAdapter javaAdapter,
181             Lazy<KeyguardTransitionInteractor> keyguardTransitionInteractor,
182             Lazy<ShadeInteractor> shadeInteractorLazy,
183             Lazy<DeviceUnlockedInteractor> deviceUnlockedInteractorLazy,
184             Lazy<SceneInteractor> sceneInteractorLazy,
185             Lazy<KeyguardClockInteractor> keyguardClockInteractorLazy) {
186         mUiEventLogger = uiEventLogger;
187         mInteractionJankMonitorLazy = interactionJankMonitorLazy;
188         mJavaAdapter = javaAdapter;
189         mKeyguardTransitionInteractorLazy = keyguardTransitionInteractor;
190         mShadeInteractorLazy = shadeInteractorLazy;
191         mDeviceUnlockedInteractorLazy = deviceUnlockedInteractorLazy;
192         mSceneInteractorLazy = sceneInteractorLazy;
193         mKeyguardClockInteractorLazy = keyguardClockInteractorLazy;
194         for (int i = 0; i < HISTORY_SIZE; i++) {
195             mHistoricalRecords[i] = new HistoricalState();
196         }
197     }
198 
199     @Override
start()200     public void start() {
201         mJavaAdapter.alwaysCollectFlow(
202                 mKeyguardTransitionInteractorLazy.get().isFinishedInState(GONE),
203                 (Boolean isFinishedInState) -> {
204                     if (isFinishedInState) {
205                         setLeaveOpenOnKeyguardHide(false);
206                     }
207                 });
208 
209         mJavaAdapter.alwaysCollectFlow(mShadeInteractorLazy.get().isAnyExpanded(),
210                 this::onShadeOrQsExpanded);
211 
212         if (SceneContainerFlag.isEnabled()) {
213             mJavaAdapter.alwaysCollectFlow(
214                     combineFlows(
215                         mDeviceUnlockedInteractorLazy.get().getDeviceUnlockStatus(),
216                         mSceneInteractorLazy.get().getCurrentScene(),
217                         this::calculateStateFromSceneFramework),
218                     this::onStatusBarStateChanged);
219         }
220     }
221 
222     @Override
getState()223     public int getState() {
224         return mState;
225     }
226 
227     @Override
setState(int state, boolean force)228     public boolean setState(int state, boolean force) {
229         if (SceneContainerFlag.isEnabled()) {
230             return false;
231         }
232 
233         if (state > MAX_STATE || state < MIN_STATE) {
234             throw new IllegalArgumentException("Invalid state " + state);
235         }
236 
237         // Unless we're explicitly asked to force the state change, don't apply the new state if
238         // it's identical to both the current and upcoming states, since that should not be
239         // necessary.
240         if (!force && state == mState && state == mUpcomingState) {
241             return false;
242         }
243 
244         updateStateAndNotifyListeners(state);
245         return true;
246     }
247 
248     /**
249      * Updates the {@link StatusBarState} and notifies registered listeners, if needed.
250      */
updateStateAndNotifyListeners(int state)251     private void updateStateAndNotifyListeners(int state) {
252         if (state != mUpcomingState) {
253             Log.d(TAG, "setState: requested state " + StatusBarState.toString(state)
254                     + "!= upcomingState: " + StatusBarState.toString(mUpcomingState) + ". "
255                     + "This usually means the status bar state transition was interrupted before "
256                     + "the upcoming state could be applied.");
257         }
258 
259         // Record the to-be mState and mLastState
260         recordHistoricalState(state /* newState */, mState /* lastState */, false);
261 
262         // b/139259891
263         if (mState == StatusBarState.SHADE && state == StatusBarState.SHADE_LOCKED) {
264             Log.e(TAG, "Invalid state transition: SHADE -> SHADE_LOCKED", new Throwable());
265         }
266 
267         synchronized (mListeners) {
268             String tag = getClass().getSimpleName() + "#setState(" + state + ")";
269             DejankUtils.startDetectingBlockingIpcs(tag);
270             for (RankedListener rl : new ArrayList<>(mListeners)) {
271                 rl.mListener.onStatePreChange(mState, state);
272             }
273             mLastState = mState;
274             mState = state;
275             updateUpcomingState(mState);
276             mUiEventLogger.log(StatusBarStateEvent.fromState(mState));
277             Trace.instantForTrack(Trace.TRACE_TAG_APP, "UI Events", "StatusBarState " + tag);
278             for (RankedListener rl : new ArrayList<>(mListeners)) {
279                 rl.mListener.onStateChanged(mState);
280             }
281 
282             for (RankedListener rl : new ArrayList<>(mListeners)) {
283                 rl.mListener.onStatePostChange();
284             }
285             DejankUtils.stopDetectingBlockingIpcs(tag);
286         }
287     }
288 
289     @Override
setUpcomingState(int nextState)290     public void setUpcomingState(int nextState) {
291         if (SceneContainerFlag.isEnabled()) {
292             return;
293         }
294 
295         recordHistoricalState(nextState /* newState */, mState /* lastState */, true);
296         updateUpcomingState(nextState);
297     }
298 
updateUpcomingState(int upcomingState)299     private void updateUpcomingState(int upcomingState) {
300         if (mUpcomingState != upcomingState) {
301             mUpcomingState = upcomingState;
302             for (RankedListener rl : new ArrayList<>(mListeners)) {
303                 rl.mListener.onUpcomingStateChanged(mUpcomingState);
304             }
305         }
306     }
307 
308     @Override
getCurrentOrUpcomingState()309     public int getCurrentOrUpcomingState() {
310         return mUpcomingState;
311     }
312 
313     @Override
isDozing()314     public boolean isDozing() {
315         return mIsDozing;
316     }
317 
318     @Override
isPulsing()319     public boolean isPulsing() {
320         return mPulsing;
321     }
322 
323     @Override
getDozeAmount()324     public float getDozeAmount() {
325         return mDozeAmount;
326     }
327 
328     @Override
isExpanded()329     public boolean isExpanded() {
330         return mIsExpanded;
331     }
332 
333     @Override
getInterpolatedDozeAmount()334     public float getInterpolatedDozeAmount() {
335         return mDozeInterpolator.getInterpolation(mDozeAmount);
336     }
337 
338     @Override
setIsDozing(boolean isDozing)339     public boolean setIsDozing(boolean isDozing) {
340         if (mIsDozing == isDozing) {
341             return false;
342         }
343 
344         mIsDozing = isDozing;
345 
346         synchronized (mListeners) {
347             String tag = getClass().getSimpleName() + "#setIsDozing";
348             DejankUtils.startDetectingBlockingIpcs(tag);
349             for (RankedListener rl : new ArrayList<>(mListeners)) {
350                 rl.mListener.onDozingChanged(isDozing);
351             }
352             DejankUtils.stopDetectingBlockingIpcs(tag);
353         }
354 
355         return true;
356     }
357 
358     @Override
setIsDreaming(boolean isDreaming)359     public boolean setIsDreaming(boolean isDreaming) {
360         if (Log.isLoggable(TAG, Log.DEBUG) || Compile.IS_DEBUG) {
361             Log.d(TAG, "setIsDreaming:" + isDreaming);
362         }
363         if (mIsDreaming == isDreaming) {
364             return false;
365         }
366 
367         mIsDreaming = isDreaming;
368 
369         synchronized (mListeners) {
370             String tag = getClass().getSimpleName() + "#setIsDreaming";
371             DejankUtils.startDetectingBlockingIpcs(tag);
372             for (RankedListener rl : new ArrayList<>(mListeners)) {
373                 rl.mListener.onDreamingChanged(isDreaming);
374             }
375             DejankUtils.stopDetectingBlockingIpcs(tag);
376         }
377 
378         return true;
379     }
380 
381     @Override
isDreaming()382     public boolean isDreaming() {
383         return mIsDreaming;
384     }
385 
386     @Override
setAndInstrumentDozeAmount(View view, float dozeAmount, boolean animated)387     public void setAndInstrumentDozeAmount(View view, float dozeAmount, boolean animated) {
388         if (mDarkAnimator != null && mDarkAnimator.isRunning()) {
389             if (animated && mDozeAmountTarget == dozeAmount) {
390                 return;
391             } else {
392                 mDarkAnimator.cancel();
393             }
394         }
395 
396         // We don't need a new attached view if we already have one.
397         if ((mView == null || !mView.isAttachedToWindow())
398                 && (view != null && view.isAttachedToWindow())) {
399             mView = view;
400             mClockSwitchView = view.findViewById(R.id.keyguard_clock_container);
401         }
402         mDozeAmountTarget = dozeAmount;
403         if (animated) {
404             startDozeAnimation();
405         } else {
406             setDozeAmountInternal(dozeAmount);
407         }
408     }
409 
onShadeOrQsExpanded(Boolean isExpanded)410     private void onShadeOrQsExpanded(Boolean isExpanded) {
411         if (mIsExpanded != isExpanded) {
412             mIsExpanded = isExpanded;
413             String tag = getClass().getSimpleName() + "#setIsExpanded";
414             DejankUtils.startDetectingBlockingIpcs(tag);
415             for (RankedListener rl : new ArrayList<>(mListeners)) {
416                 rl.mListener.onExpandedChanged(mIsExpanded);
417             }
418             DejankUtils.stopDetectingBlockingIpcs(tag);
419         }
420     }
421 
startDozeAnimation()422     private void startDozeAnimation() {
423         if (mDozeAmount == 0f || mDozeAmount == 1f) {
424             mDozeInterpolator = mIsDozing
425                     ? Interpolators.FAST_OUT_SLOW_IN
426                     : Interpolators.TOUCH_RESPONSE_REVERSE;
427         }
428         if (mDozeAmount == 1f && !mIsDozing) {
429             // Workaround to force relayoutWindow to be called a frame earlier. Otherwise, if
430             // mDozeAmount = 1f, then neither start() nor the first frame of the animation will
431             // cause the scrim opacity to change, which ultimately results in an extra relayout and
432             // causes us to miss a frame. By settings the doze amount to be <1f a frame earlier,
433             // we can batch the relayout with the one in NotificationShadeWindowControllerImpl.
434             setDozeAmountInternal(0.99f);
435         }
436         mDarkAnimator = createDarkAnimator();
437     }
438 
439     @VisibleForTesting
createDarkAnimator()440     protected ObjectAnimator createDarkAnimator() {
441         ObjectAnimator darkAnimator = ObjectAnimator.ofFloat(
442                 this, SET_DARK_AMOUNT_PROPERTY, mDozeAmountTarget);
443         darkAnimator.setInterpolator(Interpolators.LINEAR);
444         darkAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_WAKEUP);
445         darkAnimator.addListener(new AnimatorListenerAdapter() {
446             @Override
447             public void onAnimationCancel(Animator animation) {
448                 cancelInteractionJankMonitor();
449             }
450 
451             @Override
452             public void onAnimationEnd(Animator animation) {
453                 endInteractionJankMonitor();
454             }
455 
456             @Override
457             public void onAnimationStart(Animator animation) {
458                 beginInteractionJankMonitor();
459             }
460         });
461         darkAnimator.start();
462         return darkAnimator;
463     }
464 
setDozeAmountInternal(float dozeAmount)465     private void setDozeAmountInternal(float dozeAmount) {
466         if (Float.compare(dozeAmount, mDozeAmount) == 0) {
467             return;
468         }
469         mDozeAmount = dozeAmount;
470         float interpolatedAmount = mDozeInterpolator.getInterpolation(dozeAmount);
471         synchronized (mListeners) {
472             String tag = getClass().getSimpleName() + "#setDozeAmount";
473             DejankUtils.startDetectingBlockingIpcs(tag);
474             for (RankedListener rl : new ArrayList<>(mListeners)) {
475                 rl.mListener.onDozeAmountChanged(mDozeAmount, interpolatedAmount);
476             }
477             DejankUtils.stopDetectingBlockingIpcs(tag);
478         }
479     }
480 
481     /** Returns the id of the currently rendering clock */
getClockId()482     public String getClockId() {
483         if (MigrateClocksToBlueprint.isEnabled()) {
484             return mKeyguardClockInteractorLazy.get().getRenderedClockId();
485         }
486 
487         if (mClockSwitchView == null) {
488             Log.e(TAG, "Clock container was missing");
489             return KeyguardClockSwitch.MISSING_CLOCK_ID;
490         }
491 
492         return mClockSwitchView.getClockId();
493     }
494 
beginInteractionJankMonitor()495     private void beginInteractionJankMonitor() {
496         final boolean shouldPost =
497                 (mIsDozing && mDozeAmount == 0) || (!mIsDozing && mDozeAmount == 1);
498         InteractionJankMonitor monitor = mInteractionJankMonitorLazy.get();
499         if (monitor != null && mView != null && mView.isAttachedToWindow()) {
500             if (shouldPost) {
501                 Choreographer.getInstance().postCallback(
502                         Choreographer.CALLBACK_ANIMATION, this::beginInteractionJankMonitor, null);
503             } else {
504                 Configuration.Builder builder = Configuration.Builder.withView(getCujType(), mView)
505                         .setTag(getClockId())
506                         .setDeferMonitorForAnimationStart(false);
507                 monitor.begin(builder);
508             }
509         }
510     }
511 
endInteractionJankMonitor()512     private void endInteractionJankMonitor() {
513         InteractionJankMonitor monitor = mInteractionJankMonitorLazy.get();
514         if (monitor == null) {
515             return;
516         }
517         monitor.end(getCujType());
518     }
519 
cancelInteractionJankMonitor()520     private void cancelInteractionJankMonitor() {
521         InteractionJankMonitor monitor = mInteractionJankMonitorLazy.get();
522         if (monitor == null) {
523             return;
524         }
525         monitor.cancel(getCujType());
526     }
527 
getCujType()528     private int getCujType() {
529         return mIsDozing ? CUJ_LOCKSCREEN_TRANSITION_TO_AOD : CUJ_LOCKSCREEN_TRANSITION_FROM_AOD;
530     }
531 
532     @Override
goingToFullShade()533     public boolean goingToFullShade() {
534         return getState() == StatusBarState.SHADE && mLeaveOpenOnKeyguardHide;
535     }
536 
537     @Override
setLeaveOpenOnKeyguardHide(boolean leaveOpen)538     public void setLeaveOpenOnKeyguardHide(boolean leaveOpen) {
539         mLeaveOpenOnKeyguardHide = leaveOpen;
540     }
541 
542     @Override
leaveOpenOnKeyguardHide()543     public boolean leaveOpenOnKeyguardHide() {
544         return mLeaveOpenOnKeyguardHide;
545     }
546 
547     @Override
fromShadeLocked()548     public boolean fromShadeLocked() {
549         return mLastState == StatusBarState.SHADE_LOCKED;
550     }
551 
552     @Override
addCallback(@onNull StateListener listener)553     public void addCallback(@NonNull StateListener listener) {
554         synchronized (mListeners) {
555             addListenerInternalLocked(listener, Integer.MAX_VALUE);
556         }
557     }
558 
559     /**
560      * Add a listener and a rank based on the priority of this message
561      * @param listener the listener
562      * @param rank the order in which you'd like to be called. Ranked listeners will be
563      * notified before unranked, and we will sort ranked listeners from low to high
564      *
565      * @deprecated This method exists only to solve latent inter-dependencies from refactoring
566      * StatusBarState out of CentralSurfaces.java. Any new listeners should be built not to need
567      * ranking (i.e., they are non-dependent on the order of operations of StatusBarState
568      * listeners).
569      */
570     @Deprecated
571     @Override
addCallback(StateListener listener, @SbStateListenerRank int rank)572     public void addCallback(StateListener listener, @SbStateListenerRank int rank) {
573         synchronized (mListeners) {
574             addListenerInternalLocked(listener, rank);
575         }
576     }
577 
578     @GuardedBy("mListeners")
addListenerInternalLocked(StateListener listener, int rank)579     private void addListenerInternalLocked(StateListener listener, int rank) {
580         // Protect against double-subscribe
581         for (RankedListener rl : mListeners) {
582             if (rl.mListener.equals(listener)) {
583                 return;
584             }
585         }
586 
587         RankedListener rl = new SysuiStatusBarStateController.RankedListener(listener, rank);
588         mListeners.add(rl);
589         mListeners.sort(sComparator);
590     }
591 
592 
593     @Override
removeCallback(@onNull StateListener listener)594     public void removeCallback(@NonNull StateListener listener) {
595         synchronized (mListeners) {
596             mListeners.removeIf((it) -> it.mListener.equals(listener));
597         }
598     }
599 
600     @Override
setKeyguardRequested(boolean keyguardRequested)601     public void setKeyguardRequested(boolean keyguardRequested) {
602         mKeyguardRequested = keyguardRequested;
603     }
604 
605     @Override
isKeyguardRequested()606     public boolean isKeyguardRequested() {
607         return mKeyguardRequested;
608     }
609 
610     @Override
setPulsing(boolean pulsing)611     public void setPulsing(boolean pulsing) {
612         if (mPulsing != pulsing) {
613             mPulsing = pulsing;
614             synchronized (mListeners) {
615                 for (RankedListener rl : new ArrayList<>(mListeners)) {
616                     rl.mListener.onPulsingChanged(pulsing);
617                 }
618             }
619         }
620     }
621 
622     /**
623      * Returns String readable state of status bar from {@link StatusBarState}
624      */
describe(int state)625     public static String describe(int state) {
626         return StatusBarState.toString(state);
627     }
628 
629     @Override
dump(PrintWriter pw, String[] args)630     public void dump(PrintWriter pw, String[] args) {
631         pw.println("StatusBarStateController: ");
632         pw.println(" mState=" + mState + " (" + describe(mState) + ")");
633         pw.println(" mLastState=" + mLastState + " (" + describe(mLastState) + ")");
634         pw.println(" mLeaveOpenOnKeyguardHide=" + mLeaveOpenOnKeyguardHide);
635         pw.println(" mKeyguardRequested=" + mKeyguardRequested);
636         pw.println(" mIsDozing=" + mIsDozing);
637         pw.println(" mIsDreaming=" + mIsDreaming);
638         pw.println(" mListeners{" + mListeners.size() + "}=");
639         for (RankedListener rl : mListeners) {
640             pw.println("    " + rl.mListener);
641         }
642         pw.println(" Historical states:");
643         // Ignore records without a timestamp
644         int size = 0;
645         for (int i = 0; i < HISTORY_SIZE; i++) {
646             if (mHistoricalRecords[i].mTimestamp != 0) size++;
647         }
648         for (int i = mHistoryIndex + HISTORY_SIZE;
649                 i >= mHistoryIndex + HISTORY_SIZE - size + 1; i--) {
650             pw.println("  (" + (mHistoryIndex + HISTORY_SIZE - i + 1) + ")"
651                     + mHistoricalRecords[i & (HISTORY_SIZE - 1)]);
652         }
653     }
654 
recordHistoricalState(int newState, int lastState, boolean upcoming)655     private void recordHistoricalState(int newState, int lastState, boolean upcoming) {
656         Trace.traceCounter(Trace.TRACE_TAG_APP, "statusBarState", newState);
657         mHistoryIndex = (mHistoryIndex + 1) % HISTORY_SIZE;
658         HistoricalState state = mHistoricalRecords[mHistoryIndex];
659         state.mNewState = newState;
660         state.mLastState = lastState;
661         state.mTimestamp = System.currentTimeMillis();
662         state.mUpcoming = upcoming;
663     }
664 
calculateStateFromSceneFramework( DeviceUnlockStatus deviceUnlockStatus, SceneKey currentScene)665     private int calculateStateFromSceneFramework(
666             DeviceUnlockStatus deviceUnlockStatus,
667             SceneKey currentScene) {
668         SceneContainerFlag.isUnexpectedlyInLegacyMode();
669 
670         if (deviceUnlockStatus.isUnlocked()) {
671             return StatusBarState.SHADE;
672         } else {
673             return Preconditions.checkNotNull(sStatusBarStateByLockedSceneKey.get(currentScene));
674         }
675     }
676 
677     /** Notifies that the {@link StatusBarState} has changed to the given new state. */
onStatusBarStateChanged(int newState)678     private void onStatusBarStateChanged(int newState) {
679         SceneContainerFlag.isUnexpectedlyInLegacyMode();
680 
681         if (newState == mState) {
682             return;
683         }
684 
685         updateStateAndNotifyListeners(newState);
686     }
687 
688     private static final Map<SceneKey, Integer> sStatusBarStateByLockedSceneKey = Map.of(
689             Scenes.Lockscreen, StatusBarState.KEYGUARD,
690             Scenes.Bouncer, StatusBarState.KEYGUARD,
691             Scenes.Communal, StatusBarState.KEYGUARD,
692             Scenes.Shade, StatusBarState.SHADE_LOCKED,
693             Scenes.NotificationsShade, StatusBarState.SHADE_LOCKED,
694             Scenes.QuickSettings, StatusBarState.SHADE_LOCKED,
695             Scenes.QuickSettingsShade, StatusBarState.SHADE_LOCKED,
696             Scenes.Gone, StatusBarState.SHADE
697     );
698 
699     /**
700      * For keeping track of our previous state to help with debugging
701      */
702     private static class HistoricalState {
703         int mNewState;
704         int mLastState;
705         long mTimestamp;
706         boolean mUpcoming;
707 
708         @Override
toString()709         public String toString() {
710             if (mTimestamp != 0) {
711                 StringBuilder sb = new StringBuilder();
712                 if (mUpcoming) {
713                     sb.append("upcoming-");
714                 }
715                 sb.append("newState=").append(mNewState)
716                         .append("(").append(describe(mNewState)).append(")");
717                 sb.append(" lastState=").append(mLastState).append("(").append(describe(mLastState))
718                         .append(")");
719                 sb.append(" timestamp=")
720                         .append(DateFormat.format("MM-dd HH:mm:ss", mTimestamp));
721 
722                 return sb.toString();
723             }
724             return "Empty " + getClass().getSimpleName();
725         }
726     }
727 }
728