1 /* 2 * Copyright (C) 2014 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.phone; 18 19 import static java.lang.Float.isNaN; 20 21 import android.animation.Animator; 22 import android.animation.AnimatorListenerAdapter; 23 import android.animation.ValueAnimator; 24 import android.annotation.IntDef; 25 import android.app.AlarmManager; 26 import android.graphics.Color; 27 import android.os.Handler; 28 import android.os.Trace; 29 import android.util.Log; 30 import android.util.MathUtils; 31 import android.util.Pair; 32 import android.view.View; 33 import android.view.ViewTreeObserver; 34 import android.view.animation.DecelerateInterpolator; 35 import android.view.animation.Interpolator; 36 37 import androidx.annotation.FloatRange; 38 import androidx.annotation.Nullable; 39 40 import com.android.internal.annotations.VisibleForTesting; 41 import com.android.internal.colorextraction.ColorExtractor.GradientColors; 42 import com.android.internal.graphics.ColorUtils; 43 import com.android.internal.util.function.TriConsumer; 44 import com.android.keyguard.KeyguardUpdateMonitor; 45 import com.android.keyguard.KeyguardUpdateMonitorCallback; 46 import com.android.settingslib.Utils; 47 import com.android.systemui.DejankUtils; 48 import com.android.systemui.Dumpable; 49 import com.android.systemui.R; 50 import com.android.systemui.animation.ShadeInterpolation; 51 import com.android.systemui.dagger.SysUISingleton; 52 import com.android.systemui.dagger.qualifiers.Main; 53 import com.android.systemui.dock.DockManager; 54 import com.android.systemui.scrim.ScrimView; 55 import com.android.systemui.statusbar.notification.stack.ViewState; 56 import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; 57 import com.android.systemui.statusbar.policy.ConfigurationController; 58 import com.android.systemui.statusbar.policy.KeyguardStateController; 59 import com.android.systemui.util.AlarmTimeout; 60 import com.android.systemui.util.wakelock.DelayedWakeLock; 61 import com.android.systemui.util.wakelock.WakeLock; 62 63 import java.io.FileDescriptor; 64 import java.io.PrintWriter; 65 import java.lang.annotation.Retention; 66 import java.lang.annotation.RetentionPolicy; 67 import java.util.concurrent.Executor; 68 import java.util.function.Consumer; 69 70 import javax.inject.Inject; 71 72 /** 73 * Controls both the scrim behind the notifications and in front of the notifications (when a 74 * security method gets shown). 75 */ 76 @SysUISingleton 77 public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dumpable { 78 79 static final String TAG = "ScrimController"; 80 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 81 82 /** 83 * General scrim animation duration. 84 */ 85 public static final long ANIMATION_DURATION = 220; 86 /** 87 * Longer duration, currently only used when going to AOD. 88 */ 89 public static final long ANIMATION_DURATION_LONG = 1000; 90 /** 91 * When both scrims have 0 alpha. 92 */ 93 public static final int TRANSPARENT = 0; 94 /** 95 * When scrims aren't transparent (alpha 0) but also not opaque (alpha 1.) 96 */ 97 public static final int SEMI_TRANSPARENT = 1; 98 /** 99 * When at least 1 scrim is fully opaque (alpha set to 1.) 100 */ 101 public static final int OPAQUE = 2; 102 private boolean mClipsQsScrim; 103 104 /** 105 * The amount of progress we are currently in if we're transitioning to the full shade. 106 * 0.0f means we're not transitioning yet, while 1 means we're all the way in the full 107 * shade. 108 */ 109 private float mTransitionToFullShadeProgress; 110 111 /** 112 * If we're currently transitioning to the full shade. 113 */ 114 private boolean mTransitioningToFullShade; 115 116 /** 117 * Is there currently an unocclusion animation running. Used to avoid bright flickers 118 * of the notification scrim. 119 */ 120 private boolean mUnOcclusionAnimationRunning; 121 122 /** 123 * Set whether an unocclusion animation is currently running on the notification panel. Used 124 * to avoid bright flickers of the notification scrim. 125 */ setUnocclusionAnimationRunning(boolean unocclusionAnimationRunning)126 public void setUnocclusionAnimationRunning(boolean unocclusionAnimationRunning) { 127 mUnOcclusionAnimationRunning = unocclusionAnimationRunning; 128 } 129 130 @IntDef(prefix = {"VISIBILITY_"}, value = { 131 TRANSPARENT, 132 SEMI_TRANSPARENT, 133 OPAQUE 134 }) 135 @Retention(RetentionPolicy.SOURCE) 136 public @interface ScrimVisibility { 137 } 138 139 /** 140 * Default alpha value for most scrims. 141 */ 142 protected static final float KEYGUARD_SCRIM_ALPHA = 0.2f; 143 /** 144 * Scrim opacity when the phone is about to wake-up. 145 */ 146 public static final float WAKE_SENSOR_SCRIM_ALPHA = 0.6f; 147 148 /** 149 * The default scrim under the shade and dialogs. 150 * This should not be lower than 0.54, otherwise we won't pass GAR. 151 */ 152 public static final float BUSY_SCRIM_ALPHA = 1f; 153 154 /** 155 * Scrim opacity that can have text on top. 156 */ 157 public static final float GAR_SCRIM_ALPHA = 0.6f; 158 159 static final int TAG_KEY_ANIM = R.id.scrim; 160 private static final int TAG_START_ALPHA = R.id.scrim_alpha_start; 161 private static final int TAG_END_ALPHA = R.id.scrim_alpha_end; 162 private static final float NOT_INITIALIZED = -1; 163 164 private ScrimState mState = ScrimState.UNINITIALIZED; 165 166 private ScrimView mScrimInFront; 167 private ScrimView mNotificationsScrim; 168 private ScrimView mScrimBehind; 169 170 private Runnable mScrimBehindChangeRunnable; 171 172 private final KeyguardStateController mKeyguardStateController; 173 private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; 174 private final DozeParameters mDozeParameters; 175 private final DockManager mDockManager; 176 private final AlarmTimeout mTimeTicker; 177 private final KeyguardVisibilityCallback mKeyguardVisibilityCallback; 178 private final Handler mHandler; 179 private final Executor mMainExecutor; 180 private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; 181 182 private GradientColors mColors; 183 private boolean mNeedsDrawableColorUpdate; 184 185 private float mScrimBehindAlphaKeyguard = KEYGUARD_SCRIM_ALPHA; 186 private final float mDefaultScrimAlpha; 187 188 private float mRawPanelExpansionFraction; 189 private float mPanelScrimMinFraction; 190 // Calculated based on mRawPanelExpansionFraction and mPanelScrimMinFraction 191 private float mPanelExpansionFraction = 1f; // Assume shade is expanded during initialization 192 private float mQsExpansion; 193 private boolean mQsBottomVisible; 194 195 private boolean mDarkenWhileDragging; 196 private boolean mExpansionAffectsAlpha = true; 197 private boolean mAnimateChange; 198 private boolean mUpdatePending; 199 private boolean mTracking; 200 private long mAnimationDuration = -1; 201 private long mAnimationDelay; 202 private Animator.AnimatorListener mAnimatorListener; 203 private final Interpolator mInterpolator = new DecelerateInterpolator(); 204 205 private float mInFrontAlpha = NOT_INITIALIZED; 206 private float mBehindAlpha = NOT_INITIALIZED; 207 private float mNotificationsAlpha = NOT_INITIALIZED; 208 209 private int mInFrontTint; 210 private int mBehindTint; 211 private int mNotificationsTint; 212 213 private boolean mWallpaperVisibilityTimedOut; 214 private int mScrimsVisibility; 215 private final TriConsumer<ScrimState, Float, GradientColors> mScrimStateListener; 216 private Consumer<Integer> mScrimVisibleListener; 217 private boolean mBlankScreen; 218 private boolean mScreenBlankingCallbackCalled; 219 private Callback mCallback; 220 private boolean mWallpaperSupportsAmbientMode; 221 private boolean mScreenOn; 222 223 // Scrim blanking callbacks 224 private Runnable mPendingFrameCallback; 225 private Runnable mBlankingTransitionRunnable; 226 227 private final WakeLock mWakeLock; 228 private boolean mWakeLockHeld; 229 private boolean mKeyguardOccluded; 230 231 @Inject ScrimController(LightBarController lightBarController, DozeParameters dozeParameters, AlarmManager alarmManager, KeyguardStateController keyguardStateController, DelayedWakeLock.Builder delayedWakeLockBuilder, Handler handler, KeyguardUpdateMonitor keyguardUpdateMonitor, DockManager dockManager, ConfigurationController configurationController, @Main Executor mainExecutor, UnlockedScreenOffAnimationController unlockedScreenOffAnimationController, PanelExpansionStateManager panelExpansionStateManager)232 public ScrimController(LightBarController lightBarController, DozeParameters dozeParameters, 233 AlarmManager alarmManager, KeyguardStateController keyguardStateController, 234 DelayedWakeLock.Builder delayedWakeLockBuilder, Handler handler, 235 KeyguardUpdateMonitor keyguardUpdateMonitor, DockManager dockManager, 236 ConfigurationController configurationController, @Main Executor mainExecutor, 237 UnlockedScreenOffAnimationController unlockedScreenOffAnimationController, 238 PanelExpansionStateManager panelExpansionStateManager) { 239 mScrimStateListener = lightBarController::setScrimState; 240 mDefaultScrimAlpha = BUSY_SCRIM_ALPHA; 241 242 mKeyguardStateController = keyguardStateController; 243 mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen(); 244 mKeyguardUpdateMonitor = keyguardUpdateMonitor; 245 mKeyguardVisibilityCallback = new KeyguardVisibilityCallback(); 246 mHandler = handler; 247 mMainExecutor = mainExecutor; 248 mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; 249 mTimeTicker = new AlarmTimeout(alarmManager, this::onHideWallpaperTimeout, 250 "hide_aod_wallpaper", mHandler); 251 mWakeLock = delayedWakeLockBuilder.setHandler(mHandler).setTag("Scrims").build(); 252 // Scrim alpha is initially set to the value on the resource but might be changed 253 // to make sure that text on top of it is legible. 254 mDozeParameters = dozeParameters; 255 mDockManager = dockManager; 256 keyguardStateController.addCallback(new KeyguardStateController.Callback() { 257 @Override 258 public void onKeyguardFadingAwayChanged() { 259 setKeyguardFadingAway(keyguardStateController.isKeyguardFadingAway(), 260 keyguardStateController.getKeyguardFadingAwayDuration()); 261 } 262 }); 263 configurationController.addCallback(new ConfigurationController.ConfigurationListener() { 264 @Override 265 public void onThemeChanged() { 266 ScrimController.this.onThemeChanged(); 267 } 268 269 @Override 270 public void onUiModeChanged() { 271 ScrimController.this.onThemeChanged(); 272 } 273 }); 274 panelExpansionStateManager.addExpansionListener( 275 (fraction, expanded, tracking) -> setRawPanelExpansionFraction(fraction) 276 ); 277 278 mColors = new GradientColors(); 279 } 280 281 /** 282 * Attach the controller to the supplied views. 283 */ attachViews(ScrimView behindScrim, ScrimView notificationsScrim, ScrimView scrimInFront)284 public void attachViews(ScrimView behindScrim, ScrimView notificationsScrim, 285 ScrimView scrimInFront) { 286 mNotificationsScrim = notificationsScrim; 287 mScrimBehind = behindScrim; 288 mScrimInFront = scrimInFront; 289 updateThemeColors(); 290 291 behindScrim.enableBottomEdgeConcave(mClipsQsScrim); 292 mNotificationsScrim.enableRoundedCorners(true); 293 294 if (mScrimBehindChangeRunnable != null) { 295 mScrimBehind.setChangeRunnable(mScrimBehindChangeRunnable, mMainExecutor); 296 mScrimBehindChangeRunnable = null; 297 } 298 299 final ScrimState[] states = ScrimState.values(); 300 for (int i = 0; i < states.length; i++) { 301 states[i].init(mScrimInFront, mScrimBehind, mDozeParameters, mDockManager); 302 states[i].setScrimBehindAlphaKeyguard(mScrimBehindAlphaKeyguard); 303 states[i].setDefaultScrimAlpha(mDefaultScrimAlpha); 304 } 305 306 mScrimBehind.setDefaultFocusHighlightEnabled(false); 307 mNotificationsScrim.setDefaultFocusHighlightEnabled(false); 308 mScrimInFront.setDefaultFocusHighlightEnabled(false); 309 updateScrims(); 310 mKeyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback); 311 } 312 313 /** 314 * Sets corner radius of scrims. 315 */ setScrimCornerRadius(int radius)316 public void setScrimCornerRadius(int radius) { 317 if (mScrimBehind == null || mNotificationsScrim == null) { 318 return; 319 } 320 mScrimBehind.setCornerRadius(radius); 321 mNotificationsScrim.setCornerRadius(radius); 322 } 323 setScrimVisibleListener(Consumer<Integer> listener)324 void setScrimVisibleListener(Consumer<Integer> listener) { 325 mScrimVisibleListener = listener; 326 } 327 transitionTo(ScrimState state)328 public void transitionTo(ScrimState state) { 329 transitionTo(state, null); 330 } 331 transitionTo(ScrimState state, Callback callback)332 public void transitionTo(ScrimState state, Callback callback) { 333 if (state == mState) { 334 // Call the callback anyway, unless it's already enqueued 335 if (callback != null && mCallback != callback) { 336 callback.onFinished(); 337 } 338 return; 339 } else if (DEBUG) { 340 Log.d(TAG, "State changed to: " + state); 341 } 342 343 if (state == ScrimState.UNINITIALIZED) { 344 throw new IllegalArgumentException("Cannot change to UNINITIALIZED."); 345 } 346 347 final ScrimState oldState = mState; 348 mState = state; 349 Trace.traceCounter(Trace.TRACE_TAG_APP, "scrim_state", mState.ordinal()); 350 351 if (mCallback != null) { 352 mCallback.onCancelled(); 353 } 354 mCallback = callback; 355 356 state.prepare(oldState); 357 mScreenBlankingCallbackCalled = false; 358 mAnimationDelay = 0; 359 mBlankScreen = state.getBlanksScreen(); 360 mAnimateChange = state.getAnimateChange(); 361 mAnimationDuration = state.getAnimationDuration(); 362 363 applyState(); 364 365 // Scrim might acquire focus when user is navigating with a D-pad or a keyboard. 366 // We need to disable focus otherwise AOD would end up with a gray overlay. 367 mScrimInFront.setFocusable(!state.isLowPowerState()); 368 mScrimBehind.setFocusable(!state.isLowPowerState()); 369 mNotificationsScrim.setFocusable(!state.isLowPowerState()); 370 371 // Cancel blanking transitions that were pending before we requested a new state 372 if (mPendingFrameCallback != null) { 373 mScrimBehind.removeCallbacks(mPendingFrameCallback); 374 mPendingFrameCallback = null; 375 } 376 if (mHandler.hasCallbacks(mBlankingTransitionRunnable)) { 377 mHandler.removeCallbacks(mBlankingTransitionRunnable); 378 mBlankingTransitionRunnable = null; 379 } 380 381 // Showing/hiding the keyguard means that scrim colors have to be switched, not necessary 382 // to do the same when you're just showing the brightness mirror. 383 mNeedsDrawableColorUpdate = state != ScrimState.BRIGHTNESS_MIRROR; 384 385 // The device might sleep if it's entering AOD, we need to make sure that 386 // the animation plays properly until the last frame. 387 // It's important to avoid holding the wakelock unless necessary because 388 // WakeLock#aqcuire will trigger an IPC and will cause jank. 389 if (mState.isLowPowerState()) { 390 holdWakeLock(); 391 } 392 393 // AOD wallpapers should fade away after a while. 394 // Docking pulses may take a long time, wallpapers should also fade away after a while. 395 mWallpaperVisibilityTimedOut = false; 396 if (shouldFadeAwayWallpaper()) { 397 DejankUtils.postAfterTraversal(() -> { 398 mTimeTicker.schedule(mDozeParameters.getWallpaperAodDuration(), 399 AlarmTimeout.MODE_IGNORE_IF_SCHEDULED); 400 }); 401 } else { 402 DejankUtils.postAfterTraversal(mTimeTicker::cancel); 403 } 404 405 if (mKeyguardUpdateMonitor.needsSlowUnlockTransition() && mState == ScrimState.UNLOCKED) { 406 mAnimationDelay = StatusBar.FADE_KEYGUARD_START_DELAY; 407 scheduleUpdate(); 408 } else if (((oldState == ScrimState.AOD || oldState == ScrimState.PULSING) // leaving doze 409 && (!mDozeParameters.getAlwaysOn() || mState == ScrimState.UNLOCKED)) 410 || (mState == ScrimState.AOD && !mDozeParameters.getDisplayNeedsBlanking())) { 411 // Scheduling a frame isn't enough when: 412 // • Leaving doze and we need to modify scrim color immediately 413 // • ColorFade will not kick-in and scrim cannot wait for pre-draw. 414 onPreDraw(); 415 } else { 416 // Schedule a frame 417 scheduleUpdate(); 418 } 419 420 dispatchBackScrimState(mScrimBehind.getViewAlpha()); 421 } 422 shouldFadeAwayWallpaper()423 private boolean shouldFadeAwayWallpaper() { 424 if (!mWallpaperSupportsAmbientMode) { 425 return false; 426 } 427 428 if (mState == ScrimState.AOD 429 && (mDozeParameters.getAlwaysOn() || mDockManager.isDocked())) { 430 return true; 431 } 432 433 return false; 434 } 435 getState()436 public ScrimState getState() { 437 return mState; 438 } 439 setScrimBehindValues(float scrimBehindAlphaKeyguard)440 protected void setScrimBehindValues(float scrimBehindAlphaKeyguard) { 441 mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard; 442 ScrimState[] states = ScrimState.values(); 443 for (int i = 0; i < states.length; i++) { 444 states[i].setScrimBehindAlphaKeyguard(scrimBehindAlphaKeyguard); 445 } 446 scheduleUpdate(); 447 } 448 onTrackingStarted()449 public void onTrackingStarted() { 450 mTracking = true; 451 mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen(); 452 } 453 onExpandingFinished()454 public void onExpandingFinished() { 455 mTracking = false; 456 setUnocclusionAnimationRunning(false); 457 } 458 459 @VisibleForTesting onHideWallpaperTimeout()460 protected void onHideWallpaperTimeout() { 461 if (mState != ScrimState.AOD && mState != ScrimState.PULSING) { 462 return; 463 } 464 465 holdWakeLock(); 466 mWallpaperVisibilityTimedOut = true; 467 mAnimateChange = true; 468 mAnimationDuration = mDozeParameters.getWallpaperFadeOutDuration(); 469 scheduleUpdate(); 470 } 471 holdWakeLock()472 private void holdWakeLock() { 473 if (!mWakeLockHeld) { 474 if (mWakeLock != null) { 475 mWakeLockHeld = true; 476 mWakeLock.acquire(TAG); 477 } else { 478 Log.w(TAG, "Cannot hold wake lock, it has not been set yet"); 479 } 480 } 481 } 482 483 /** 484 * Current state of the shade expansion when pulling it from the top. 485 * This value is 1 when on top of the keyguard and goes to 0 as the user drags up. 486 * 487 * The expansion fraction is tied to the scrim opacity. 488 * 489 * See {@link PanelExpansionListener#onPanelExpansionChanged}. 490 * 491 * @param rawPanelExpansionFraction From 0 to 1 where 0 means collapsed and 1 expanded. 492 */ 493 @VisibleForTesting setRawPanelExpansionFraction( @loatRangefrom = 0.0, to = 1.0) float rawPanelExpansionFraction)494 void setRawPanelExpansionFraction( 495 @FloatRange(from = 0.0, to = 1.0) float rawPanelExpansionFraction) { 496 if (isNaN(rawPanelExpansionFraction)) { 497 throw new IllegalArgumentException("rawPanelExpansionFraction should not be NaN"); 498 } 499 mRawPanelExpansionFraction = rawPanelExpansionFraction; 500 calculateAndUpdatePanelExpansion(); 501 } 502 503 /** See {@link NotificationPanelViewController#setPanelScrimMinFraction(float)}. */ setPanelScrimMinFraction(float minFraction)504 public void setPanelScrimMinFraction(float minFraction) { 505 if (isNaN(minFraction)) { 506 throw new IllegalArgumentException("minFraction should not be NaN"); 507 } 508 mPanelScrimMinFraction = minFraction; 509 calculateAndUpdatePanelExpansion(); 510 } 511 calculateAndUpdatePanelExpansion()512 private void calculateAndUpdatePanelExpansion() { 513 float panelExpansionFraction = mRawPanelExpansionFraction; 514 if (mPanelScrimMinFraction < 1.0f) { 515 panelExpansionFraction = Math.max( 516 (mRawPanelExpansionFraction - mPanelScrimMinFraction) 517 / (1.0f - mPanelScrimMinFraction), 518 0); 519 } 520 521 if (mPanelExpansionFraction != panelExpansionFraction) { 522 mPanelExpansionFraction = panelExpansionFraction; 523 524 boolean relevantState = (mState == ScrimState.UNLOCKED 525 || mState == ScrimState.KEYGUARD 526 || mState == ScrimState.SHADE_LOCKED 527 || mState == ScrimState.PULSING); 528 if (!(relevantState && mExpansionAffectsAlpha)) { 529 return; 530 } 531 applyAndDispatchState(); 532 } 533 } 534 535 /** 536 * Set the amount of progress we are currently in if we're transitioning to the full shade. 537 * 0.0f means we're not transitioning yet, while 1 means we're all the way in the full 538 * shade. 539 */ setTransitionToFullShadeProgress(float progress)540 public void setTransitionToFullShadeProgress(float progress) { 541 if (progress != mTransitionToFullShadeProgress) { 542 mTransitionToFullShadeProgress = progress; 543 setTransitionToFullShade(progress > 0.0f); 544 applyAndDispatchState(); 545 } 546 } 547 548 /** 549 * Set if we're currently transitioning to the full shade 550 */ setTransitionToFullShade(boolean transitioning)551 private void setTransitionToFullShade(boolean transitioning) { 552 if (transitioning != mTransitioningToFullShade) { 553 mTransitioningToFullShade = transitioning; 554 if (transitioning) { 555 // Let's make sure the shade locked is ready 556 ScrimState.SHADE_LOCKED.prepare(mState); 557 } 558 } 559 } 560 561 562 /** 563 * Set bounds for notifications background, all coordinates are absolute 564 */ setNotificationsBounds(float left, float top, float right, float bottom)565 public void setNotificationsBounds(float left, float top, float right, float bottom) { 566 if (mClipsQsScrim) { 567 // notification scrim's rounded corners are anti-aliased, but clipping of the QS/behind 568 // scrim can't be and it's causing jagged corners. That's why notification scrim needs 569 // to overlap QS scrim by one pixel horizontally (left - 1 and right + 1) 570 // see: b/186644628 571 mNotificationsScrim.setDrawableBounds(left - 1, top, right + 1, bottom); 572 mScrimBehind.setBottomEdgePosition((int) top); 573 } else { 574 mNotificationsScrim.setDrawableBounds(left, top, right, bottom); 575 } 576 } 577 578 /** 579 * Current state of the QuickSettings when pulling it from the top. 580 * 581 * @param expansionFraction From 0 to 1 where 0 means collapsed and 1 expanded. 582 * @param qsPanelBottomY Absolute Y position of qs panel bottom 583 */ setQsPosition(float expansionFraction, int qsPanelBottomY)584 public void setQsPosition(float expansionFraction, int qsPanelBottomY) { 585 if (isNaN(expansionFraction)) { 586 return; 587 } 588 expansionFraction = ShadeInterpolation.getNotificationScrimAlpha(expansionFraction); 589 boolean qsBottomVisible = qsPanelBottomY > 0; 590 if (mQsExpansion != expansionFraction || mQsBottomVisible != qsBottomVisible) { 591 mQsExpansion = expansionFraction; 592 mQsBottomVisible = qsBottomVisible; 593 boolean relevantState = (mState == ScrimState.SHADE_LOCKED 594 || mState == ScrimState.KEYGUARD 595 || mState == ScrimState.PULSING); 596 if (!(relevantState && mExpansionAffectsAlpha)) { 597 return; 598 } 599 applyAndDispatchState(); 600 } 601 } 602 603 /** 604 * If QS and notification scrims should not overlap, and should be clipped to each other's 605 * bounds instead. 606 */ setClipsQsScrim(boolean clipScrim)607 public void setClipsQsScrim(boolean clipScrim) { 608 if (clipScrim == mClipsQsScrim) { 609 return; 610 } 611 mClipsQsScrim = clipScrim; 612 for (ScrimState state : ScrimState.values()) { 613 state.setClipQsScrim(mClipsQsScrim); 614 } 615 if (mScrimBehind != null) { 616 mScrimBehind.enableBottomEdgeConcave(mClipsQsScrim); 617 } 618 if (mState != ScrimState.UNINITIALIZED) { 619 // the clipScrimState has changed, let's reprepare ourselves 620 mState.prepare(mState); 621 applyAndDispatchState(); 622 } 623 } 624 625 @VisibleForTesting getClipQsScrim()626 public boolean getClipQsScrim() { 627 return mClipsQsScrim; 628 } 629 setOrAdaptCurrentAnimation(@ullable View scrim)630 private void setOrAdaptCurrentAnimation(@Nullable View scrim) { 631 if (scrim == null) { 632 return; 633 } 634 635 float alpha = getCurrentScrimAlpha(scrim); 636 boolean qsScrimPullingDown = scrim == mScrimBehind && mQsBottomVisible; 637 if (isAnimating(scrim) && !qsScrimPullingDown) { 638 // Adapt current animation. 639 ValueAnimator previousAnimator = (ValueAnimator) scrim.getTag(TAG_KEY_ANIM); 640 float previousEndValue = (Float) scrim.getTag(TAG_END_ALPHA); 641 float previousStartValue = (Float) scrim.getTag(TAG_START_ALPHA); 642 float relativeDiff = alpha - previousEndValue; 643 float newStartValue = previousStartValue + relativeDiff; 644 scrim.setTag(TAG_START_ALPHA, newStartValue); 645 scrim.setTag(TAG_END_ALPHA, alpha); 646 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); 647 } else { 648 // Set animation. 649 updateScrimColor(scrim, alpha, getCurrentScrimTint(scrim)); 650 } 651 } 652 applyState()653 private void applyState() { 654 mInFrontTint = mState.getFrontTint(); 655 mBehindTint = mState.getBehindTint(); 656 mNotificationsTint = mState.getNotifTint(); 657 658 mInFrontAlpha = mState.getFrontAlpha(); 659 mBehindAlpha = mState.getBehindAlpha(); 660 mNotificationsAlpha = mState.getNotifAlpha(); 661 662 assertAlphasValid(); 663 664 if (!mExpansionAffectsAlpha) { 665 return; 666 } 667 668 if (mState == ScrimState.UNLOCKED) { 669 // Darken scrim as you pull down the shade when unlocked, unless the shade is expanding 670 // because we're doing the screen off animation. 671 if (!mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()) { 672 float behindFraction = getInterpolatedFraction(); 673 behindFraction = (float) Math.pow(behindFraction, 0.8f); 674 if (mClipsQsScrim) { 675 mBehindAlpha = 1; 676 mNotificationsAlpha = behindFraction * mDefaultScrimAlpha; 677 } else { 678 mBehindAlpha = behindFraction * mDefaultScrimAlpha; 679 mNotificationsAlpha = mBehindAlpha; 680 } 681 mInFrontAlpha = 0; 682 } 683 } else if (mState == ScrimState.AUTH_SCRIMMED_SHADE) { 684 float behindFraction = getInterpolatedFraction(); 685 behindFraction = (float) Math.pow(behindFraction, 0.8f); 686 687 mBehindAlpha = behindFraction * mDefaultScrimAlpha; 688 mNotificationsAlpha = mBehindAlpha; 689 } else if (mState == ScrimState.KEYGUARD || mState == ScrimState.SHADE_LOCKED 690 || mState == ScrimState.PULSING) { 691 Pair<Integer, Float> result = calculateBackStateForState(mState); 692 int behindTint = result.first; 693 float behindAlpha = result.second; 694 if (mTransitionToFullShadeProgress > 0.0f) { 695 Pair<Integer, Float> shadeResult = calculateBackStateForState( 696 ScrimState.SHADE_LOCKED); 697 behindAlpha = MathUtils.lerp(behindAlpha, shadeResult.second, 698 mTransitionToFullShadeProgress); 699 behindTint = ColorUtils.blendARGB(behindTint, shadeResult.first, 700 mTransitionToFullShadeProgress); 701 } 702 mInFrontAlpha = mState.getFrontAlpha(); 703 if (mClipsQsScrim) { 704 mNotificationsAlpha = behindAlpha; 705 mNotificationsTint = behindTint; 706 mBehindAlpha = 1; 707 mBehindTint = Color.BLACK; 708 } else { 709 mBehindAlpha = behindAlpha; 710 if (mState == ScrimState.SHADE_LOCKED) { 711 // going from KEYGUARD to SHADE_LOCKED state 712 mNotificationsAlpha = getInterpolatedFraction(); 713 } else { 714 mNotificationsAlpha = Math.max(1.0f - getInterpolatedFraction(), mQsExpansion); 715 } 716 if (mState == ScrimState.KEYGUARD && mTransitionToFullShadeProgress > 0.0f) { 717 // Interpolate the notification alpha when transitioning! 718 mNotificationsAlpha = MathUtils.lerp( 719 mNotificationsAlpha, 720 getInterpolatedFraction(), 721 mTransitionToFullShadeProgress); 722 } 723 mNotificationsTint = mState.getNotifTint(); 724 mBehindTint = behindTint; 725 } 726 727 // At the end of a launch animation over the lockscreen, the state is either KEYGUARD or 728 // SHADE_LOCKED and this code is called. We have to set the notification alpha to 0 729 // otherwise there is a flicker to its previous value. 730 if (mKeyguardOccluded) { 731 mNotificationsAlpha = 0; 732 } 733 if (mUnOcclusionAnimationRunning && mState == ScrimState.KEYGUARD) { 734 // We're unoccluding the keyguard and don't want to have a bright flash. 735 mNotificationsAlpha = KEYGUARD_SCRIM_ALPHA; 736 mNotificationsTint = ScrimState.KEYGUARD.getNotifTint(); 737 } 738 } 739 740 assertAlphasValid(); 741 } 742 assertAlphasValid()743 private void assertAlphasValid() { 744 if (isNaN(mBehindAlpha) || isNaN(mInFrontAlpha) || isNaN(mNotificationsAlpha)) { 745 throw new IllegalStateException("Scrim opacity is NaN for state: " + mState 746 + ", front: " + mInFrontAlpha + ", back: " + mBehindAlpha + ", notif: " 747 + mNotificationsAlpha); 748 } 749 } 750 calculateBackStateForState(ScrimState state)751 private Pair<Integer, Float> calculateBackStateForState(ScrimState state) { 752 // Either darken of make the scrim transparent when you 753 // pull down the shade 754 float interpolatedFract = getInterpolatedFraction(); 755 float stateBehind = mClipsQsScrim ? state.getNotifAlpha() : state.getBehindAlpha(); 756 float behindAlpha; 757 int behindTint; 758 if (mDarkenWhileDragging) { 759 behindAlpha = MathUtils.lerp(mDefaultScrimAlpha, stateBehind, 760 interpolatedFract); 761 } else { 762 behindAlpha = MathUtils.lerp(0 /* start */, stateBehind, 763 interpolatedFract); 764 } 765 if (mClipsQsScrim) { 766 behindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getNotifTint(), 767 state.getNotifTint(), interpolatedFract); 768 } else { 769 behindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getBehindTint(), 770 state.getBehindTint(), interpolatedFract); 771 } 772 if (mQsExpansion > 0) { 773 behindAlpha = MathUtils.lerp(behindAlpha, mDefaultScrimAlpha, mQsExpansion); 774 int stateTint = mClipsQsScrim ? ScrimState.SHADE_LOCKED.getNotifTint() 775 : ScrimState.SHADE_LOCKED.getBehindTint(); 776 behindTint = ColorUtils.blendARGB(behindTint, stateTint, mQsExpansion); 777 } 778 return new Pair<>(behindTint, behindAlpha); 779 } 780 781 applyAndDispatchState()782 private void applyAndDispatchState() { 783 applyState(); 784 if (mUpdatePending) { 785 return; 786 } 787 setOrAdaptCurrentAnimation(mScrimBehind); 788 setOrAdaptCurrentAnimation(mNotificationsScrim); 789 setOrAdaptCurrentAnimation(mScrimInFront); 790 dispatchBackScrimState(mScrimBehind.getViewAlpha()); 791 792 // Reset wallpaper timeout if it's already timeout like expanding panel while PULSING 793 // and docking. 794 if (mWallpaperVisibilityTimedOut) { 795 mWallpaperVisibilityTimedOut = false; 796 DejankUtils.postAfterTraversal(() -> { 797 mTimeTicker.schedule(mDozeParameters.getWallpaperAodDuration(), 798 AlarmTimeout.MODE_IGNORE_IF_SCHEDULED); 799 }); 800 } 801 } 802 803 /** 804 * Sets the front scrim opacity in AOD so it's not as bright. 805 * <p> 806 * Displays usually don't support multiple dimming settings when in low power mode. 807 * The workaround is to modify the front scrim opacity when in AOD, so it's not as 808 * bright when you're at the movies or lying down on bed. 809 * <p> 810 * This value will be lost during transitions and only updated again after the the 811 * device is dozing when the light sensor is on. 812 */ setAodFrontScrimAlpha(float alpha)813 public void setAodFrontScrimAlpha(float alpha) { 814 if (mInFrontAlpha != alpha && shouldUpdateFrontScrimAlpha()) { 815 mInFrontAlpha = alpha; 816 updateScrims(); 817 } 818 819 mState.AOD.setAodFrontScrimAlpha(alpha); 820 mState.PULSING.setAodFrontScrimAlpha(alpha); 821 } 822 shouldUpdateFrontScrimAlpha()823 private boolean shouldUpdateFrontScrimAlpha() { 824 if (mState == ScrimState.AOD 825 && (mDozeParameters.getAlwaysOn() || mDockManager.isDocked())) { 826 return true; 827 } 828 829 if (mState == ScrimState.PULSING) { 830 return true; 831 } 832 833 return false; 834 } 835 836 /** 837 * If the lock screen sensor is active. 838 */ setWakeLockScreenSensorActive(boolean active)839 public void setWakeLockScreenSensorActive(boolean active) { 840 for (ScrimState state : ScrimState.values()) { 841 state.setWakeLockScreenSensorActive(active); 842 } 843 844 if (mState == ScrimState.PULSING) { 845 float newBehindAlpha = mState.getBehindAlpha(); 846 if (mBehindAlpha != newBehindAlpha) { 847 mBehindAlpha = newBehindAlpha; 848 if (isNaN(mBehindAlpha)) { 849 throw new IllegalStateException("Scrim opacity is NaN for state: " + mState 850 + ", back: " + mBehindAlpha); 851 } 852 updateScrims(); 853 } 854 } 855 } 856 scheduleUpdate()857 protected void scheduleUpdate() { 858 if (mUpdatePending || mScrimBehind == null) return; 859 860 // Make sure that a frame gets scheduled. 861 mScrimBehind.invalidate(); 862 mScrimBehind.getViewTreeObserver().addOnPreDrawListener(this); 863 mUpdatePending = true; 864 } 865 updateScrims()866 protected void updateScrims() { 867 // Make sure we have the right gradients and their opacities will satisfy GAR. 868 if (mNeedsDrawableColorUpdate) { 869 mNeedsDrawableColorUpdate = false; 870 // Only animate scrim color if the scrim view is actually visible 871 boolean animateScrimInFront = mScrimInFront.getViewAlpha() != 0 && !mBlankScreen; 872 boolean animateBehindScrim = mScrimBehind.getViewAlpha() != 0 && !mBlankScreen; 873 boolean animateScrimNotifications = mNotificationsScrim.getViewAlpha() != 0 874 && !mBlankScreen; 875 876 mScrimInFront.setColors(mColors, animateScrimInFront); 877 mScrimBehind.setColors(mColors, animateBehindScrim); 878 mNotificationsScrim.setColors(mColors, animateScrimNotifications); 879 880 dispatchBackScrimState(mScrimBehind.getViewAlpha()); 881 } 882 883 // We want to override the back scrim opacity for the AOD state 884 // when it's time to fade the wallpaper away. 885 boolean aodWallpaperTimeout = (mState == ScrimState.AOD || mState == ScrimState.PULSING) 886 && mWallpaperVisibilityTimedOut; 887 // We also want to hide FLAG_SHOW_WHEN_LOCKED activities under the scrim. 888 boolean occludedKeyguard = (mState == ScrimState.PULSING || mState == ScrimState.AOD) 889 && mKeyguardOccluded; 890 if (aodWallpaperTimeout || occludedKeyguard) { 891 mBehindAlpha = 1; 892 } 893 setScrimAlpha(mScrimInFront, mInFrontAlpha); 894 setScrimAlpha(mScrimBehind, mBehindAlpha); 895 setScrimAlpha(mNotificationsScrim, mNotificationsAlpha); 896 897 // The animation could have all already finished, let's call onFinished just in case 898 onFinished(mState); 899 dispatchScrimsVisible(); 900 } 901 dispatchBackScrimState(float alpha)902 private void dispatchBackScrimState(float alpha) { 903 // When clipping QS, the notification scrim is the one that feels behind. 904 // mScrimBehind will be drawing black and its opacity will always be 1. 905 if (mClipsQsScrim && mQsBottomVisible) { 906 alpha = mNotificationsAlpha; 907 } 908 mScrimStateListener.accept(mState, alpha, mScrimInFront.getColors()); 909 } 910 dispatchScrimsVisible()911 private void dispatchScrimsVisible() { 912 final ScrimView backScrim = mClipsQsScrim ? mNotificationsScrim : mScrimBehind; 913 final int currentScrimVisibility; 914 if (mScrimInFront.getViewAlpha() == 1 || backScrim.getViewAlpha() == 1) { 915 currentScrimVisibility = OPAQUE; 916 } else if (mScrimInFront.getViewAlpha() == 0 && backScrim.getViewAlpha() == 0) { 917 currentScrimVisibility = TRANSPARENT; 918 } else { 919 currentScrimVisibility = SEMI_TRANSPARENT; 920 } 921 922 if (mScrimsVisibility != currentScrimVisibility) { 923 mScrimsVisibility = currentScrimVisibility; 924 mScrimVisibleListener.accept(currentScrimVisibility); 925 } 926 } 927 getInterpolatedFraction()928 private float getInterpolatedFraction() { 929 return ShadeInterpolation.getNotificationScrimAlpha(mPanelExpansionFraction); 930 } 931 setScrimAlpha(ScrimView scrim, float alpha)932 private void setScrimAlpha(ScrimView scrim, float alpha) { 933 if (alpha == 0f) { 934 scrim.setClickable(false); 935 } else { 936 // Eat touch events (unless dozing). 937 scrim.setClickable(mState != ScrimState.AOD); 938 } 939 updateScrim(scrim, alpha); 940 } 941 getScrimName(ScrimView scrim)942 private String getScrimName(ScrimView scrim) { 943 if (scrim == mScrimInFront) { 944 return "front_scrim"; 945 } else if (scrim == mScrimBehind) { 946 return "behind_scrim"; 947 } else if (scrim == mNotificationsScrim) { 948 return "notifications_scrim"; 949 } 950 return "unknown_scrim"; 951 } 952 updateScrimColor(View scrim, float alpha, int tint)953 private void updateScrimColor(View scrim, float alpha, int tint) { 954 alpha = Math.max(0, Math.min(1.0f, alpha)); 955 if (scrim instanceof ScrimView) { 956 ScrimView scrimView = (ScrimView) scrim; 957 958 Trace.traceCounter(Trace.TRACE_TAG_APP, getScrimName(scrimView) + "_alpha", 959 (int) (alpha * 255)); 960 961 Trace.traceCounter(Trace.TRACE_TAG_APP, getScrimName(scrimView) + "_tint", 962 Color.alpha(tint)); 963 scrimView.setTint(tint); 964 scrimView.setViewAlpha(alpha); 965 } else { 966 scrim.setAlpha(alpha); 967 } 968 dispatchScrimsVisible(); 969 } 970 startScrimAnimation(final View scrim, float current)971 private void startScrimAnimation(final View scrim, float current) { 972 ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f); 973 if (mAnimatorListener != null) { 974 anim.addListener(mAnimatorListener); 975 } 976 final int initialScrimTint = scrim instanceof ScrimView ? ((ScrimView) scrim).getTint() : 977 Color.TRANSPARENT; 978 anim.addUpdateListener(animation -> { 979 final float startAlpha = (Float) scrim.getTag(TAG_START_ALPHA); 980 final float animAmount = (float) animation.getAnimatedValue(); 981 final int finalScrimTint = getCurrentScrimTint(scrim); 982 final float finalScrimAlpha = getCurrentScrimAlpha(scrim); 983 float alpha = MathUtils.lerp(startAlpha, finalScrimAlpha, animAmount); 984 alpha = MathUtils.constrain(alpha, 0f, 1f); 985 int tint = ColorUtils.blendARGB(initialScrimTint, finalScrimTint, animAmount); 986 updateScrimColor(scrim, alpha, tint); 987 dispatchScrimsVisible(); 988 }); 989 anim.setInterpolator(mInterpolator); 990 anim.setStartDelay(mAnimationDelay); 991 anim.setDuration(mAnimationDuration); 992 anim.addListener(new AnimatorListenerAdapter() { 993 private final ScrimState mLastState = mState; 994 private final Callback mLastCallback = mCallback; 995 996 @Override 997 public void onAnimationEnd(Animator animation) { 998 scrim.setTag(TAG_KEY_ANIM, null); 999 onFinished(mLastCallback, mLastState); 1000 1001 dispatchScrimsVisible(); 1002 } 1003 }); 1004 1005 // Cache alpha values because we might want to update this animator in the future if 1006 // the user expands the panel while the animation is still running. 1007 scrim.setTag(TAG_START_ALPHA, current); 1008 scrim.setTag(TAG_END_ALPHA, getCurrentScrimAlpha(scrim)); 1009 1010 scrim.setTag(TAG_KEY_ANIM, anim); 1011 anim.start(); 1012 } 1013 getCurrentScrimAlpha(View scrim)1014 private float getCurrentScrimAlpha(View scrim) { 1015 if (scrim == mScrimInFront) { 1016 return mInFrontAlpha; 1017 } else if (scrim == mScrimBehind) { 1018 return mBehindAlpha; 1019 } else if (scrim == mNotificationsScrim) { 1020 return mNotificationsAlpha; 1021 } else { 1022 throw new IllegalArgumentException("Unknown scrim view"); 1023 } 1024 } 1025 getCurrentScrimTint(View scrim)1026 private int getCurrentScrimTint(View scrim) { 1027 if (scrim == mScrimInFront) { 1028 return mInFrontTint; 1029 } else if (scrim == mScrimBehind) { 1030 return mBehindTint; 1031 } else if (scrim == mNotificationsScrim) { 1032 return mNotificationsTint; 1033 } else { 1034 throw new IllegalArgumentException("Unknown scrim view"); 1035 } 1036 } 1037 1038 @Override onPreDraw()1039 public boolean onPreDraw() { 1040 mScrimBehind.getViewTreeObserver().removeOnPreDrawListener(this); 1041 mUpdatePending = false; 1042 if (mCallback != null) { 1043 mCallback.onStart(); 1044 } 1045 updateScrims(); 1046 return true; 1047 } 1048 1049 /** 1050 * @param state that finished 1051 */ onFinished(ScrimState state)1052 private void onFinished(ScrimState state) { 1053 onFinished(mCallback, state); 1054 } 1055 onFinished(Callback callback, ScrimState state)1056 private void onFinished(Callback callback, ScrimState state) { 1057 if (mPendingFrameCallback != null) { 1058 // No animations can finish while we're waiting on the blanking to finish 1059 return; 1060 1061 } 1062 if (isAnimating(mScrimBehind) 1063 || isAnimating(mNotificationsScrim) 1064 || isAnimating(mScrimInFront)) { 1065 if (callback != null && callback != mCallback) { 1066 // Since we only notify the callback that we're finished once everything has 1067 // finished, we need to make sure that any changing callbacks are also invoked 1068 callback.onFinished(); 1069 } 1070 return; 1071 } 1072 if (mWakeLockHeld) { 1073 mWakeLock.release(TAG); 1074 mWakeLockHeld = false; 1075 } 1076 1077 if (callback != null) { 1078 callback.onFinished(); 1079 1080 if (callback == mCallback) { 1081 mCallback = null; 1082 } 1083 } 1084 1085 // When unlocking with fingerprint, we'll fade the scrims from black to transparent. 1086 // At the end of the animation we need to remove the tint. 1087 if (state == ScrimState.UNLOCKED) { 1088 mInFrontTint = Color.TRANSPARENT; 1089 mBehindTint = mState.getBehindTint(); 1090 mNotificationsTint = mState.getNotifTint(); 1091 updateScrimColor(mScrimInFront, mInFrontAlpha, mInFrontTint); 1092 updateScrimColor(mScrimBehind, mBehindAlpha, mBehindTint); 1093 updateScrimColor(mNotificationsScrim, mNotificationsAlpha, mNotificationsTint); 1094 } 1095 } 1096 isAnimating(@ullable View scrim)1097 private boolean isAnimating(@Nullable View scrim) { 1098 return scrim != null && scrim.getTag(TAG_KEY_ANIM) != null; 1099 } 1100 1101 @VisibleForTesting setAnimatorListener(Animator.AnimatorListener animatorListener)1102 void setAnimatorListener(Animator.AnimatorListener animatorListener) { 1103 mAnimatorListener = animatorListener; 1104 } 1105 updateScrim(ScrimView scrim, float alpha)1106 private void updateScrim(ScrimView scrim, float alpha) { 1107 final float currentAlpha = scrim.getViewAlpha(); 1108 1109 ValueAnimator previousAnimator = ViewState.getChildTag(scrim, TAG_KEY_ANIM); 1110 if (previousAnimator != null) { 1111 // Previous animators should always be cancelled. Not doing so would cause 1112 // overlap, especially on states that don't animate, leading to flickering, 1113 // and in the worst case, an internal state that doesn't represent what 1114 // transitionTo requested. 1115 cancelAnimator(previousAnimator); 1116 } 1117 1118 if (mPendingFrameCallback != null) { 1119 // Display is off and we're waiting. 1120 return; 1121 } else if (mBlankScreen) { 1122 // Need to blank the display before continuing. 1123 blankDisplay(); 1124 return; 1125 } else if (!mScreenBlankingCallbackCalled) { 1126 // Not blanking the screen. Letting the callback know that we're ready 1127 // to replace what was on the screen before. 1128 if (mCallback != null) { 1129 mCallback.onDisplayBlanked(); 1130 mScreenBlankingCallbackCalled = true; 1131 } 1132 } 1133 1134 if (scrim == mScrimBehind) { 1135 dispatchBackScrimState(alpha); 1136 } 1137 1138 final boolean wantsAlphaUpdate = alpha != currentAlpha; 1139 final boolean wantsTintUpdate = scrim.getTint() != getCurrentScrimTint(scrim); 1140 1141 if (wantsAlphaUpdate || wantsTintUpdate) { 1142 if (mAnimateChange) { 1143 startScrimAnimation(scrim, currentAlpha); 1144 } else { 1145 // update the alpha directly 1146 updateScrimColor(scrim, alpha, getCurrentScrimTint(scrim)); 1147 } 1148 } 1149 } 1150 cancelAnimator(ValueAnimator previousAnimator)1151 private void cancelAnimator(ValueAnimator previousAnimator) { 1152 if (previousAnimator != null) { 1153 previousAnimator.cancel(); 1154 } 1155 } 1156 blankDisplay()1157 private void blankDisplay() { 1158 updateScrimColor(mScrimInFront, 1, Color.BLACK); 1159 1160 // Notify callback that the screen is completely black and we're 1161 // ready to change the display power mode 1162 mPendingFrameCallback = () -> { 1163 if (mCallback != null) { 1164 mCallback.onDisplayBlanked(); 1165 mScreenBlankingCallbackCalled = true; 1166 } 1167 1168 mBlankingTransitionRunnable = () -> { 1169 mBlankingTransitionRunnable = null; 1170 mPendingFrameCallback = null; 1171 mBlankScreen = false; 1172 // Try again. 1173 updateScrims(); 1174 }; 1175 1176 // Setting power states can happen after we push out the frame. Make sure we 1177 // stay fully opaque until the power state request reaches the lower levels. 1178 final int delay = mScreenOn ? 32 : 500; 1179 if (DEBUG) { 1180 Log.d(TAG, "Fading out scrims with delay: " + delay); 1181 } 1182 mHandler.postDelayed(mBlankingTransitionRunnable, delay); 1183 }; 1184 doOnTheNextFrame(mPendingFrameCallback); 1185 } 1186 1187 /** 1188 * Executes a callback after the frame has hit the display. 1189 * 1190 * @param callback What to run. 1191 */ 1192 @VisibleForTesting doOnTheNextFrame(Runnable callback)1193 protected void doOnTheNextFrame(Runnable callback) { 1194 // Just calling View#postOnAnimation isn't enough because the frame might not have reached 1195 // the display yet. A timeout is the safest solution. 1196 mScrimBehind.postOnAnimationDelayed(callback, 32 /* delayMillis */); 1197 } 1198 setScrimBehindChangeRunnable(Runnable changeRunnable)1199 public void setScrimBehindChangeRunnable(Runnable changeRunnable) { 1200 // TODO: remove this. This is necessary because of an order-of-operations limitation. 1201 // The fix is to move more of these class into @StatusBarScope 1202 if (mScrimBehind == null) { 1203 mScrimBehindChangeRunnable = changeRunnable; 1204 } else { 1205 mScrimBehind.setChangeRunnable(changeRunnable, mMainExecutor); 1206 } 1207 } 1208 setCurrentUser(int currentUser)1209 public void setCurrentUser(int currentUser) { 1210 // Don't care in the base class. 1211 } 1212 updateThemeColors()1213 private void updateThemeColors() { 1214 if (mScrimBehind == null) return; 1215 int background = Utils.getColorAttr(mScrimBehind.getContext(), 1216 android.R.attr.colorBackgroundFloating).getDefaultColor(); 1217 int accent = Utils.getColorAccent(mScrimBehind.getContext()).getDefaultColor(); 1218 mColors.setMainColor(background); 1219 mColors.setSecondaryColor(accent); 1220 mColors.setSupportsDarkText( 1221 ColorUtils.calculateContrast(mColors.getMainColor(), Color.WHITE) > 4.5); 1222 mNeedsDrawableColorUpdate = true; 1223 } 1224 onThemeChanged()1225 private void onThemeChanged() { 1226 updateThemeColors(); 1227 scheduleUpdate(); 1228 } 1229 1230 @Override dump(FileDescriptor fd, PrintWriter pw, String[] args)1231 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 1232 pw.println(" ScrimController: "); 1233 pw.print(" state: "); 1234 pw.println(mState); 1235 pw.println(" mClipQsScrim = " + mState.mClipQsScrim); 1236 1237 pw.print(" frontScrim:"); 1238 pw.print(" viewAlpha="); 1239 pw.print(mScrimInFront.getViewAlpha()); 1240 pw.print(" alpha="); 1241 pw.print(mInFrontAlpha); 1242 pw.print(" tint=0x"); 1243 pw.println(Integer.toHexString(mScrimInFront.getTint())); 1244 1245 pw.print(" behindScrim:"); 1246 pw.print(" viewAlpha="); 1247 pw.print(mScrimBehind.getViewAlpha()); 1248 pw.print(" alpha="); 1249 pw.print(mBehindAlpha); 1250 pw.print(" tint=0x"); 1251 pw.println(Integer.toHexString(mScrimBehind.getTint())); 1252 1253 pw.print(" notificationsScrim:"); 1254 pw.print(" viewAlpha="); 1255 pw.print(mNotificationsScrim.getViewAlpha()); 1256 pw.print(" alpha="); 1257 pw.print(mNotificationsAlpha); 1258 pw.print(" tint=0x"); 1259 pw.println(Integer.toHexString(mNotificationsScrim.getTint())); 1260 1261 pw.print(" mTracking="); 1262 pw.println(mTracking); 1263 pw.print(" mDefaultScrimAlpha="); 1264 pw.println(mDefaultScrimAlpha); 1265 pw.print(" mPanelExpansionFraction="); 1266 pw.println(mPanelExpansionFraction); 1267 pw.print(" mExpansionAffectsAlpha="); 1268 pw.println(mExpansionAffectsAlpha); 1269 1270 pw.print(" mState.getMaxLightRevealScrimAlpha="); 1271 pw.println(mState.getMaxLightRevealScrimAlpha()); 1272 } 1273 setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode)1274 public void setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode) { 1275 mWallpaperSupportsAmbientMode = wallpaperSupportsAmbientMode; 1276 ScrimState[] states = ScrimState.values(); 1277 for (int i = 0; i < states.length; i++) { 1278 states[i].setWallpaperSupportsAmbientMode(wallpaperSupportsAmbientMode); 1279 } 1280 } 1281 1282 /** 1283 * Interrupts blanking transitions once the display notifies that it's already on. 1284 */ onScreenTurnedOn()1285 public void onScreenTurnedOn() { 1286 mScreenOn = true; 1287 if (mHandler.hasCallbacks(mBlankingTransitionRunnable)) { 1288 if (DEBUG) { 1289 Log.d(TAG, "Shorter blanking because screen turned on. All good."); 1290 } 1291 mHandler.removeCallbacks(mBlankingTransitionRunnable); 1292 mBlankingTransitionRunnable.run(); 1293 } 1294 } 1295 onScreenTurnedOff()1296 public void onScreenTurnedOff() { 1297 mScreenOn = false; 1298 } 1299 setExpansionAffectsAlpha(boolean expansionAffectsAlpha)1300 public void setExpansionAffectsAlpha(boolean expansionAffectsAlpha) { 1301 mExpansionAffectsAlpha = expansionAffectsAlpha; 1302 if (expansionAffectsAlpha) { 1303 applyAndDispatchState(); 1304 } 1305 } 1306 setKeyguardOccluded(boolean keyguardOccluded)1307 public void setKeyguardOccluded(boolean keyguardOccluded) { 1308 mKeyguardOccluded = keyguardOccluded; 1309 updateScrims(); 1310 } 1311 setHasBackdrop(boolean hasBackdrop)1312 public void setHasBackdrop(boolean hasBackdrop) { 1313 for (ScrimState state : ScrimState.values()) { 1314 state.setHasBackdrop(hasBackdrop); 1315 } 1316 1317 // Backdrop event may arrive after state was already applied, 1318 // in this case, back-scrim needs to be re-evaluated 1319 if (mState == ScrimState.AOD || mState == ScrimState.PULSING) { 1320 float newBehindAlpha = mState.getBehindAlpha(); 1321 if (isNaN(newBehindAlpha)) { 1322 throw new IllegalStateException("Scrim opacity is NaN for state: " + mState 1323 + ", back: " + mBehindAlpha); 1324 } 1325 if (mBehindAlpha != newBehindAlpha) { 1326 mBehindAlpha = newBehindAlpha; 1327 updateScrims(); 1328 } 1329 } 1330 } 1331 setKeyguardFadingAway(boolean fadingAway, long duration)1332 private void setKeyguardFadingAway(boolean fadingAway, long duration) { 1333 for (ScrimState state : ScrimState.values()) { 1334 state.setKeyguardFadingAway(fadingAway, duration); 1335 } 1336 } 1337 setLaunchingAffordanceWithPreview(boolean launchingAffordanceWithPreview)1338 public void setLaunchingAffordanceWithPreview(boolean launchingAffordanceWithPreview) { 1339 for (ScrimState state : ScrimState.values()) { 1340 state.setLaunchingAffordanceWithPreview(launchingAffordanceWithPreview); 1341 } 1342 } 1343 1344 public interface Callback { onStart()1345 default void onStart() { 1346 } 1347 onDisplayBlanked()1348 default void onDisplayBlanked() { 1349 } 1350 onFinished()1351 default void onFinished() { 1352 } 1353 onCancelled()1354 default void onCancelled() { 1355 } 1356 } 1357 1358 /** 1359 * Simple keyguard callback that updates scrims when keyguard visibility changes. 1360 */ 1361 private class KeyguardVisibilityCallback extends KeyguardUpdateMonitorCallback { 1362 1363 @Override onKeyguardVisibilityChanged(boolean showing)1364 public void onKeyguardVisibilityChanged(boolean showing) { 1365 mNeedsDrawableColorUpdate = true; 1366 scheduleUpdate(); 1367 } 1368 } 1369 } 1370