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