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 mAnimationDelay = StatusBar.FADE_KEYGUARD_START_DELAY; 352 scheduleUpdate(); 353 } else if ((!mDozeParameters.getAlwaysOn() && oldState == ScrimState.AOD) 354 || (mState == ScrimState.AOD && !mDozeParameters.getDisplayNeedsBlanking())) { 355 // Scheduling a frame isn't enough when: 356 // • Leaving doze and we need to modify scrim color immediately 357 // • ColorFade will not kick-in and scrim cannot wait for pre-draw. 358 onPreDraw(); 359 } else { 360 scheduleUpdate(); 361 } 362 363 dispatchScrimState(mScrimBehind.getViewAlpha()); 364 } 365 shouldFadeAwayWallpaper()366 private boolean shouldFadeAwayWallpaper() { 367 if (!mWallpaperSupportsAmbientMode) { 368 return false; 369 } 370 371 if (mState == ScrimState.AOD 372 && (mDozeParameters.getAlwaysOn() || mDockManager.isDocked())) { 373 return true; 374 } 375 376 return false; 377 } 378 getState()379 public ScrimState getState() { 380 return mState; 381 } 382 setScrimBehindValues(float scrimBehindAlphaKeyguard)383 protected void setScrimBehindValues(float scrimBehindAlphaKeyguard) { 384 mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard; 385 ScrimState[] states = ScrimState.values(); 386 for (int i = 0; i < states.length; i++) { 387 states[i].setScrimBehindAlphaKeyguard(scrimBehindAlphaKeyguard); 388 } 389 scheduleUpdate(); 390 } 391 onTrackingStarted()392 public void onTrackingStarted() { 393 mTracking = true; 394 mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen(); 395 } 396 onExpandingFinished()397 public void onExpandingFinished() { 398 mTracking = false; 399 } 400 401 @VisibleForTesting onHideWallpaperTimeout()402 protected void onHideWallpaperTimeout() { 403 if (mState != ScrimState.AOD && mState != ScrimState.PULSING) { 404 return; 405 } 406 407 holdWakeLock(); 408 mWallpaperVisibilityTimedOut = true; 409 mAnimateChange = true; 410 mAnimationDuration = mDozeParameters.getWallpaperFadeOutDuration(); 411 scheduleUpdate(); 412 } 413 holdWakeLock()414 private void holdWakeLock() { 415 if (!mWakeLockHeld) { 416 if (mWakeLock != null) { 417 mWakeLockHeld = true; 418 mWakeLock.acquire(TAG); 419 } else { 420 Log.w(TAG, "Cannot hold wake lock, it has not been set yet"); 421 } 422 } 423 } 424 425 /** 426 * Current state of the shade expansion when pulling it from the top. 427 * This value is 1 when on top of the keyguard and goes to 0 as the user drags up. 428 * 429 * The expansion fraction is tied to the scrim opacity. 430 * 431 * @param fraction From 0 to 1 where 0 means collapsed and 1 expanded. 432 */ setPanelExpansion(float fraction)433 public void setPanelExpansion(float fraction) { 434 if (isNaN(fraction)) { 435 throw new IllegalArgumentException("Fraction should not be NaN"); 436 } 437 if (mExpansionFraction != fraction) { 438 mExpansionFraction = fraction; 439 440 boolean relevantState = (mState == ScrimState.UNLOCKED 441 || mState == ScrimState.KEYGUARD 442 || mState == ScrimState.PULSING 443 || mState == ScrimState.BUBBLE_EXPANDED); 444 if (!(relevantState && mExpansionAffectsAlpha)) { 445 return; 446 } 447 applyAndDispatchExpansion(); 448 } 449 } 450 setOrAdaptCurrentAnimation(View scrim)451 private void setOrAdaptCurrentAnimation(View scrim) { 452 float alpha = getCurrentScrimAlpha(scrim); 453 if (isAnimating(scrim)) { 454 // Adapt current animation. 455 ValueAnimator previousAnimator = (ValueAnimator) scrim.getTag(TAG_KEY_ANIM); 456 float previousEndValue = (Float) scrim.getTag(TAG_END_ALPHA); 457 float previousStartValue = (Float) scrim.getTag(TAG_START_ALPHA); 458 float relativeDiff = alpha - previousEndValue; 459 float newStartValue = previousStartValue + relativeDiff; 460 scrim.setTag(TAG_START_ALPHA, newStartValue); 461 scrim.setTag(TAG_END_ALPHA, alpha); 462 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); 463 } else { 464 // Set animation. 465 updateScrimColor(scrim, alpha, getCurrentScrimTint(scrim)); 466 } 467 } 468 applyExpansionToAlpha()469 private void applyExpansionToAlpha() { 470 if (!mExpansionAffectsAlpha) { 471 return; 472 } 473 474 if (mState == ScrimState.UNLOCKED || mState == ScrimState.BUBBLE_EXPANDED) { 475 // Darken scrim as you pull down the shade when unlocked 476 float behindFraction = getInterpolatedFraction(); 477 behindFraction = (float) Math.pow(behindFraction, 0.8f); 478 mBehindAlpha = behindFraction * mDefaultScrimAlpha; 479 mInFrontAlpha = 0; 480 } else if (mState == ScrimState.KEYGUARD || mState == ScrimState.PULSING) { 481 // Either darken of make the scrim transparent when you 482 // pull down the shade 483 float interpolatedFract = getInterpolatedFraction(); 484 float alphaBehind = mState.getBehindAlpha(); 485 if (mDarkenWhileDragging) { 486 mBehindAlpha = MathUtils.lerp(mDefaultScrimAlpha, alphaBehind, 487 interpolatedFract); 488 mInFrontAlpha = mState.getFrontAlpha(); 489 } else { 490 mBehindAlpha = MathUtils.lerp(0 /* start */, alphaBehind, 491 interpolatedFract); 492 mInFrontAlpha = mState.getFrontAlpha(); 493 } 494 mBehindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getBehindTint(), 495 mState.getBehindTint(), interpolatedFract); 496 } 497 if (isNaN(mBehindAlpha) || isNaN(mInFrontAlpha)) { 498 throw new IllegalStateException("Scrim opacity is NaN for state: " + mState 499 + ", front: " + mInFrontAlpha + ", back: " + mBehindAlpha); 500 } 501 } 502 applyAndDispatchExpansion()503 private void applyAndDispatchExpansion() { 504 applyExpansionToAlpha(); 505 if (mUpdatePending) { 506 return; 507 } 508 setOrAdaptCurrentAnimation(mScrimBehind); 509 setOrAdaptCurrentAnimation(mScrimInFront); 510 setOrAdaptCurrentAnimation(mScrimForBubble); 511 dispatchScrimState(mScrimBehind.getViewAlpha()); 512 513 // Reset wallpaper timeout if it's already timeout like expanding panel while PULSING 514 // and docking. 515 if (mWallpaperVisibilityTimedOut) { 516 mWallpaperVisibilityTimedOut = false; 517 DejankUtils.postAfterTraversal(() -> { 518 mTimeTicker.schedule(mDozeParameters.getWallpaperAodDuration(), 519 AlarmTimeout.MODE_IGNORE_IF_SCHEDULED); 520 }); 521 } 522 } 523 524 /** 525 * Sets the given drawable as the background of the scrim that shows up behind the 526 * notifications. 527 */ setScrimBehindDrawable(Drawable drawable)528 public void setScrimBehindDrawable(Drawable drawable) { 529 mScrimBehind.setDrawable(drawable); 530 } 531 532 /** 533 * Sets the front scrim opacity in AOD so it's not as bright. 534 * <p> 535 * Displays usually don't support multiple dimming settings when in low power mode. 536 * The workaround is to modify the front scrim opacity when in AOD, so it's not as 537 * bright when you're at the movies or lying down on bed. 538 * <p> 539 * This value will be lost during transitions and only updated again after the the 540 * device is dozing when the light sensor is on. 541 */ setAodFrontScrimAlpha(float alpha)542 public void setAodFrontScrimAlpha(float alpha) { 543 if (mInFrontAlpha != alpha && shouldUpdateFrontScrimAlpha()) { 544 mInFrontAlpha = alpha; 545 updateScrims(); 546 } 547 548 mState.AOD.setAodFrontScrimAlpha(alpha); 549 mState.PULSING.setAodFrontScrimAlpha(alpha); 550 } 551 shouldUpdateFrontScrimAlpha()552 private boolean shouldUpdateFrontScrimAlpha() { 553 if (mState == ScrimState.AOD 554 && (mDozeParameters.getAlwaysOn() || mDockManager.isDocked())) { 555 return true; 556 } 557 558 if (mState == ScrimState.PULSING) { 559 return true; 560 } 561 562 return false; 563 } 564 565 /** 566 * If the lock screen sensor is active. 567 */ setWakeLockScreenSensorActive(boolean active)568 public void setWakeLockScreenSensorActive(boolean active) { 569 for (ScrimState state : ScrimState.values()) { 570 state.setWakeLockScreenSensorActive(active); 571 } 572 573 if (mState == ScrimState.PULSING) { 574 float newBehindAlpha = mState.getBehindAlpha(); 575 if (mBehindAlpha != newBehindAlpha) { 576 mBehindAlpha = newBehindAlpha; 577 if (isNaN(mBehindAlpha)) { 578 throw new IllegalStateException("Scrim opacity is NaN for state: " + mState 579 + ", back: " + mBehindAlpha); 580 } 581 updateScrims(); 582 } 583 } 584 } 585 scheduleUpdate()586 protected void scheduleUpdate() { 587 if (mUpdatePending || mScrimBehind == null) return; 588 589 // Make sure that a frame gets scheduled. 590 mScrimBehind.invalidate(); 591 mScrimBehind.getViewTreeObserver().addOnPreDrawListener(this); 592 mUpdatePending = true; 593 } 594 updateScrims()595 protected void updateScrims() { 596 // Make sure we have the right gradients and their opacities will satisfy GAR. 597 if (mNeedsDrawableColorUpdate) { 598 mNeedsDrawableColorUpdate = false; 599 // Only animate scrim color if the scrim view is actually visible 600 boolean animateScrimInFront = mScrimInFront.getViewAlpha() != 0 && !mBlankScreen; 601 boolean animateScrimBehind = mScrimBehind.getViewAlpha() != 0 && !mBlankScreen; 602 boolean animateScrimForBubble = mScrimForBubble.getViewAlpha() != 0 && !mBlankScreen; 603 604 mScrimInFront.setColors(mColors, animateScrimInFront); 605 mScrimBehind.setColors(mColors, animateScrimBehind); 606 mScrimForBubble.setColors(mColors, animateScrimForBubble); 607 608 // Calculate minimum scrim opacity for white or black text. 609 int textColor = mColors.supportsDarkText() ? Color.BLACK : Color.WHITE; 610 int mainColor = mColors.getMainColor(); 611 float minOpacity = ColorUtils.calculateMinimumBackgroundAlpha(textColor, mainColor, 612 4.5f /* minimumContrast */) / 255f; 613 dispatchScrimState(mScrimBehind.getViewAlpha()); 614 } 615 616 // We want to override the back scrim opacity for the AOD state 617 // when it's time to fade the wallpaper away. 618 boolean aodWallpaperTimeout = (mState == ScrimState.AOD || mState == ScrimState.PULSING) 619 && mWallpaperVisibilityTimedOut; 620 // We also want to hide FLAG_SHOW_WHEN_LOCKED activities under the scrim. 621 boolean occludedKeyguard = (mState == ScrimState.PULSING || mState == ScrimState.AOD) 622 && mKeyguardOccluded; 623 if (aodWallpaperTimeout || occludedKeyguard) { 624 mBehindAlpha = 1; 625 } 626 setScrimAlpha(mScrimInFront, mInFrontAlpha); 627 setScrimAlpha(mScrimBehind, mBehindAlpha); 628 setScrimAlpha(mScrimForBubble, mBubbleAlpha); 629 // The animation could have all already finished, let's call onFinished just in case 630 onFinished(); 631 dispatchScrimsVisible(); 632 } 633 dispatchScrimState(float alpha)634 private void dispatchScrimState(float alpha) { 635 mScrimStateListener.accept(mState, alpha, mScrimInFront.getColors()); 636 } 637 dispatchScrimsVisible()638 private void dispatchScrimsVisible() { 639 final int currentScrimVisibility; 640 if (mScrimInFront.getViewAlpha() == 1 || mScrimBehind.getViewAlpha() == 1) { 641 currentScrimVisibility = OPAQUE; 642 } else if (mScrimInFront.getViewAlpha() == 0 && mScrimBehind.getViewAlpha() == 0) { 643 currentScrimVisibility = TRANSPARENT; 644 } else { 645 currentScrimVisibility = SEMI_TRANSPARENT; 646 } 647 648 if (mScrimsVisibility != currentScrimVisibility) { 649 mScrimsVisibility = currentScrimVisibility; 650 mScrimVisibleListener.accept(currentScrimVisibility); 651 } 652 } 653 getInterpolatedFraction()654 private float getInterpolatedFraction() { 655 float frac = mExpansionFraction; 656 // let's start this 20% of the way down the screen 657 frac = frac * 1.2f - 0.2f; 658 if (frac <= 0) { 659 return 0; 660 } else { 661 // woo, special effects 662 return (float) (1f - 0.5f * (1f - Math.cos(3.14159f * Math.pow(1f - frac, 2f)))); 663 } 664 } 665 setScrimAlpha(ScrimView scrim, float alpha)666 private void setScrimAlpha(ScrimView scrim, float alpha) { 667 if (alpha == 0f) { 668 scrim.setClickable(false); 669 } else { 670 // Eat touch events (unless dozing). 671 scrim.setClickable(mState != ScrimState.AOD); 672 } 673 updateScrim(scrim, alpha); 674 } 675 getScrimName(ScrimView scrim)676 private String getScrimName(ScrimView scrim) { 677 if (scrim == mScrimInFront) { 678 return "front_scrim"; 679 } else if (scrim == mScrimBehind) { 680 return "back_scrim"; 681 } else if (scrim == mScrimForBubble) { 682 return "bubble_scrim"; 683 } 684 return "unknown_scrim"; 685 } 686 updateScrimColor(View scrim, float alpha, int tint)687 private void updateScrimColor(View scrim, float alpha, int tint) { 688 alpha = Math.max(0, Math.min(1.0f, alpha)); 689 if (scrim instanceof ScrimView) { 690 ScrimView scrimView = (ScrimView) scrim; 691 692 Trace.traceCounter(Trace.TRACE_TAG_APP, getScrimName(scrimView) + "_alpha", 693 (int) (alpha * 255)); 694 695 Trace.traceCounter(Trace.TRACE_TAG_APP, getScrimName(scrimView) + "_tint", 696 Color.alpha(tint)); 697 698 scrimView.setTint(tint); 699 scrimView.setViewAlpha(alpha); 700 } else { 701 scrim.setAlpha(alpha); 702 } 703 dispatchScrimsVisible(); 704 } 705 startScrimAnimation(final View scrim, float current)706 private void startScrimAnimation(final View scrim, float current) { 707 ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f); 708 if (mAnimatorListener != null) { 709 anim.addListener(mAnimatorListener); 710 } 711 final int initialScrimTint = scrim instanceof ScrimView ? ((ScrimView) scrim).getTint() : 712 Color.TRANSPARENT; 713 anim.addUpdateListener(animation -> { 714 final float startAlpha = (Float) scrim.getTag(TAG_START_ALPHA); 715 final float animAmount = (float) animation.getAnimatedValue(); 716 final int finalScrimTint = getCurrentScrimTint(scrim); 717 final float finalScrimAlpha = getCurrentScrimAlpha(scrim); 718 float alpha = MathUtils.lerp(startAlpha, finalScrimAlpha, animAmount); 719 alpha = MathUtils.constrain(alpha, 0f, 1f); 720 int tint = ColorUtils.blendARGB(initialScrimTint, finalScrimTint, animAmount); 721 updateScrimColor(scrim, alpha, tint); 722 dispatchScrimsVisible(); 723 }); 724 anim.setInterpolator(mInterpolator); 725 anim.setStartDelay(mAnimationDelay); 726 anim.setDuration(mAnimationDuration); 727 anim.addListener(new AnimatorListenerAdapter() { 728 private Callback lastCallback = mCallback; 729 730 @Override 731 public void onAnimationEnd(Animator animation) { 732 scrim.setTag(TAG_KEY_ANIM, null); 733 onFinished(lastCallback); 734 735 dispatchScrimsVisible(); 736 } 737 }); 738 739 // Cache alpha values because we might want to update this animator in the future if 740 // the user expands the panel while the animation is still running. 741 scrim.setTag(TAG_START_ALPHA, current); 742 scrim.setTag(TAG_END_ALPHA, getCurrentScrimAlpha(scrim)); 743 744 scrim.setTag(TAG_KEY_ANIM, anim); 745 anim.start(); 746 } 747 getCurrentScrimAlpha(View scrim)748 private float getCurrentScrimAlpha(View scrim) { 749 if (scrim == mScrimInFront) { 750 return mInFrontAlpha; 751 } else if (scrim == mScrimBehind) { 752 return mBehindAlpha; 753 } else if (scrim == mScrimForBubble) { 754 return mBubbleAlpha; 755 } else { 756 throw new IllegalArgumentException("Unknown scrim view"); 757 } 758 } 759 getCurrentScrimTint(View scrim)760 private int getCurrentScrimTint(View scrim) { 761 if (scrim == mScrimInFront) { 762 return mInFrontTint; 763 } else if (scrim == mScrimBehind) { 764 return mBehindTint; 765 } else if (scrim == mScrimForBubble) { 766 return mBubbleTint; 767 } else { 768 throw new IllegalArgumentException("Unknown scrim view"); 769 } 770 } 771 772 @Override onPreDraw()773 public boolean onPreDraw() { 774 mScrimBehind.getViewTreeObserver().removeOnPreDrawListener(this); 775 mUpdatePending = false; 776 if (mCallback != null) { 777 mCallback.onStart(); 778 } 779 updateScrims(); 780 return true; 781 } 782 onFinished()783 private void onFinished() { 784 onFinished(mCallback); 785 } 786 onFinished(Callback callback)787 private void onFinished(Callback callback) { 788 if (mPendingFrameCallback != null) { 789 // No animations can finish while we're waiting on the blanking to finish 790 return; 791 792 } 793 if (isAnimating(mScrimBehind) 794 || isAnimating(mScrimInFront) 795 || isAnimating(mScrimForBubble)) { 796 if (callback != null && callback != mCallback) { 797 // Since we only notify the callback that we're finished once everything has 798 // finished, we need to make sure that any changing callbacks are also invoked 799 callback.onFinished(); 800 } 801 return; 802 } 803 if (mWakeLockHeld) { 804 mWakeLock.release(TAG); 805 mWakeLockHeld = false; 806 } 807 808 if (callback != null) { 809 callback.onFinished(); 810 811 if (callback == mCallback) { 812 mCallback = null; 813 } 814 } 815 816 // When unlocking with fingerprint, we'll fade the scrims from black to transparent. 817 // At the end of the animation we need to remove the tint. 818 if (mState == ScrimState.UNLOCKED) { 819 mInFrontTint = Color.TRANSPARENT; 820 mBehindTint = Color.TRANSPARENT; 821 mBubbleTint = Color.TRANSPARENT; 822 updateScrimColor(mScrimInFront, mInFrontAlpha, mInFrontTint); 823 updateScrimColor(mScrimBehind, mBehindAlpha, mBehindTint); 824 updateScrimColor(mScrimForBubble, mBubbleAlpha, mBubbleTint); 825 } 826 } 827 isAnimating(View scrim)828 private boolean isAnimating(View scrim) { 829 return scrim.getTag(TAG_KEY_ANIM) != null; 830 } 831 832 @VisibleForTesting setAnimatorListener(Animator.AnimatorListener animatorListener)833 void setAnimatorListener(Animator.AnimatorListener animatorListener) { 834 mAnimatorListener = animatorListener; 835 } 836 updateScrim(ScrimView scrim, float alpha)837 private void updateScrim(ScrimView scrim, float alpha) { 838 final float currentAlpha = scrim.getViewAlpha(); 839 840 ValueAnimator previousAnimator = ViewState.getChildTag(scrim, TAG_KEY_ANIM); 841 if (previousAnimator != null) { 842 // Previous animators should always be cancelled. Not doing so would cause 843 // overlap, especially on states that don't animate, leading to flickering, 844 // and in the worst case, an internal state that doesn't represent what 845 // transitionTo requested. 846 cancelAnimator(previousAnimator); 847 } 848 849 if (mPendingFrameCallback != null) { 850 // Display is off and we're waiting. 851 return; 852 } else if (mBlankScreen) { 853 // Need to blank the display before continuing. 854 blankDisplay(); 855 return; 856 } else if (!mScreenBlankingCallbackCalled) { 857 // Not blanking the screen. Letting the callback know that we're ready 858 // to replace what was on the screen before. 859 if (mCallback != null) { 860 mCallback.onDisplayBlanked(); 861 mScreenBlankingCallbackCalled = true; 862 } 863 } 864 865 if (scrim == mScrimBehind) { 866 dispatchScrimState(alpha); 867 } 868 869 final boolean wantsAlphaUpdate = alpha != currentAlpha; 870 final boolean wantsTintUpdate = scrim.getTint() != getCurrentScrimTint(scrim); 871 872 if (wantsAlphaUpdate || wantsTintUpdate) { 873 if (mAnimateChange) { 874 startScrimAnimation(scrim, currentAlpha); 875 } else { 876 // update the alpha directly 877 updateScrimColor(scrim, alpha, getCurrentScrimTint(scrim)); 878 } 879 } 880 } 881 cancelAnimator(ValueAnimator previousAnimator)882 private void cancelAnimator(ValueAnimator previousAnimator) { 883 if (previousAnimator != null) { 884 previousAnimator.cancel(); 885 } 886 } 887 blankDisplay()888 private void blankDisplay() { 889 updateScrimColor(mScrimInFront, 1, Color.BLACK); 890 891 // Notify callback that the screen is completely black and we're 892 // ready to change the display power mode 893 mPendingFrameCallback = () -> { 894 if (mCallback != null) { 895 mCallback.onDisplayBlanked(); 896 mScreenBlankingCallbackCalled = true; 897 } 898 899 mBlankingTransitionRunnable = () -> { 900 mBlankingTransitionRunnable = null; 901 mPendingFrameCallback = null; 902 mBlankScreen = false; 903 // Try again. 904 updateScrims(); 905 }; 906 907 // Setting power states can happen after we push out the frame. Make sure we 908 // stay fully opaque until the power state request reaches the lower levels. 909 final int delay = mScreenOn ? 32 : 500; 910 if (DEBUG) { 911 Log.d(TAG, "Fading out scrims with delay: " + delay); 912 } 913 mHandler.postDelayed(mBlankingTransitionRunnable, delay); 914 }; 915 doOnTheNextFrame(mPendingFrameCallback); 916 } 917 918 /** 919 * Executes a callback after the frame has hit the display. 920 * 921 * @param callback What to run. 922 */ 923 @VisibleForTesting doOnTheNextFrame(Runnable callback)924 protected void doOnTheNextFrame(Runnable callback) { 925 // Just calling View#postOnAnimation isn't enough because the frame might not have reached 926 // the display yet. A timeout is the safest solution. 927 mScrimBehind.postOnAnimationDelayed(callback, 32 /* delayMillis */); 928 } 929 getBackgroundColor()930 public int getBackgroundColor() { 931 int color = mColors.getMainColor(); 932 return Color.argb((int) (mScrimBehind.getViewAlpha() * Color.alpha(color)), 933 Color.red(color), Color.green(color), Color.blue(color)); 934 } 935 setScrimBehindChangeRunnable(Runnable changeRunnable)936 public void setScrimBehindChangeRunnable(Runnable changeRunnable) { 937 mScrimBehind.setChangeRunnable(changeRunnable); 938 } 939 setCurrentUser(int currentUser)940 public void setCurrentUser(int currentUser) { 941 // Don't care in the base class. 942 } 943 944 @Override onColorsChanged(ColorExtractor colorExtractor, int which)945 public void onColorsChanged(ColorExtractor colorExtractor, int which) { 946 mColors = mColorExtractor.getNeutralColors(); 947 mNeedsDrawableColorUpdate = true; 948 scheduleUpdate(); 949 } 950 951 @Override dump(FileDescriptor fd, PrintWriter pw, String[] args)952 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 953 pw.println(" ScrimController: "); 954 pw.print(" state: "); 955 pw.println(mState); 956 957 pw.print(" frontScrim:"); 958 pw.print(" viewAlpha="); 959 pw.print(mScrimInFront.getViewAlpha()); 960 pw.print(" alpha="); 961 pw.print(mInFrontAlpha); 962 pw.print(" tint=0x"); 963 pw.println(Integer.toHexString(mScrimInFront.getTint())); 964 965 pw.print(" backScrim:"); 966 pw.print(" viewAlpha="); 967 pw.print(mScrimBehind.getViewAlpha()); 968 pw.print(" alpha="); 969 pw.print(mBehindAlpha); 970 pw.print(" tint=0x"); 971 pw.println(Integer.toHexString(mScrimBehind.getTint())); 972 973 pw.print(" bubbleScrim:"); 974 pw.print(" viewAlpha="); 975 pw.print(mScrimForBubble.getViewAlpha()); 976 pw.print(" alpha="); 977 pw.print(mBubbleAlpha); 978 pw.print(" tint=0x"); 979 pw.println(Integer.toHexString(mScrimForBubble.getTint())); 980 981 pw.print(" mTracking="); 982 pw.println(mTracking); 983 pw.print(" mDefaultScrimAlpha="); 984 pw.println(mDefaultScrimAlpha); 985 pw.print(" mExpansionFraction="); 986 pw.println(mExpansionFraction); 987 } 988 setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode)989 public void setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode) { 990 mWallpaperSupportsAmbientMode = wallpaperSupportsAmbientMode; 991 ScrimState[] states = ScrimState.values(); 992 for (int i = 0; i < states.length; i++) { 993 states[i].setWallpaperSupportsAmbientMode(wallpaperSupportsAmbientMode); 994 } 995 } 996 997 /** 998 * Interrupts blanking transitions once the display notifies that it's already on. 999 */ onScreenTurnedOn()1000 public void onScreenTurnedOn() { 1001 mScreenOn = true; 1002 if (mHandler.hasCallbacks(mBlankingTransitionRunnable)) { 1003 if (DEBUG) { 1004 Log.d(TAG, "Shorter blanking because screen turned on. All good."); 1005 } 1006 mHandler.removeCallbacks(mBlankingTransitionRunnable); 1007 mBlankingTransitionRunnable.run(); 1008 } 1009 } 1010 onScreenTurnedOff()1011 public void onScreenTurnedOff() { 1012 mScreenOn = false; 1013 } 1014 setExpansionAffectsAlpha(boolean expansionAffectsAlpha)1015 public void setExpansionAffectsAlpha(boolean expansionAffectsAlpha) { 1016 mExpansionAffectsAlpha = expansionAffectsAlpha; 1017 if (expansionAffectsAlpha) { 1018 applyAndDispatchExpansion(); 1019 } 1020 } 1021 setKeyguardOccluded(boolean keyguardOccluded)1022 public void setKeyguardOccluded(boolean keyguardOccluded) { 1023 mKeyguardOccluded = keyguardOccluded; 1024 updateScrims(); 1025 } 1026 setHasBackdrop(boolean hasBackdrop)1027 public void setHasBackdrop(boolean hasBackdrop) { 1028 for (ScrimState state : ScrimState.values()) { 1029 state.setHasBackdrop(hasBackdrop); 1030 } 1031 1032 // Backdrop event may arrive after state was already applied, 1033 // in this case, back-scrim needs to be re-evaluated 1034 if (mState == ScrimState.AOD || mState == ScrimState.PULSING) { 1035 float newBehindAlpha = mState.getBehindAlpha(); 1036 if (isNaN(newBehindAlpha)) { 1037 throw new IllegalStateException("Scrim opacity is NaN for state: " + mState 1038 + ", back: " + mBehindAlpha); 1039 } 1040 if (mBehindAlpha != newBehindAlpha) { 1041 mBehindAlpha = newBehindAlpha; 1042 updateScrims(); 1043 } 1044 } 1045 } 1046 setKeyguardFadingAway(boolean fadingAway, long duration)1047 private void setKeyguardFadingAway(boolean fadingAway, long duration) { 1048 for (ScrimState state : ScrimState.values()) { 1049 state.setKeyguardFadingAway(fadingAway, duration); 1050 } 1051 } 1052 setLaunchingAffordanceWithPreview(boolean launchingAffordanceWithPreview)1053 public void setLaunchingAffordanceWithPreview(boolean launchingAffordanceWithPreview) { 1054 for (ScrimState state : ScrimState.values()) { 1055 state.setLaunchingAffordanceWithPreview(launchingAffordanceWithPreview); 1056 } 1057 } 1058 1059 public interface Callback { onStart()1060 default void onStart() { 1061 } 1062 onDisplayBlanked()1063 default void onDisplayBlanked() { 1064 } 1065 onFinished()1066 default void onFinished() { 1067 } 1068 onCancelled()1069 default void onCancelled() { 1070 } 1071 } 1072 1073 /** 1074 * Simple keyguard callback that updates scrims when keyguard visibility changes. 1075 */ 1076 private class KeyguardVisibilityCallback extends KeyguardUpdateMonitorCallback { 1077 1078 @Override onKeyguardVisibilityChanged(boolean showing)1079 public void onKeyguardVisibilityChanged(boolean showing) { 1080 mNeedsDrawableColorUpdate = true; 1081 scheduleUpdate(); 1082 } 1083 } 1084 } 1085