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