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