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