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.graphics.drawable.Drawable; 28 import android.os.Handler; 29 import android.os.Trace; 30 import android.util.Log; 31 import android.util.MathUtils; 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 com.android.internal.annotations.VisibleForTesting; 38 import com.android.internal.colorextraction.ColorExtractor; 39 import com.android.internal.colorextraction.ColorExtractor.GradientColors; 40 import com.android.internal.colorextraction.ColorExtractor.OnColorsChangedListener; 41 import com.android.internal.graphics.ColorUtils; 42 import com.android.internal.util.function.TriConsumer; 43 import com.android.keyguard.KeyguardUpdateMonitor; 44 import com.android.keyguard.KeyguardUpdateMonitorCallback; 45 import com.android.systemui.DejankUtils; 46 import com.android.systemui.Dumpable; 47 import com.android.systemui.R; 48 import com.android.systemui.colorextraction.SysuiColorExtractor; 49 import com.android.systemui.dock.DockManager; 50 import com.android.systemui.statusbar.BlurUtils; 51 import com.android.systemui.statusbar.ScrimView; 52 import com.android.systemui.statusbar.notification.stack.ViewState; 53 import com.android.systemui.statusbar.policy.KeyguardStateController; 54 import com.android.systemui.util.AlarmTimeout; 55 import com.android.systemui.util.wakelock.DelayedWakeLock; 56 import com.android.systemui.util.wakelock.WakeLock; 57 58 import java.io.FileDescriptor; 59 import java.io.PrintWriter; 60 import java.lang.annotation.Retention; 61 import java.lang.annotation.RetentionPolicy; 62 import java.util.function.Consumer; 63 64 import javax.inject.Inject; 65 import javax.inject.Singleton; 66 67 /** 68 * Controls both the scrim behind the notifications and in front of the notifications (when a 69 * security method gets shown). 70 */ 71 @Singleton 72 public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnColorsChangedListener, 73 Dumpable { 74 75 static final String TAG = "ScrimController"; 76 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 77 78 /** 79 * General scrim animation duration. 80 */ 81 public static final long ANIMATION_DURATION = 220; 82 /** 83 * Longer duration, currently only used when going to AOD. 84 */ 85 public static final long ANIMATION_DURATION_LONG = 1000; 86 /** 87 * When both scrims have 0 alpha. 88 */ 89 public static final int TRANSPARENT = 0; 90 /** 91 * When scrims aren't transparent (alpha 0) but also not opaque (alpha 1.) 92 */ 93 public static final int SEMI_TRANSPARENT = 1; 94 /** 95 * When at least 1 scrim is fully opaque (alpha set to 1.) 96 */ 97 public static final int OPAQUE = 2; 98 99 @IntDef(prefix = {"VISIBILITY_"}, value = { 100 TRANSPARENT, 101 SEMI_TRANSPARENT, 102 OPAQUE 103 }) 104 @Retention(RetentionPolicy.SOURCE) 105 public @interface ScrimVisibility { 106 } 107 108 /** 109 * Default alpha value for most scrims. 110 */ 111 protected static final float KEYGUARD_SCRIM_ALPHA = 0.2f; 112 /** 113 * Scrim opacity when the phone is about to wake-up. 114 */ 115 public static final float WAKE_SENSOR_SCRIM_ALPHA = 0.6f; 116 117 /** 118 * Scrim opacity when bubbles are expanded. 119 */ 120 public static final float BUBBLE_SCRIM_ALPHA = 0.6f; 121 122 /** 123 * The default scrim under the shade and dialogs. 124 * This should not be lower than 0.54, otherwise we won't pass GAR. 125 */ 126 public static final float BUSY_SCRIM_ALPHA = 0.85f; 127 128 /** 129 * Same as above, but when blur is supported. 130 */ 131 public static final float BLUR_SCRIM_ALPHA = 0.54f; 132 133 static final int TAG_KEY_ANIM = R.id.scrim; 134 private static final int TAG_START_ALPHA = R.id.scrim_alpha_start; 135 private static final int TAG_END_ALPHA = R.id.scrim_alpha_end; 136 private static final float NOT_INITIALIZED = -1; 137 138 private ScrimState mState = ScrimState.UNINITIALIZED; 139 140 private ScrimView mScrimInFront; 141 private ScrimView mScrimBehind; 142 private ScrimView mScrimForBubble; 143 144 private final KeyguardStateController mKeyguardStateController; 145 private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; 146 private final DozeParameters mDozeParameters; 147 private final DockManager mDockManager; 148 private final AlarmTimeout mTimeTicker; 149 private final KeyguardVisibilityCallback mKeyguardVisibilityCallback; 150 private final Handler mHandler; 151 152 private final SysuiColorExtractor mColorExtractor; 153 private GradientColors mColors; 154 private boolean mNeedsDrawableColorUpdate; 155 156 private float mScrimBehindAlphaKeyguard = KEYGUARD_SCRIM_ALPHA; 157 private final float mDefaultScrimAlpha; 158 159 // Assuming the shade is expanded during initialization 160 private float mExpansionFraction = 1f; 161 162 private boolean mDarkenWhileDragging; 163 private boolean mExpansionAffectsAlpha = true; 164 private boolean mAnimateChange; 165 private boolean mUpdatePending; 166 private boolean mTracking; 167 private long mAnimationDuration = -1; 168 private long mAnimationDelay; 169 private Animator.AnimatorListener mAnimatorListener; 170 private final Interpolator mInterpolator = new DecelerateInterpolator(); 171 172 private float mInFrontAlpha = NOT_INITIALIZED; 173 private float mBehindAlpha = NOT_INITIALIZED; 174 private float mBubbleAlpha = NOT_INITIALIZED; 175 176 private int mInFrontTint; 177 private int mBehindTint; 178 private int mBubbleTint; 179 180 private boolean mWallpaperVisibilityTimedOut; 181 private int mScrimsVisibility; 182 private final TriConsumer<ScrimState, Float, GradientColors> mScrimStateListener; 183 private Consumer<Integer> mScrimVisibleListener; 184 private boolean mBlankScreen; 185 private boolean mScreenBlankingCallbackCalled; 186 private Callback mCallback; 187 private boolean mWallpaperSupportsAmbientMode; 188 private boolean mScreenOn; 189 190 // Scrim blanking callbacks 191 private Runnable mPendingFrameCallback; 192 private Runnable mBlankingTransitionRunnable; 193 194 private final WakeLock mWakeLock; 195 private boolean mWakeLockHeld; 196 private boolean mKeyguardOccluded; 197 198 @Inject ScrimController(LightBarController lightBarController, DozeParameters dozeParameters, AlarmManager alarmManager, KeyguardStateController keyguardStateController, DelayedWakeLock.Builder delayedWakeLockBuilder, Handler handler, KeyguardUpdateMonitor keyguardUpdateMonitor, SysuiColorExtractor sysuiColorExtractor, DockManager dockManager, BlurUtils blurUtils)199 public ScrimController(LightBarController lightBarController, DozeParameters dozeParameters, 200 AlarmManager alarmManager, KeyguardStateController keyguardStateController, 201 DelayedWakeLock.Builder delayedWakeLockBuilder, Handler handler, 202 KeyguardUpdateMonitor keyguardUpdateMonitor, SysuiColorExtractor sysuiColorExtractor, 203 DockManager dockManager, BlurUtils blurUtils) { 204 205 mScrimStateListener = lightBarController::setScrimState; 206 mDefaultScrimAlpha = blurUtils.supportsBlursOnWindows() 207 ? BLUR_SCRIM_ALPHA : BUSY_SCRIM_ALPHA; 208 209 mKeyguardStateController = keyguardStateController; 210 mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen(); 211 mKeyguardUpdateMonitor = keyguardUpdateMonitor; 212 mKeyguardVisibilityCallback = new KeyguardVisibilityCallback(); 213 mHandler = handler; 214 mTimeTicker = new AlarmTimeout(alarmManager, this::onHideWallpaperTimeout, 215 "hide_aod_wallpaper", mHandler); 216 mWakeLock = delayedWakeLockBuilder.setHandler(mHandler).setTag("Scrims").build(); 217 // Scrim alpha is initially set to the value on the resource but might be changed 218 // to make sure that text on top of it is legible. 219 mDozeParameters = dozeParameters; 220 mDockManager = dockManager; 221 keyguardStateController.addCallback(new KeyguardStateController.Callback() { 222 @Override 223 public void onKeyguardFadingAwayChanged() { 224 setKeyguardFadingAway(keyguardStateController.isKeyguardFadingAway(), 225 keyguardStateController.getKeyguardFadingAwayDuration()); 226 } 227 }); 228 229 mColorExtractor = sysuiColorExtractor; 230 mColorExtractor.addOnColorsChangedListener(this); 231 mColors = mColorExtractor.getNeutralColors(); 232 mNeedsDrawableColorUpdate = true; 233 } 234 235 /** 236 * Attach the controller to the supplied views. 237 */ attachViews( ScrimView scrimBehind, ScrimView scrimInFront, ScrimView scrimForBubble)238 public void attachViews( 239 ScrimView scrimBehind, ScrimView scrimInFront, ScrimView scrimForBubble) { 240 mScrimBehind = scrimBehind; 241 mScrimInFront = scrimInFront; 242 mScrimForBubble = scrimForBubble; 243 244 final ScrimState[] states = ScrimState.values(); 245 for (int i = 0; i < states.length; i++) { 246 states[i].init(mScrimInFront, mScrimBehind, mScrimForBubble, mDozeParameters, 247 mDockManager); 248 states[i].setScrimBehindAlphaKeyguard(mScrimBehindAlphaKeyguard); 249 states[i].setDefaultScrimAlpha(mDefaultScrimAlpha); 250 } 251 252 mScrimBehind.setDefaultFocusHighlightEnabled(false); 253 mScrimInFront.setDefaultFocusHighlightEnabled(false); 254 mScrimForBubble.setDefaultFocusHighlightEnabled(false); 255 updateScrims(); 256 mKeyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback); 257 } 258 setScrimVisibleListener(Consumer<Integer> listener)259 void setScrimVisibleListener(Consumer<Integer> listener) { 260 mScrimVisibleListener = listener; 261 } 262 transitionTo(ScrimState state)263 public void transitionTo(ScrimState state) { 264 transitionTo(state, null); 265 } 266 transitionTo(ScrimState state, Callback callback)267 public void transitionTo(ScrimState state, Callback callback) { 268 if (state == mState) { 269 // Call the callback anyway, unless it's already enqueued 270 if (callback != null && mCallback != callback) { 271 callback.onFinished(); 272 } 273 return; 274 } else if (DEBUG) { 275 Log.d(TAG, "State changed to: " + state); 276 } 277 278 if (state == ScrimState.UNINITIALIZED) { 279 throw new IllegalArgumentException("Cannot change to UNINITIALIZED."); 280 } 281 282 final ScrimState oldState = mState; 283 mState = state; 284 Trace.traceCounter(Trace.TRACE_TAG_APP, "scrim_state", mState.ordinal()); 285 286 if (mCallback != null) { 287 mCallback.onCancelled(); 288 } 289 mCallback = callback; 290 291 state.prepare(oldState); 292 mScreenBlankingCallbackCalled = false; 293 mAnimationDelay = 0; 294 mBlankScreen = state.getBlanksScreen(); 295 mAnimateChange = state.getAnimateChange(); 296 mAnimationDuration = state.getAnimationDuration(); 297 298 mInFrontTint = state.getFrontTint(); 299 mBehindTint = state.getBehindTint(); 300 mBubbleTint = state.getBubbleTint(); 301 302 mInFrontAlpha = state.getFrontAlpha(); 303 mBehindAlpha = state.getBehindAlpha(); 304 mBubbleAlpha = state.getBubbleAlpha(); 305 if (isNaN(mBehindAlpha) || isNaN(mInFrontAlpha)) { 306 throw new IllegalStateException("Scrim opacity is NaN for state: " + state + ", front: " 307 + mInFrontAlpha + ", back: " + mBehindAlpha); 308 } 309 applyExpansionToAlpha(); 310 311 // Scrim might acquire focus when user is navigating with a D-pad or a keyboard. 312 // We need to disable focus otherwise AOD would end up with a gray overlay. 313 mScrimInFront.setFocusable(!state.isLowPowerState()); 314 mScrimBehind.setFocusable(!state.isLowPowerState()); 315 316 // Cancel blanking transitions that were pending before we requested a new state 317 if (mPendingFrameCallback != null) { 318 mScrimBehind.removeCallbacks(mPendingFrameCallback); 319 mPendingFrameCallback = null; 320 } 321 if (mHandler.hasCallbacks(mBlankingTransitionRunnable)) { 322 mHandler.removeCallbacks(mBlankingTransitionRunnable); 323 mBlankingTransitionRunnable = null; 324 } 325 326 // Showing/hiding the keyguard means that scrim colors have to be switched, not necessary 327 // to do the same when you're just showing the brightness mirror. 328 mNeedsDrawableColorUpdate = state != ScrimState.BRIGHTNESS_MIRROR; 329 330 // The device might sleep if it's entering AOD, we need to make sure that 331 // the animation plays properly until the last frame. 332 // It's important to avoid holding the wakelock unless necessary because 333 // WakeLock#aqcuire will trigger an IPC and will cause jank. 334 if (mState.isLowPowerState()) { 335 holdWakeLock(); 336 } 337 338 // AOD wallpapers should fade away after a while. 339 // Docking pulses may take a long time, wallpapers should also fade away after a while. 340 mWallpaperVisibilityTimedOut = false; 341 if (shouldFadeAwayWallpaper()) { 342 DejankUtils.postAfterTraversal(() -> { 343 mTimeTicker.schedule(mDozeParameters.getWallpaperAodDuration(), 344 AlarmTimeout.MODE_IGNORE_IF_SCHEDULED); 345 }); 346 } else { 347 DejankUtils.postAfterTraversal(mTimeTicker::cancel); 348 } 349 350 if (mKeyguardUpdateMonitor.needsSlowUnlockTransition() && mState == ScrimState.UNLOCKED) { 351 // In case the user isn't unlocked, make sure to delay a bit because the system is hosed 352 // with too many things at this case, in order to not skip the initial frames. 353 mScrimInFront.postOnAnimationDelayed(this::scheduleUpdate, 16); 354 mAnimationDelay = StatusBar.FADE_KEYGUARD_START_DELAY; 355 } else if ((!mDozeParameters.getAlwaysOn() && oldState == ScrimState.AOD) 356 || (mState == ScrimState.AOD && !mDozeParameters.getDisplayNeedsBlanking())) { 357 // Scheduling a frame isn't enough when: 358 // • Leaving doze and we need to modify scrim color immediately 359 // • ColorFade will not kick-in and scrim cannot wait for pre-draw. 360 onPreDraw(); 361 } else { 362 scheduleUpdate(); 363 } 364 365 dispatchScrimState(mScrimBehind.getViewAlpha()); 366 } 367 shouldFadeAwayWallpaper()368 private boolean shouldFadeAwayWallpaper() { 369 if (!mWallpaperSupportsAmbientMode) { 370 return false; 371 } 372 373 if (mState == ScrimState.AOD 374 && (mDozeParameters.getAlwaysOn() || mDockManager.isDocked())) { 375 return true; 376 } 377 378 return false; 379 } 380 getState()381 public ScrimState getState() { 382 return mState; 383 } 384 setScrimBehindValues(float scrimBehindAlphaKeyguard)385 protected void setScrimBehindValues(float scrimBehindAlphaKeyguard) { 386 mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard; 387 ScrimState[] states = ScrimState.values(); 388 for (int i = 0; i < states.length; i++) { 389 states[i].setScrimBehindAlphaKeyguard(scrimBehindAlphaKeyguard); 390 } 391 scheduleUpdate(); 392 } 393 onTrackingStarted()394 public void onTrackingStarted() { 395 mTracking = true; 396 mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen(); 397 } 398 onExpandingFinished()399 public void onExpandingFinished() { 400 mTracking = false; 401 } 402 403 @VisibleForTesting onHideWallpaperTimeout()404 protected void onHideWallpaperTimeout() { 405 if (mState != ScrimState.AOD && mState != ScrimState.PULSING) { 406 return; 407 } 408 409 holdWakeLock(); 410 mWallpaperVisibilityTimedOut = true; 411 mAnimateChange = true; 412 mAnimationDuration = mDozeParameters.getWallpaperFadeOutDuration(); 413 scheduleUpdate(); 414 } 415 holdWakeLock()416 private void holdWakeLock() { 417 if (!mWakeLockHeld) { 418 if (mWakeLock != null) { 419 mWakeLockHeld = true; 420 mWakeLock.acquire(TAG); 421 } else { 422 Log.w(TAG, "Cannot hold wake lock, it has not been set yet"); 423 } 424 } 425 } 426 427 /** 428 * Current state of the shade expansion when pulling it from the top. 429 * This value is 1 when on top of the keyguard and goes to 0 as the user drags up. 430 * 431 * The expansion fraction is tied to the scrim opacity. 432 * 433 * @param fraction From 0 to 1 where 0 means collapsed and 1 expanded. 434 */ setPanelExpansion(float fraction)435 public void setPanelExpansion(float fraction) { 436 if (isNaN(fraction)) { 437 throw new IllegalArgumentException("Fraction should not be NaN"); 438 } 439 if (mExpansionFraction != fraction) { 440 mExpansionFraction = fraction; 441 442 boolean relevantState = (mState == ScrimState.UNLOCKED 443 || mState == ScrimState.KEYGUARD 444 || mState == ScrimState.PULSING 445 || mState == ScrimState.BUBBLE_EXPANDED); 446 if (!(relevantState && mExpansionAffectsAlpha)) { 447 return; 448 } 449 applyAndDispatchExpansion(); 450 } 451 } 452 setOrAdaptCurrentAnimation(View scrim)453 private void setOrAdaptCurrentAnimation(View scrim) { 454 float alpha = getCurrentScrimAlpha(scrim); 455 if (isAnimating(scrim)) { 456 // Adapt current animation. 457 ValueAnimator previousAnimator = (ValueAnimator) scrim.getTag(TAG_KEY_ANIM); 458 float previousEndValue = (Float) scrim.getTag(TAG_END_ALPHA); 459 float previousStartValue = (Float) scrim.getTag(TAG_START_ALPHA); 460 float relativeDiff = alpha - previousEndValue; 461 float newStartValue = previousStartValue + relativeDiff; 462 scrim.setTag(TAG_START_ALPHA, newStartValue); 463 scrim.setTag(TAG_END_ALPHA, alpha); 464 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); 465 } else { 466 // Set animation. 467 updateScrimColor(scrim, alpha, getCurrentScrimTint(scrim)); 468 } 469 } 470 applyExpansionToAlpha()471 private void applyExpansionToAlpha() { 472 if (!mExpansionAffectsAlpha) { 473 return; 474 } 475 476 if (mState == ScrimState.UNLOCKED || mState == ScrimState.BUBBLE_EXPANDED) { 477 // Darken scrim as you pull down the shade when unlocked 478 float behindFraction = getInterpolatedFraction(); 479 behindFraction = (float) Math.pow(behindFraction, 0.8f); 480 mBehindAlpha = behindFraction * mDefaultScrimAlpha; 481 mInFrontAlpha = 0; 482 } else if (mState == ScrimState.KEYGUARD || mState == ScrimState.PULSING) { 483 // Either darken of make the scrim transparent when you 484 // pull down the shade 485 float interpolatedFract = getInterpolatedFraction(); 486 float alphaBehind = mState.getBehindAlpha(); 487 if (mDarkenWhileDragging) { 488 mBehindAlpha = MathUtils.lerp(mDefaultScrimAlpha, alphaBehind, 489 interpolatedFract); 490 mInFrontAlpha = mState.getFrontAlpha(); 491 } else { 492 mBehindAlpha = MathUtils.lerp(0 /* start */, alphaBehind, 493 interpolatedFract); 494 mInFrontAlpha = mState.getFrontAlpha(); 495 } 496 mBehindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getBehindTint(), 497 mState.getBehindTint(), interpolatedFract); 498 } 499 if (isNaN(mBehindAlpha) || isNaN(mInFrontAlpha)) { 500 throw new IllegalStateException("Scrim opacity is NaN for state: " + mState 501 + ", front: " + mInFrontAlpha + ", back: " + mBehindAlpha); 502 } 503 } 504 applyAndDispatchExpansion()505 private void applyAndDispatchExpansion() { 506 applyExpansionToAlpha(); 507 if (mUpdatePending) { 508 return; 509 } 510 setOrAdaptCurrentAnimation(mScrimBehind); 511 setOrAdaptCurrentAnimation(mScrimInFront); 512 setOrAdaptCurrentAnimation(mScrimForBubble); 513 dispatchScrimState(mScrimBehind.getViewAlpha()); 514 515 // Reset wallpaper timeout if it's already timeout like expanding panel while PULSING 516 // and docking. 517 if (mWallpaperVisibilityTimedOut) { 518 mWallpaperVisibilityTimedOut = false; 519 DejankUtils.postAfterTraversal(() -> { 520 mTimeTicker.schedule(mDozeParameters.getWallpaperAodDuration(), 521 AlarmTimeout.MODE_IGNORE_IF_SCHEDULED); 522 }); 523 } 524 } 525 526 /** 527 * Sets the given drawable as the background of the scrim that shows up behind the 528 * notifications. 529 */ setScrimBehindDrawable(Drawable drawable)530 public void setScrimBehindDrawable(Drawable drawable) { 531 mScrimBehind.setDrawable(drawable); 532 } 533 534 /** 535 * Sets the front scrim opacity in AOD so it's not as bright. 536 * <p> 537 * Displays usually don't support multiple dimming settings when in low power mode. 538 * The workaround is to modify the front scrim opacity when in AOD, so it's not as 539 * bright when you're at the movies or lying down on bed. 540 * <p> 541 * This value will be lost during transitions and only updated again after the the 542 * device is dozing when the light sensor is on. 543 */ setAodFrontScrimAlpha(float alpha)544 public void setAodFrontScrimAlpha(float alpha) { 545 if (mInFrontAlpha != alpha && shouldUpdateFrontScrimAlpha()) { 546 mInFrontAlpha = alpha; 547 updateScrims(); 548 } 549 550 mState.AOD.setAodFrontScrimAlpha(alpha); 551 mState.PULSING.setAodFrontScrimAlpha(alpha); 552 } 553 shouldUpdateFrontScrimAlpha()554 private boolean shouldUpdateFrontScrimAlpha() { 555 if (mState == ScrimState.AOD 556 && (mDozeParameters.getAlwaysOn() || mDockManager.isDocked())) { 557 return true; 558 } 559 560 if (mState == ScrimState.PULSING) { 561 return true; 562 } 563 564 return false; 565 } 566 567 /** 568 * If the lock screen sensor is active. 569 */ setWakeLockScreenSensorActive(boolean active)570 public void setWakeLockScreenSensorActive(boolean active) { 571 for (ScrimState state : ScrimState.values()) { 572 state.setWakeLockScreenSensorActive(active); 573 } 574 575 if (mState == ScrimState.PULSING) { 576 float newBehindAlpha = mState.getBehindAlpha(); 577 if (mBehindAlpha != newBehindAlpha) { 578 mBehindAlpha = newBehindAlpha; 579 if (isNaN(mBehindAlpha)) { 580 throw new IllegalStateException("Scrim opacity is NaN for state: " + mState 581 + ", back: " + mBehindAlpha); 582 } 583 updateScrims(); 584 } 585 } 586 } 587 scheduleUpdate()588 protected void scheduleUpdate() { 589 if (mUpdatePending || mScrimBehind == null) return; 590 591 // Make sure that a frame gets scheduled. 592 mScrimBehind.invalidate(); 593 mScrimBehind.getViewTreeObserver().addOnPreDrawListener(this); 594 mUpdatePending = true; 595 } 596 updateScrims()597 protected void updateScrims() { 598 // Make sure we have the right gradients and their opacities will satisfy GAR. 599 if (mNeedsDrawableColorUpdate) { 600 mNeedsDrawableColorUpdate = false; 601 // Only animate scrim color if the scrim view is actually visible 602 boolean animateScrimInFront = mScrimInFront.getViewAlpha() != 0 && !mBlankScreen; 603 boolean animateScrimBehind = mScrimBehind.getViewAlpha() != 0 && !mBlankScreen; 604 boolean animateScrimForBubble = mScrimForBubble.getViewAlpha() != 0 && !mBlankScreen; 605 606 mScrimInFront.setColors(mColors, animateScrimInFront); 607 mScrimBehind.setColors(mColors, animateScrimBehind); 608 mScrimForBubble.setColors(mColors, animateScrimForBubble); 609 610 // Calculate minimum scrim opacity for white or black text. 611 int textColor = mColors.supportsDarkText() ? Color.BLACK : Color.WHITE; 612 int mainColor = mColors.getMainColor(); 613 float minOpacity = ColorUtils.calculateMinimumBackgroundAlpha(textColor, mainColor, 614 4.5f /* minimumContrast */) / 255f; 615 dispatchScrimState(mScrimBehind.getViewAlpha()); 616 } 617 618 // We want to override the back scrim opacity for the AOD state 619 // when it's time to fade the wallpaper away. 620 boolean aodWallpaperTimeout = (mState == ScrimState.AOD || mState == ScrimState.PULSING) 621 && mWallpaperVisibilityTimedOut; 622 // We also want to hide FLAG_SHOW_WHEN_LOCKED activities under the scrim. 623 boolean occludedKeyguard = (mState == ScrimState.PULSING || mState == ScrimState.AOD) 624 && mKeyguardOccluded; 625 if (aodWallpaperTimeout || occludedKeyguard) { 626 mBehindAlpha = 1; 627 } 628 setScrimAlpha(mScrimInFront, mInFrontAlpha); 629 setScrimAlpha(mScrimBehind, mBehindAlpha); 630 setScrimAlpha(mScrimForBubble, mBubbleAlpha); 631 // The animation could have all already finished, let's call onFinished just in case 632 onFinished(); 633 dispatchScrimsVisible(); 634 } 635 dispatchScrimState(float alpha)636 private void dispatchScrimState(float alpha) { 637 mScrimStateListener.accept(mState, alpha, mScrimInFront.getColors()); 638 } 639 dispatchScrimsVisible()640 private void dispatchScrimsVisible() { 641 final int currentScrimVisibility; 642 if (mScrimInFront.getViewAlpha() == 1 || mScrimBehind.getViewAlpha() == 1) { 643 currentScrimVisibility = OPAQUE; 644 } else if (mScrimInFront.getViewAlpha() == 0 && mScrimBehind.getViewAlpha() == 0) { 645 currentScrimVisibility = TRANSPARENT; 646 } else { 647 currentScrimVisibility = SEMI_TRANSPARENT; 648 } 649 650 if (mScrimsVisibility != currentScrimVisibility) { 651 mScrimsVisibility = currentScrimVisibility; 652 mScrimVisibleListener.accept(currentScrimVisibility); 653 } 654 } 655 getInterpolatedFraction()656 private float getInterpolatedFraction() { 657 float frac = mExpansionFraction; 658 // let's start this 20% of the way down the screen 659 frac = frac * 1.2f - 0.2f; 660 if (frac <= 0) { 661 return 0; 662 } else { 663 // woo, special effects 664 return (float) (1f - 0.5f * (1f - Math.cos(3.14159f * Math.pow(1f - frac, 2f)))); 665 } 666 } 667 setScrimAlpha(ScrimView scrim, float alpha)668 private void setScrimAlpha(ScrimView scrim, float alpha) { 669 if (alpha == 0f) { 670 scrim.setClickable(false); 671 } else { 672 // Eat touch events (unless dozing). 673 scrim.setClickable(mState != ScrimState.AOD); 674 } 675 updateScrim(scrim, alpha); 676 } 677 getScrimName(ScrimView scrim)678 private String getScrimName(ScrimView scrim) { 679 if (scrim == mScrimInFront) { 680 return "front_scrim"; 681 } else if (scrim == mScrimBehind) { 682 return "back_scrim"; 683 } else if (scrim == mScrimForBubble) { 684 return "bubble_scrim"; 685 } 686 return "unknown_scrim"; 687 } 688 updateScrimColor(View scrim, float alpha, int tint)689 private void updateScrimColor(View scrim, float alpha, int tint) { 690 alpha = Math.max(0, Math.min(1.0f, alpha)); 691 if (scrim instanceof ScrimView) { 692 ScrimView scrimView = (ScrimView) scrim; 693 694 Trace.traceCounter(Trace.TRACE_TAG_APP, getScrimName(scrimView) + "_alpha", 695 (int) (alpha * 255)); 696 697 Trace.traceCounter(Trace.TRACE_TAG_APP, getScrimName(scrimView) + "_tint", 698 Color.alpha(tint)); 699 700 scrimView.setTint(tint); 701 scrimView.setViewAlpha(alpha); 702 } else { 703 scrim.setAlpha(alpha); 704 } 705 dispatchScrimsVisible(); 706 } 707 startScrimAnimation(final View scrim, float current)708 private void startScrimAnimation(final View scrim, float current) { 709 ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f); 710 if (mAnimatorListener != null) { 711 anim.addListener(mAnimatorListener); 712 } 713 final int initialScrimTint = scrim instanceof ScrimView ? ((ScrimView) scrim).getTint() : 714 Color.TRANSPARENT; 715 anim.addUpdateListener(animation -> { 716 final float startAlpha = (Float) scrim.getTag(TAG_START_ALPHA); 717 final float animAmount = (float) animation.getAnimatedValue(); 718 final int finalScrimTint = getCurrentScrimTint(scrim); 719 final float finalScrimAlpha = getCurrentScrimAlpha(scrim); 720 float alpha = MathUtils.lerp(startAlpha, finalScrimAlpha, animAmount); 721 alpha = MathUtils.constrain(alpha, 0f, 1f); 722 int tint = ColorUtils.blendARGB(initialScrimTint, finalScrimTint, animAmount); 723 updateScrimColor(scrim, alpha, tint); 724 dispatchScrimsVisible(); 725 }); 726 anim.setInterpolator(mInterpolator); 727 anim.setStartDelay(mAnimationDelay); 728 anim.setDuration(mAnimationDuration); 729 anim.addListener(new AnimatorListenerAdapter() { 730 private Callback lastCallback = mCallback; 731 732 @Override 733 public void onAnimationEnd(Animator animation) { 734 scrim.setTag(TAG_KEY_ANIM, null); 735 onFinished(lastCallback); 736 737 dispatchScrimsVisible(); 738 } 739 }); 740 741 // Cache alpha values because we might want to update this animator in the future if 742 // the user expands the panel while the animation is still running. 743 scrim.setTag(TAG_START_ALPHA, current); 744 scrim.setTag(TAG_END_ALPHA, getCurrentScrimAlpha(scrim)); 745 746 scrim.setTag(TAG_KEY_ANIM, anim); 747 anim.start(); 748 } 749 getCurrentScrimAlpha(View scrim)750 private float getCurrentScrimAlpha(View scrim) { 751 if (scrim == mScrimInFront) { 752 return mInFrontAlpha; 753 } else if (scrim == mScrimBehind) { 754 return mBehindAlpha; 755 } else if (scrim == mScrimForBubble) { 756 return mBubbleAlpha; 757 } else { 758 throw new IllegalArgumentException("Unknown scrim view"); 759 } 760 } 761 getCurrentScrimTint(View scrim)762 private int getCurrentScrimTint(View scrim) { 763 if (scrim == mScrimInFront) { 764 return mInFrontTint; 765 } else if (scrim == mScrimBehind) { 766 return mBehindTint; 767 } else if (scrim == mScrimForBubble) { 768 return mBubbleTint; 769 } else { 770 throw new IllegalArgumentException("Unknown scrim view"); 771 } 772 } 773 774 @Override onPreDraw()775 public boolean onPreDraw() { 776 mScrimBehind.getViewTreeObserver().removeOnPreDrawListener(this); 777 mUpdatePending = false; 778 if (mCallback != null) { 779 mCallback.onStart(); 780 } 781 updateScrims(); 782 return true; 783 } 784 onFinished()785 private void onFinished() { 786 onFinished(mCallback); 787 } 788 onFinished(Callback callback)789 private void onFinished(Callback callback) { 790 if (mPendingFrameCallback != null) { 791 // No animations can finish while we're waiting on the blanking to finish 792 return; 793 794 } 795 if (isAnimating(mScrimBehind) 796 || isAnimating(mScrimInFront) 797 || isAnimating(mScrimForBubble)) { 798 if (callback != null && callback != mCallback) { 799 // Since we only notify the callback that we're finished once everything has 800 // finished, we need to make sure that any changing callbacks are also invoked 801 callback.onFinished(); 802 } 803 return; 804 } 805 if (mWakeLockHeld) { 806 mWakeLock.release(TAG); 807 mWakeLockHeld = false; 808 } 809 810 if (callback != null) { 811 callback.onFinished(); 812 813 if (callback == mCallback) { 814 mCallback = null; 815 } 816 } 817 818 // When unlocking with fingerprint, we'll fade the scrims from black to transparent. 819 // At the end of the animation we need to remove the tint. 820 if (mState == ScrimState.UNLOCKED) { 821 mInFrontTint = Color.TRANSPARENT; 822 mBehindTint = Color.TRANSPARENT; 823 mBubbleTint = Color.TRANSPARENT; 824 updateScrimColor(mScrimInFront, mInFrontAlpha, mInFrontTint); 825 updateScrimColor(mScrimBehind, mBehindAlpha, mBehindTint); 826 updateScrimColor(mScrimForBubble, mBubbleAlpha, mBubbleTint); 827 } 828 } 829 isAnimating(View scrim)830 private boolean isAnimating(View scrim) { 831 return scrim.getTag(TAG_KEY_ANIM) != null; 832 } 833 834 @VisibleForTesting setAnimatorListener(Animator.AnimatorListener animatorListener)835 void setAnimatorListener(Animator.AnimatorListener animatorListener) { 836 mAnimatorListener = animatorListener; 837 } 838 updateScrim(ScrimView scrim, float alpha)839 private void updateScrim(ScrimView scrim, float alpha) { 840 final float currentAlpha = scrim.getViewAlpha(); 841 842 ValueAnimator previousAnimator = ViewState.getChildTag(scrim, TAG_KEY_ANIM); 843 if (previousAnimator != null) { 844 // Previous animators should always be cancelled. Not doing so would cause 845 // overlap, especially on states that don't animate, leading to flickering, 846 // and in the worst case, an internal state that doesn't represent what 847 // transitionTo requested. 848 cancelAnimator(previousAnimator); 849 } 850 851 if (mPendingFrameCallback != null) { 852 // Display is off and we're waiting. 853 return; 854 } else if (mBlankScreen) { 855 // Need to blank the display before continuing. 856 blankDisplay(); 857 return; 858 } else if (!mScreenBlankingCallbackCalled) { 859 // Not blanking the screen. Letting the callback know that we're ready 860 // to replace what was on the screen before. 861 if (mCallback != null) { 862 mCallback.onDisplayBlanked(); 863 mScreenBlankingCallbackCalled = true; 864 } 865 } 866 867 if (scrim == mScrimBehind) { 868 dispatchScrimState(alpha); 869 } 870 871 final boolean wantsAlphaUpdate = alpha != currentAlpha; 872 final boolean wantsTintUpdate = scrim.getTint() != getCurrentScrimTint(scrim); 873 874 if (wantsAlphaUpdate || wantsTintUpdate) { 875 if (mAnimateChange) { 876 startScrimAnimation(scrim, currentAlpha); 877 } else { 878 // update the alpha directly 879 updateScrimColor(scrim, alpha, getCurrentScrimTint(scrim)); 880 } 881 } 882 } 883 cancelAnimator(ValueAnimator previousAnimator)884 private void cancelAnimator(ValueAnimator previousAnimator) { 885 if (previousAnimator != null) { 886 previousAnimator.cancel(); 887 } 888 } 889 blankDisplay()890 private void blankDisplay() { 891 updateScrimColor(mScrimInFront, 1, Color.BLACK); 892 893 // Notify callback that the screen is completely black and we're 894 // ready to change the display power mode 895 mPendingFrameCallback = () -> { 896 if (mCallback != null) { 897 mCallback.onDisplayBlanked(); 898 mScreenBlankingCallbackCalled = true; 899 } 900 901 mBlankingTransitionRunnable = () -> { 902 mBlankingTransitionRunnable = null; 903 mPendingFrameCallback = null; 904 mBlankScreen = false; 905 // Try again. 906 updateScrims(); 907 }; 908 909 // Setting power states can happen after we push out the frame. Make sure we 910 // stay fully opaque until the power state request reaches the lower levels. 911 final int delay = mScreenOn ? 32 : 500; 912 if (DEBUG) { 913 Log.d(TAG, "Fading out scrims with delay: " + delay); 914 } 915 mHandler.postDelayed(mBlankingTransitionRunnable, delay); 916 }; 917 doOnTheNextFrame(mPendingFrameCallback); 918 } 919 920 /** 921 * Executes a callback after the frame has hit the display. 922 * 923 * @param callback What to run. 924 */ 925 @VisibleForTesting doOnTheNextFrame(Runnable callback)926 protected void doOnTheNextFrame(Runnable callback) { 927 // Just calling View#postOnAnimation isn't enough because the frame might not have reached 928 // the display yet. A timeout is the safest solution. 929 mScrimBehind.postOnAnimationDelayed(callback, 32 /* delayMillis */); 930 } 931 getBackgroundColor()932 public int getBackgroundColor() { 933 int color = mColors.getMainColor(); 934 return Color.argb((int) (mScrimBehind.getViewAlpha() * Color.alpha(color)), 935 Color.red(color), Color.green(color), Color.blue(color)); 936 } 937 setScrimBehindChangeRunnable(Runnable changeRunnable)938 public void setScrimBehindChangeRunnable(Runnable changeRunnable) { 939 mScrimBehind.setChangeRunnable(changeRunnable); 940 } 941 setCurrentUser(int currentUser)942 public void setCurrentUser(int currentUser) { 943 // Don't care in the base class. 944 } 945 946 @Override onColorsChanged(ColorExtractor colorExtractor, int which)947 public void onColorsChanged(ColorExtractor colorExtractor, int which) { 948 mColors = mColorExtractor.getNeutralColors(); 949 mNeedsDrawableColorUpdate = true; 950 scheduleUpdate(); 951 } 952 953 @Override dump(FileDescriptor fd, PrintWriter pw, String[] args)954 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 955 pw.println(" ScrimController: "); 956 pw.print(" state: "); 957 pw.println(mState); 958 959 pw.print(" frontScrim:"); 960 pw.print(" viewAlpha="); 961 pw.print(mScrimInFront.getViewAlpha()); 962 pw.print(" alpha="); 963 pw.print(mInFrontAlpha); 964 pw.print(" tint=0x"); 965 pw.println(Integer.toHexString(mScrimInFront.getTint())); 966 967 pw.print(" backScrim:"); 968 pw.print(" viewAlpha="); 969 pw.print(mScrimBehind.getViewAlpha()); 970 pw.print(" alpha="); 971 pw.print(mBehindAlpha); 972 pw.print(" tint=0x"); 973 pw.println(Integer.toHexString(mScrimBehind.getTint())); 974 975 pw.print(" bubbleScrim:"); 976 pw.print(" viewAlpha="); 977 pw.print(mScrimForBubble.getViewAlpha()); 978 pw.print(" alpha="); 979 pw.print(mBubbleAlpha); 980 pw.print(" tint=0x"); 981 pw.println(Integer.toHexString(mScrimForBubble.getTint())); 982 983 pw.print(" mTracking="); 984 pw.println(mTracking); 985 pw.print(" mDefaultScrimAlpha="); 986 pw.println(mDefaultScrimAlpha); 987 pw.print(" mExpansionFraction="); 988 pw.println(mExpansionFraction); 989 } 990 setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode)991 public void setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode) { 992 mWallpaperSupportsAmbientMode = wallpaperSupportsAmbientMode; 993 ScrimState[] states = ScrimState.values(); 994 for (int i = 0; i < states.length; i++) { 995 states[i].setWallpaperSupportsAmbientMode(wallpaperSupportsAmbientMode); 996 } 997 } 998 999 /** 1000 * Interrupts blanking transitions once the display notifies that it's already on. 1001 */ onScreenTurnedOn()1002 public void onScreenTurnedOn() { 1003 mScreenOn = true; 1004 if (mHandler.hasCallbacks(mBlankingTransitionRunnable)) { 1005 if (DEBUG) { 1006 Log.d(TAG, "Shorter blanking because screen turned on. All good."); 1007 } 1008 mHandler.removeCallbacks(mBlankingTransitionRunnable); 1009 mBlankingTransitionRunnable.run(); 1010 } 1011 } 1012 onScreenTurnedOff()1013 public void onScreenTurnedOff() { 1014 mScreenOn = false; 1015 } 1016 setExpansionAffectsAlpha(boolean expansionAffectsAlpha)1017 public void setExpansionAffectsAlpha(boolean expansionAffectsAlpha) { 1018 mExpansionAffectsAlpha = expansionAffectsAlpha; 1019 if (expansionAffectsAlpha) { 1020 applyAndDispatchExpansion(); 1021 } 1022 } 1023 setKeyguardOccluded(boolean keyguardOccluded)1024 public void setKeyguardOccluded(boolean keyguardOccluded) { 1025 mKeyguardOccluded = keyguardOccluded; 1026 updateScrims(); 1027 } 1028 setHasBackdrop(boolean hasBackdrop)1029 public void setHasBackdrop(boolean hasBackdrop) { 1030 for (ScrimState state : ScrimState.values()) { 1031 state.setHasBackdrop(hasBackdrop); 1032 } 1033 1034 // Backdrop event may arrive after state was already applied, 1035 // in this case, back-scrim needs to be re-evaluated 1036 if (mState == ScrimState.AOD || mState == ScrimState.PULSING) { 1037 float newBehindAlpha = mState.getBehindAlpha(); 1038 if (isNaN(newBehindAlpha)) { 1039 throw new IllegalStateException("Scrim opacity is NaN for state: " + mState 1040 + ", back: " + mBehindAlpha); 1041 } 1042 if (mBehindAlpha != newBehindAlpha) { 1043 mBehindAlpha = newBehindAlpha; 1044 updateScrims(); 1045 } 1046 } 1047 } 1048 setKeyguardFadingAway(boolean fadingAway, long duration)1049 private void setKeyguardFadingAway(boolean fadingAway, long duration) { 1050 for (ScrimState state : ScrimState.values()) { 1051 state.setKeyguardFadingAway(fadingAway, duration); 1052 } 1053 } 1054 setLaunchingAffordanceWithPreview(boolean launchingAffordanceWithPreview)1055 public void setLaunchingAffordanceWithPreview(boolean launchingAffordanceWithPreview) { 1056 for (ScrimState state : ScrimState.values()) { 1057 state.setLaunchingAffordanceWithPreview(launchingAffordanceWithPreview); 1058 } 1059 } 1060 1061 public interface Callback { onStart()1062 default void onStart() { 1063 } 1064 onDisplayBlanked()1065 default void onDisplayBlanked() { 1066 } 1067 onFinished()1068 default void onFinished() { 1069 } 1070 onCancelled()1071 default void onCancelled() { 1072 } 1073 } 1074 1075 /** 1076 * Simple keyguard callback that updates scrims when keyguard visibility changes. 1077 */ 1078 private class KeyguardVisibilityCallback extends KeyguardUpdateMonitorCallback { 1079 1080 @Override onKeyguardVisibilityChanged(boolean showing)1081 public void onKeyguardVisibilityChanged(boolean showing) { 1082 mNeedsDrawableColorUpdate = true; 1083 scheduleUpdate(); 1084 } 1085 } 1086 } 1087