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