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 com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; 20 21 import static java.lang.Float.isNaN; 22 23 import android.animation.Animator; 24 import android.animation.AnimatorListenerAdapter; 25 import android.animation.ValueAnimator; 26 import android.annotation.IntDef; 27 import android.app.AlarmManager; 28 import android.graphics.Color; 29 import android.os.Handler; 30 import android.os.Trace; 31 import android.util.Log; 32 import android.util.MathUtils; 33 import android.util.Pair; 34 import android.view.View; 35 import android.view.ViewTreeObserver; 36 import android.view.animation.DecelerateInterpolator; 37 import android.view.animation.Interpolator; 38 39 import androidx.annotation.FloatRange; 40 import androidx.annotation.Nullable; 41 42 import com.android.internal.annotations.VisibleForTesting; 43 import com.android.internal.colorextraction.ColorExtractor.GradientColors; 44 import com.android.internal.graphics.ColorUtils; 45 import com.android.internal.util.function.TriConsumer; 46 import com.android.keyguard.BouncerPanelExpansionCalculator; 47 import com.android.keyguard.KeyguardUpdateMonitor; 48 import com.android.keyguard.KeyguardUpdateMonitorCallback; 49 import com.android.settingslib.Utils; 50 import com.android.systemui.DejankUtils; 51 import com.android.systemui.Dumpable; 52 import com.android.systemui.R; 53 import com.android.systemui.animation.ShadeInterpolation; 54 import com.android.systemui.dagger.SysUISingleton; 55 import com.android.systemui.dagger.qualifiers.Main; 56 import com.android.systemui.dock.DockManager; 57 import com.android.systemui.flags.FeatureFlags; 58 import com.android.systemui.flags.Flags; 59 import com.android.systemui.keyguard.KeyguardUnlockAnimationController; 60 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; 61 import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants; 62 import com.android.systemui.keyguard.shared.model.ScrimAlpha; 63 import com.android.systemui.keyguard.shared.model.TransitionState; 64 import com.android.systemui.keyguard.shared.model.TransitionStep; 65 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel; 66 import com.android.systemui.scrim.ScrimView; 67 import com.android.systemui.shade.NotificationPanelViewController; 68 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator; 69 import com.android.systemui.statusbar.notification.stack.ViewState; 70 import com.android.systemui.statusbar.policy.ConfigurationController; 71 import com.android.systemui.statusbar.policy.KeyguardStateController; 72 import com.android.systemui.util.AlarmTimeout; 73 import com.android.systemui.util.wakelock.DelayedWakeLock; 74 import com.android.systemui.util.wakelock.WakeLock; 75 76 import java.io.PrintWriter; 77 import java.lang.annotation.Retention; 78 import java.lang.annotation.RetentionPolicy; 79 import java.util.concurrent.Executor; 80 import java.util.function.Consumer; 81 82 import javax.inject.Inject; 83 84 import kotlinx.coroutines.CoroutineDispatcher; 85 86 /** 87 * Controls both the scrim behind the notifications and in front of the notifications (when a 88 * security method gets shown). 89 */ 90 @SysUISingleton 91 public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dumpable { 92 93 static final String TAG = "ScrimController"; 94 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 95 96 // debug mode colors scrims with below debug colors, irrespectively of which state they're in 97 public static final boolean DEBUG_MODE = false; 98 99 public static final int DEBUG_NOTIFICATIONS_TINT = Color.RED; 100 public static final int DEBUG_FRONT_TINT = Color.GREEN; 101 public static final int DEBUG_BEHIND_TINT = Color.BLUE; 102 103 /** 104 * General scrim animation duration. 105 */ 106 public static final long ANIMATION_DURATION = 220; 107 /** 108 * Longer duration, currently only used when going to AOD. 109 */ 110 public static final long ANIMATION_DURATION_LONG = 1000; 111 /** 112 * When both scrims have 0 alpha. 113 */ 114 public static final int TRANSPARENT = 0; 115 /** 116 * When scrims aren't transparent (alpha 0) but also not opaque (alpha 1.) 117 */ 118 public static final int SEMI_TRANSPARENT = 1; 119 /** 120 * When at least 1 scrim is fully opaque (alpha set to 1.) 121 */ 122 public static final int OPAQUE = 2; 123 private boolean mClipsQsScrim; 124 125 /** 126 * Whether an activity is launching over the lockscreen. During the launch animation, we want to 127 * delay certain scrim changes until after the animation ends. 128 */ 129 private boolean mOccludeAnimationPlaying = false; 130 131 /** 132 * The amount of progress we are currently in if we're transitioning to the full shade. 133 * 0.0f means we're not transitioning yet, while 1 means we're all the way in the full 134 * shade. 135 */ 136 private float mTransitionToFullShadeProgress; 137 138 /** 139 * Same as {@link #mTransitionToFullShadeProgress}, but specifically for the notifications scrim 140 * on the lock screen. 141 * 142 * On split shade lock screen we want the different scrims to fade in at different times and 143 * rates. 144 */ 145 private float mTransitionToLockScreenFullShadeNotificationsProgress; 146 147 /** 148 * If we're currently transitioning to the full shade. 149 */ 150 private boolean mTransitioningToFullShade; 151 152 /** 153 * The percentage of the bouncer which is hidden. If 1, the bouncer is completely hidden. If 154 * 0, the bouncer is visible. 155 */ 156 @FloatRange(from = 0, to = 1) 157 private float mBouncerHiddenFraction = KeyguardBouncerConstants.EXPANSION_HIDDEN; 158 159 @IntDef(prefix = {"VISIBILITY_"}, value = { 160 TRANSPARENT, 161 SEMI_TRANSPARENT, 162 OPAQUE 163 }) 164 @Retention(RetentionPolicy.SOURCE) 165 public @interface ScrimVisibility { 166 } 167 168 /** 169 * Default alpha value for most scrims. 170 */ 171 protected static final float KEYGUARD_SCRIM_ALPHA = 0.2f; 172 /** 173 * Scrim opacity when the phone is about to wake-up. 174 */ 175 public static final float WAKE_SENSOR_SCRIM_ALPHA = 0.6f; 176 177 /** 178 * The default scrim under the shade and dialogs. 179 * This should not be lower than 0.54, otherwise we won't pass GAR. 180 */ 181 public static final float BUSY_SCRIM_ALPHA = 1f; 182 183 /** 184 * Scrim opacity that can have text on top. 185 */ 186 public static final float GAR_SCRIM_ALPHA = 0.6f; 187 188 static final int TAG_KEY_ANIM = R.id.scrim; 189 private static final int TAG_START_ALPHA = R.id.scrim_alpha_start; 190 private static final int TAG_END_ALPHA = R.id.scrim_alpha_end; 191 private static final float NOT_INITIALIZED = -1; 192 193 private ScrimState mState = ScrimState.UNINITIALIZED; 194 195 private ScrimView mScrimInFront; 196 private ScrimView mNotificationsScrim; 197 private ScrimView mScrimBehind; 198 199 private Runnable mScrimBehindChangeRunnable; 200 201 private final KeyguardStateController mKeyguardStateController; 202 private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; 203 private final DozeParameters mDozeParameters; 204 private final DockManager mDockManager; 205 private final AlarmTimeout mTimeTicker; 206 private final KeyguardVisibilityCallback mKeyguardVisibilityCallback; 207 private final Handler mHandler; 208 private final Executor mMainExecutor; 209 private final ScreenOffAnimationController mScreenOffAnimationController; 210 private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; 211 private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; 212 213 private GradientColors mColors; 214 private boolean mNeedsDrawableColorUpdate; 215 216 private float mAdditionalScrimBehindAlphaKeyguard = 0f; 217 // Combined scrim behind keyguard alpha of default scrim + additional scrim 218 // (if wallpaper dimming is applied). 219 private float mScrimBehindAlphaKeyguard = KEYGUARD_SCRIM_ALPHA; 220 private final float mDefaultScrimAlpha; 221 222 private float mRawPanelExpansionFraction; 223 private float mPanelScrimMinFraction; 224 // Calculated based on mRawPanelExpansionFraction and mPanelScrimMinFraction 225 private float mPanelExpansionFraction = 1f; // Assume shade is expanded during initialization 226 private float mQsExpansion; 227 private boolean mQsBottomVisible; 228 private boolean mAnimatingPanelExpansionOnUnlock; // don't animate scrim 229 230 private boolean mDarkenWhileDragging; 231 private boolean mExpansionAffectsAlpha = true; 232 private boolean mAnimateChange; 233 private boolean mUpdatePending; 234 private long mAnimationDuration = -1; 235 private long mAnimationDelay; 236 private Animator.AnimatorListener mAnimatorListener; 237 private final Interpolator mInterpolator = new DecelerateInterpolator(); 238 239 private float mInFrontAlpha = NOT_INITIALIZED; 240 private float mBehindAlpha = NOT_INITIALIZED; 241 private float mNotificationsAlpha = NOT_INITIALIZED; 242 243 private int mInFrontTint; 244 private int mBehindTint; 245 private int mNotificationsTint; 246 247 private boolean mWallpaperVisibilityTimedOut; 248 private int mScrimsVisibility; 249 private final TriConsumer<ScrimState, Float, GradientColors> mScrimStateListener; 250 private final LargeScreenShadeInterpolator mLargeScreenShadeInterpolator; 251 private final FeatureFlags mFeatureFlags; 252 private Consumer<Integer> mScrimVisibleListener; 253 private boolean mBlankScreen; 254 private boolean mScreenBlankingCallbackCalled; 255 private Callback mCallback; 256 private boolean mWallpaperSupportsAmbientMode; 257 private boolean mScreenOn; 258 private boolean mTransparentScrimBackground; 259 260 // Scrim blanking callbacks 261 private Runnable mPendingFrameCallback; 262 private Runnable mBlankingTransitionRunnable; 263 264 private final WakeLock mWakeLock; 265 private boolean mWakeLockHeld; 266 private boolean mKeyguardOccluded; 267 268 private KeyguardTransitionInteractor mKeyguardTransitionInteractor; 269 private CoroutineDispatcher mMainDispatcher; 270 private boolean mIsBouncerToGoneTransitionRunning = false; 271 private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel; 272 private final Consumer<ScrimAlpha> mScrimAlphaConsumer = 273 (ScrimAlpha alphas) -> { 274 mInFrontAlpha = alphas.getFrontAlpha(); 275 mScrimInFront.setViewAlpha(mInFrontAlpha); 276 277 mNotificationsAlpha = alphas.getNotificationsAlpha(); 278 mNotificationsScrim.setViewAlpha(mNotificationsAlpha); 279 280 mBehindAlpha = alphas.getBehindAlpha(); 281 mScrimBehind.setViewAlpha(mBehindAlpha); 282 }; 283 284 Consumer<TransitionStep> mPrimaryBouncerToGoneTransition; 285 286 @Inject ScrimController( LightBarController lightBarController, DozeParameters dozeParameters, AlarmManager alarmManager, KeyguardStateController keyguardStateController, DelayedWakeLock.Builder delayedWakeLockBuilder, Handler handler, KeyguardUpdateMonitor keyguardUpdateMonitor, DockManager dockManager, ConfigurationController configurationController, @Main Executor mainExecutor, ScreenOffAnimationController screenOffAnimationController, KeyguardUnlockAnimationController keyguardUnlockAnimationController, StatusBarKeyguardViewManager statusBarKeyguardViewManager, PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel, KeyguardTransitionInteractor keyguardTransitionInteractor, @Main CoroutineDispatcher mainDispatcher, LargeScreenShadeInterpolator largeScreenShadeInterpolator, FeatureFlags featureFlags)287 public ScrimController( 288 LightBarController lightBarController, 289 DozeParameters dozeParameters, 290 AlarmManager alarmManager, 291 KeyguardStateController keyguardStateController, 292 DelayedWakeLock.Builder delayedWakeLockBuilder, 293 Handler handler, 294 KeyguardUpdateMonitor keyguardUpdateMonitor, 295 DockManager dockManager, 296 ConfigurationController configurationController, 297 @Main Executor mainExecutor, 298 ScreenOffAnimationController screenOffAnimationController, 299 KeyguardUnlockAnimationController keyguardUnlockAnimationController, 300 StatusBarKeyguardViewManager statusBarKeyguardViewManager, 301 PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel, 302 KeyguardTransitionInteractor keyguardTransitionInteractor, 303 @Main CoroutineDispatcher mainDispatcher, 304 LargeScreenShadeInterpolator largeScreenShadeInterpolator, 305 FeatureFlags featureFlags) { 306 mScrimStateListener = lightBarController::setScrimState; 307 mLargeScreenShadeInterpolator = largeScreenShadeInterpolator; 308 mFeatureFlags = featureFlags; 309 mDefaultScrimAlpha = BUSY_SCRIM_ALPHA; 310 311 mKeyguardStateController = keyguardStateController; 312 mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen(); 313 mKeyguardUpdateMonitor = keyguardUpdateMonitor; 314 mKeyguardVisibilityCallback = new KeyguardVisibilityCallback(); 315 mHandler = handler; 316 mMainExecutor = mainExecutor; 317 mScreenOffAnimationController = screenOffAnimationController; 318 mTimeTicker = new AlarmTimeout(alarmManager, this::onHideWallpaperTimeout, 319 "hide_aod_wallpaper", mHandler); 320 mWakeLock = delayedWakeLockBuilder.setHandler(mHandler).setTag("Scrims").build(); 321 // Scrim alpha is initially set to the value on the resource but might be changed 322 // to make sure that text on top of it is legible. 323 mDozeParameters = dozeParameters; 324 mDockManager = dockManager; 325 mKeyguardUnlockAnimationController = keyguardUnlockAnimationController; 326 keyguardStateController.addCallback(new KeyguardStateController.Callback() { 327 @Override 328 public void onKeyguardFadingAwayChanged() { 329 setKeyguardFadingAway(keyguardStateController.isKeyguardFadingAway(), 330 keyguardStateController.getKeyguardFadingAwayDuration()); 331 } 332 }); 333 mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; 334 configurationController.addCallback(new ConfigurationController.ConfigurationListener() { 335 @Override 336 public void onThemeChanged() { 337 ScrimController.this.onThemeChanged(); 338 } 339 340 @Override 341 public void onUiModeChanged() { 342 ScrimController.this.onThemeChanged(); 343 } 344 }); 345 mColors = new GradientColors(); 346 mPrimaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel; 347 mKeyguardTransitionInteractor = keyguardTransitionInteractor; 348 mMainDispatcher = mainDispatcher; 349 } 350 351 /** 352 * Attach the controller to the supplied views. 353 */ attachViews(ScrimView behindScrim, ScrimView notificationsScrim, ScrimView scrimInFront)354 public void attachViews(ScrimView behindScrim, ScrimView notificationsScrim, 355 ScrimView scrimInFront) { 356 mNotificationsScrim = notificationsScrim; 357 mScrimBehind = behindScrim; 358 mScrimInFront = scrimInFront; 359 updateThemeColors(); 360 361 behindScrim.enableBottomEdgeConcave(mClipsQsScrim); 362 mNotificationsScrim.enableRoundedCorners(true); 363 364 if (mScrimBehindChangeRunnable != null) { 365 mScrimBehind.setChangeRunnable(mScrimBehindChangeRunnable, mMainExecutor); 366 mScrimBehindChangeRunnable = null; 367 } 368 369 final ScrimState[] states = ScrimState.values(); 370 for (int i = 0; i < states.length; i++) { 371 states[i].init(mScrimInFront, mScrimBehind, mDozeParameters, mDockManager); 372 states[i].setScrimBehindAlphaKeyguard(mScrimBehindAlphaKeyguard); 373 states[i].setDefaultScrimAlpha(mDefaultScrimAlpha); 374 } 375 376 mScrimBehind.setDefaultFocusHighlightEnabled(false); 377 mNotificationsScrim.setDefaultFocusHighlightEnabled(false); 378 mScrimInFront.setDefaultFocusHighlightEnabled(false); 379 mTransparentScrimBackground = notificationsScrim.getResources() 380 .getBoolean(R.bool.notification_scrim_transparent); 381 updateScrims(); 382 mKeyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback); 383 384 // prepare() sets proper initial values for most states 385 for (ScrimState state : ScrimState.values()) { 386 state.prepare(state); 387 } 388 389 // Directly control transition to UNLOCKED scrim state from PRIMARY_BOUNCER, and make sure 390 // to report back that keyguard has faded away. This fixes cases where the scrim state was 391 // rapidly switching on unlock, due to shifts in state in CentralSurfacesImpl 392 mPrimaryBouncerToGoneTransition = 393 (TransitionStep step) -> { 394 TransitionState state = step.getTransitionState(); 395 396 mIsBouncerToGoneTransitionRunning = state == TransitionState.RUNNING; 397 398 if (state == TransitionState.STARTED) { 399 setExpansionAffectsAlpha(false); 400 transitionTo(ScrimState.UNLOCKED); 401 } 402 403 if (state == TransitionState.FINISHED || state == TransitionState.CANCELED) { 404 setExpansionAffectsAlpha(true); 405 if (mKeyguardStateController.isKeyguardFadingAway()) { 406 mStatusBarKeyguardViewManager.onKeyguardFadedAway(); 407 } 408 dispatchScrimsVisible(); 409 } 410 }; 411 412 collectFlow(behindScrim, mKeyguardTransitionInteractor.getPrimaryBouncerToGoneTransition(), 413 mPrimaryBouncerToGoneTransition, mMainDispatcher); 414 collectFlow(behindScrim, mPrimaryBouncerToGoneTransitionViewModel.getScrimAlpha(), 415 mScrimAlphaConsumer, mMainDispatcher); 416 } 417 418 /** 419 * Sets corner radius of scrims. 420 */ setScrimCornerRadius(int radius)421 public void setScrimCornerRadius(int radius) { 422 if (mScrimBehind == null || mNotificationsScrim == null) { 423 return; 424 } 425 mScrimBehind.setCornerRadius(radius); 426 mNotificationsScrim.setCornerRadius(radius); 427 } 428 setScrimVisibleListener(Consumer<Integer> listener)429 void setScrimVisibleListener(Consumer<Integer> listener) { 430 mScrimVisibleListener = listener; 431 } 432 transitionTo(ScrimState state)433 public void transitionTo(ScrimState state) { 434 transitionTo(state, null); 435 } 436 transitionTo(ScrimState state, Callback callback)437 public void transitionTo(ScrimState state, Callback callback) { 438 if (mIsBouncerToGoneTransitionRunning) { 439 Log.i(TAG, "Skipping transition to: " + state 440 + " while mIsBouncerToGoneTransitionRunning"); 441 return; 442 } 443 if (state == mState) { 444 // Call the callback anyway, unless it's already enqueued 445 if (callback != null && mCallback != callback) { 446 callback.onFinished(); 447 } 448 return; 449 } else if (DEBUG) { 450 Log.d(TAG, "State changed to: " + state); 451 } 452 453 if (state == ScrimState.UNINITIALIZED) { 454 throw new IllegalArgumentException("Cannot change to UNINITIALIZED."); 455 } 456 457 final ScrimState oldState = mState; 458 mState = state; 459 Trace.traceCounter(Trace.TRACE_TAG_APP, "scrim_state", mState.ordinal()); 460 461 if (mCallback != null) { 462 mCallback.onCancelled(); 463 } 464 mCallback = callback; 465 466 state.prepare(oldState); 467 mScreenBlankingCallbackCalled = false; 468 mAnimationDelay = 0; 469 mBlankScreen = state.getBlanksScreen(); 470 mAnimateChange = state.getAnimateChange(); 471 mAnimationDuration = state.getAnimationDuration(); 472 473 applyState(); 474 475 // Scrim might acquire focus when user is navigating with a D-pad or a keyboard. 476 // We need to disable focus otherwise AOD would end up with a gray overlay. 477 mScrimInFront.setFocusable(!state.isLowPowerState()); 478 mScrimBehind.setFocusable(!state.isLowPowerState()); 479 mNotificationsScrim.setFocusable(!state.isLowPowerState()); 480 481 mScrimInFront.setBlendWithMainColor(state.shouldBlendWithMainColor()); 482 483 // Cancel blanking transitions that were pending before we requested a new state 484 if (mPendingFrameCallback != null) { 485 mScrimBehind.removeCallbacks(mPendingFrameCallback); 486 mPendingFrameCallback = null; 487 } 488 if (mHandler.hasCallbacks(mBlankingTransitionRunnable)) { 489 mHandler.removeCallbacks(mBlankingTransitionRunnable); 490 mBlankingTransitionRunnable = null; 491 } 492 493 // Showing/hiding the keyguard means that scrim colors have to be switched, not necessary 494 // to do the same when you're just showing the brightness mirror. 495 mNeedsDrawableColorUpdate = state != ScrimState.BRIGHTNESS_MIRROR; 496 497 // The device might sleep if it's entering AOD, we need to make sure that 498 // the animation plays properly until the last frame. 499 // It's important to avoid holding the wakelock unless necessary because 500 // WakeLock#aqcuire will trigger an IPC and will cause jank. 501 if (mState.isLowPowerState()) { 502 holdWakeLock(); 503 } 504 505 // AOD wallpapers should fade away after a while. 506 // Docking pulses may take a long time, wallpapers should also fade away after a while. 507 mWallpaperVisibilityTimedOut = false; 508 if (shouldFadeAwayWallpaper()) { 509 DejankUtils.postAfterTraversal(() -> { 510 mTimeTicker.schedule(mDozeParameters.getWallpaperAodDuration(), 511 AlarmTimeout.MODE_IGNORE_IF_SCHEDULED); 512 }); 513 } else { 514 DejankUtils.postAfterTraversal(mTimeTicker::cancel); 515 } 516 517 if (mKeyguardUpdateMonitor.needsSlowUnlockTransition() && mState == ScrimState.UNLOCKED) { 518 mAnimationDelay = CentralSurfaces.FADE_KEYGUARD_START_DELAY; 519 scheduleUpdate(); 520 } else if (((oldState == ScrimState.AOD || oldState == ScrimState.PULSING) // leaving doze 521 && (!mDozeParameters.getAlwaysOn() || mState == ScrimState.UNLOCKED)) 522 || (mState == ScrimState.AOD && !mDozeParameters.getDisplayNeedsBlanking())) { 523 // Scheduling a frame isn't enough when: 524 // • Leaving doze and we need to modify scrim color immediately 525 // • ColorFade will not kick-in and scrim cannot wait for pre-draw. 526 onPreDraw(); 527 } else { 528 // Schedule a frame 529 scheduleUpdate(); 530 } 531 532 dispatchBackScrimState(mScrimBehind.getViewAlpha()); 533 } 534 shouldFadeAwayWallpaper()535 private boolean shouldFadeAwayWallpaper() { 536 if (!mWallpaperSupportsAmbientMode) { 537 return false; 538 } 539 540 if (mState == ScrimState.AOD 541 && (mDozeParameters.getAlwaysOn() || mDockManager.isDocked())) { 542 return true; 543 } 544 545 return false; 546 } 547 getState()548 public ScrimState getState() { 549 return mState; 550 } 551 552 /** 553 * Sets the additional scrim behind alpha keyguard that would be blended with the default scrim 554 * by applying alpha composition on both values. 555 * 556 * @param additionalScrimAlpha alpha value of additional scrim behind alpha keyguard. 557 */ setAdditionalScrimBehindAlphaKeyguard(float additionalScrimAlpha)558 protected void setAdditionalScrimBehindAlphaKeyguard(float additionalScrimAlpha) { 559 mAdditionalScrimBehindAlphaKeyguard = additionalScrimAlpha; 560 } 561 562 /** 563 * Applies alpha composition to the default scrim behind alpha keyguard and the additional 564 * scrim alpha, and sets this value to the scrim behind alpha keyguard. 565 * This is used to apply additional keyguard dimming on top of the default scrim alpha value. 566 */ applyCompositeAlphaOnScrimBehindKeyguard()567 protected void applyCompositeAlphaOnScrimBehindKeyguard() { 568 int compositeAlpha = ColorUtils.compositeAlpha( 569 (int) (255 * mAdditionalScrimBehindAlphaKeyguard), 570 (int) (255 * KEYGUARD_SCRIM_ALPHA)); 571 float keyguardScrimAlpha = (float) compositeAlpha / 255; 572 setScrimBehindValues(keyguardScrimAlpha); 573 } 574 575 /** 576 * Sets the scrim behind alpha keyguard values. This is how much the keyguard will be dimmed. 577 * 578 * @param scrimBehindAlphaKeyguard alpha value of the scrim behind 579 */ setScrimBehindValues(float scrimBehindAlphaKeyguard)580 private void setScrimBehindValues(float scrimBehindAlphaKeyguard) { 581 mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard; 582 ScrimState[] states = ScrimState.values(); 583 for (int i = 0; i < states.length; i++) { 584 states[i].setScrimBehindAlphaKeyguard(scrimBehindAlphaKeyguard); 585 } 586 scheduleUpdate(); 587 } 588 onTrackingStarted()589 public void onTrackingStarted() { 590 mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen(); 591 if (!mKeyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) { 592 mAnimatingPanelExpansionOnUnlock = false; 593 } 594 } 595 596 @VisibleForTesting onHideWallpaperTimeout()597 protected void onHideWallpaperTimeout() { 598 if (mState != ScrimState.AOD && mState != ScrimState.PULSING) { 599 return; 600 } 601 602 holdWakeLock(); 603 mWallpaperVisibilityTimedOut = true; 604 mAnimateChange = true; 605 mAnimationDuration = mDozeParameters.getWallpaperFadeOutDuration(); 606 scheduleUpdate(); 607 } 608 holdWakeLock()609 private void holdWakeLock() { 610 if (!mWakeLockHeld) { 611 if (mWakeLock != null) { 612 mWakeLockHeld = true; 613 mWakeLock.acquire(TAG); 614 } else { 615 Log.w(TAG, "Cannot hold wake lock, it has not been set yet"); 616 } 617 } 618 } 619 620 /** 621 * Current state of the shade expansion when pulling it from the top. 622 * This value is 1 when on top of the keyguard and goes to 0 as the user drags up. 623 * 624 * The expansion fraction is tied to the scrim opacity. 625 * 626 * See {@link ScrimShadeTransitionController#onPanelExpansionChanged}. 627 * 628 * @param rawPanelExpansionFraction From 0 to 1 where 0 means collapsed and 1 expanded. 629 */ setRawPanelExpansionFraction( @loatRangefrom = 0.0, to = 1.0) float rawPanelExpansionFraction)630 public void setRawPanelExpansionFraction( 631 @FloatRange(from = 0.0, to = 1.0) float rawPanelExpansionFraction) { 632 if (isNaN(rawPanelExpansionFraction)) { 633 throw new IllegalArgumentException("rawPanelExpansionFraction should not be NaN"); 634 } 635 mRawPanelExpansionFraction = rawPanelExpansionFraction; 636 calculateAndUpdatePanelExpansion(); 637 } 638 639 /** See {@link NotificationPanelViewController#setPanelScrimMinFraction(float)}. */ setPanelScrimMinFraction(float minFraction)640 public void setPanelScrimMinFraction(float minFraction) { 641 if (isNaN(minFraction)) { 642 throw new IllegalArgumentException("minFraction should not be NaN"); 643 } 644 mPanelScrimMinFraction = minFraction; 645 calculateAndUpdatePanelExpansion(); 646 } 647 calculateAndUpdatePanelExpansion()648 private void calculateAndUpdatePanelExpansion() { 649 float panelExpansionFraction = mRawPanelExpansionFraction; 650 if (mPanelScrimMinFraction < 1.0f) { 651 panelExpansionFraction = Math.max( 652 (mRawPanelExpansionFraction - mPanelScrimMinFraction) 653 / (1.0f - mPanelScrimMinFraction), 654 0); 655 } 656 657 if (mPanelExpansionFraction != panelExpansionFraction) { 658 if (panelExpansionFraction != 0f 659 && mKeyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) { 660 mAnimatingPanelExpansionOnUnlock = true; 661 } else if (panelExpansionFraction == 0f) { 662 mAnimatingPanelExpansionOnUnlock = false; 663 } 664 665 mPanelExpansionFraction = panelExpansionFraction; 666 667 boolean relevantState = (mState == ScrimState.UNLOCKED 668 || mState == ScrimState.KEYGUARD 669 || mState == ScrimState.DREAMING 670 || mState == ScrimState.SHADE_LOCKED 671 || mState == ScrimState.PULSING); 672 if (!(relevantState && mExpansionAffectsAlpha) || mAnimatingPanelExpansionOnUnlock) { 673 return; 674 } 675 applyAndDispatchState(); 676 } 677 } 678 679 /** 680 * Set the amount of progress we are currently in if we're transitioning to the full shade. 681 * 0.0f means we're not transitioning yet, while 1 means we're all the way in the full 682 * shade. 683 * 684 * @param progress the progress for all scrims. 685 * @param lockScreenNotificationsProgress the progress specifically for the notifications scrim. 686 */ setTransitionToFullShadeProgress(float progress, float lockScreenNotificationsProgress)687 public void setTransitionToFullShadeProgress(float progress, 688 float lockScreenNotificationsProgress) { 689 if (progress != mTransitionToFullShadeProgress || lockScreenNotificationsProgress 690 != mTransitionToLockScreenFullShadeNotificationsProgress) { 691 mTransitionToFullShadeProgress = progress; 692 mTransitionToLockScreenFullShadeNotificationsProgress = lockScreenNotificationsProgress; 693 setTransitionToFullShade(progress > 0.0f || lockScreenNotificationsProgress > 0.0f); 694 applyAndDispatchState(); 695 } 696 } 697 698 /** 699 * Set if we're currently transitioning to the full shade 700 */ setTransitionToFullShade(boolean transitioning)701 private void setTransitionToFullShade(boolean transitioning) { 702 if (transitioning != mTransitioningToFullShade) { 703 mTransitioningToFullShade = transitioning; 704 } 705 } 706 707 708 /** 709 * Set bounds for notifications background, all coordinates are absolute 710 */ setNotificationsBounds(float left, float top, float right, float bottom)711 public void setNotificationsBounds(float left, float top, float right, float bottom) { 712 if (mClipsQsScrim) { 713 // notification scrim's rounded corners are anti-aliased, but clipping of the QS/behind 714 // scrim can't be and it's causing jagged corners. That's why notification scrim needs 715 // to overlap QS scrim by one pixel horizontally (left - 1 and right + 1) 716 // see: b/186644628 717 mNotificationsScrim.setDrawableBounds(left - 1, top, right + 1, bottom); 718 mScrimBehind.setBottomEdgePosition((int) top); 719 } else { 720 mNotificationsScrim.setDrawableBounds(left, top, right, bottom); 721 } 722 } 723 724 /** 725 * Sets the amount of vertical over scroll that should be performed on the notifications scrim. 726 */ setNotificationsOverScrollAmount(int overScrollAmount)727 public void setNotificationsOverScrollAmount(int overScrollAmount) { 728 mNotificationsScrim.setTranslationY(overScrollAmount); 729 } 730 731 /** 732 * Current state of the QuickSettings when pulling it from the top. 733 * 734 * @param expansionFraction From 0 to 1 where 0 means collapsed and 1 expanded. 735 * @param qsPanelBottomY Absolute Y position of qs panel bottom 736 */ setQsPosition(float expansionFraction, int qsPanelBottomY)737 public void setQsPosition(float expansionFraction, int qsPanelBottomY) { 738 if (isNaN(expansionFraction)) { 739 return; 740 } 741 expansionFraction = ShadeInterpolation.getNotificationScrimAlpha(expansionFraction); 742 boolean qsBottomVisible = qsPanelBottomY > 0; 743 if (mQsExpansion != expansionFraction || mQsBottomVisible != qsBottomVisible) { 744 mQsExpansion = expansionFraction; 745 mQsBottomVisible = qsBottomVisible; 746 boolean relevantState = (mState == ScrimState.SHADE_LOCKED 747 || mState == ScrimState.KEYGUARD 748 || mState == ScrimState.PULSING); 749 if (!(relevantState && mExpansionAffectsAlpha)) { 750 return; 751 } 752 applyAndDispatchState(); 753 } 754 } 755 756 /** 757 * Updates the percentage of the bouncer which is hidden. 758 */ setBouncerHiddenFraction(@loatRangefrom = 0, to = 1) float bouncerHiddenAmount)759 public void setBouncerHiddenFraction(@FloatRange(from = 0, to = 1) float bouncerHiddenAmount) { 760 if (mBouncerHiddenFraction == bouncerHiddenAmount) { 761 return; 762 } 763 mBouncerHiddenFraction = bouncerHiddenAmount; 764 if (mState == ScrimState.DREAMING) { 765 // Only the dreaming state requires this for the scrim calculation, so we should 766 // only trigger an update if dreaming. 767 applyAndDispatchState(); 768 } 769 } 770 771 /** 772 * If QS and notification scrims should not overlap, and should be clipped to each other's 773 * bounds instead. 774 */ setClipsQsScrim(boolean clipScrim)775 public void setClipsQsScrim(boolean clipScrim) { 776 if (clipScrim == mClipsQsScrim) { 777 return; 778 } 779 mClipsQsScrim = clipScrim; 780 for (ScrimState state : ScrimState.values()) { 781 state.setClipQsScrim(mClipsQsScrim); 782 } 783 if (mScrimBehind != null) { 784 mScrimBehind.enableBottomEdgeConcave(mClipsQsScrim); 785 } 786 if (mState != ScrimState.UNINITIALIZED) { 787 // the clipScrimState has changed, let's reprepare ourselves 788 mState.prepare(mState); 789 applyAndDispatchState(); 790 } 791 } 792 793 @VisibleForTesting getClipQsScrim()794 public boolean getClipQsScrim() { 795 return mClipsQsScrim; 796 } 797 setOccludeAnimationPlaying(boolean occludeAnimationPlaying)798 public void setOccludeAnimationPlaying(boolean occludeAnimationPlaying) { 799 mOccludeAnimationPlaying = occludeAnimationPlaying; 800 801 for (ScrimState state : ScrimState.values()) { 802 state.setOccludeAnimationPlaying(occludeAnimationPlaying); 803 } 804 805 applyAndDispatchState(); 806 } 807 setOrAdaptCurrentAnimation(@ullable View scrim)808 private void setOrAdaptCurrentAnimation(@Nullable View scrim) { 809 if (scrim == null) { 810 return; 811 } 812 813 float alpha = getCurrentScrimAlpha(scrim); 814 boolean qsScrimPullingDown = scrim == mScrimBehind && mQsBottomVisible; 815 if (isAnimating(scrim) && !qsScrimPullingDown) { 816 // Adapt current animation. 817 ValueAnimator previousAnimator = (ValueAnimator) scrim.getTag(TAG_KEY_ANIM); 818 float previousEndValue = (Float) scrim.getTag(TAG_END_ALPHA); 819 float previousStartValue = (Float) scrim.getTag(TAG_START_ALPHA); 820 float relativeDiff = alpha - previousEndValue; 821 float newStartValue = previousStartValue + relativeDiff; 822 scrim.setTag(TAG_START_ALPHA, newStartValue); 823 scrim.setTag(TAG_END_ALPHA, alpha); 824 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); 825 } else { 826 // Set animation. 827 updateScrimColor(scrim, alpha, getCurrentScrimTint(scrim)); 828 } 829 } 830 applyState()831 private void applyState() { 832 mInFrontTint = mState.getFrontTint(); 833 mBehindTint = mState.getBehindTint(); 834 mNotificationsTint = mState.getNotifTint(); 835 836 mInFrontAlpha = mState.getFrontAlpha(); 837 mBehindAlpha = mState.getBehindAlpha(); 838 mNotificationsAlpha = mState.getNotifAlpha(); 839 840 assertAlphasValid(); 841 842 if (!mExpansionAffectsAlpha) { 843 return; 844 } 845 846 if (mState == ScrimState.UNLOCKED || mState == ScrimState.DREAMING) { 847 final boolean occluding = 848 mOccludeAnimationPlaying || mState.mLaunchingAffordanceWithPreview; 849 // Darken scrim as it's pulled down while unlocked. If we're unlocked but playing the 850 // screen off/occlusion animations, ignore expansion changes while those animations 851 // play. 852 if (!mScreenOffAnimationController.shouldExpandNotifications() 853 && !mAnimatingPanelExpansionOnUnlock 854 && !occluding) { 855 if (mTransparentScrimBackground) { 856 mBehindAlpha = 0; 857 mNotificationsAlpha = 0; 858 } else if (mClipsQsScrim) { 859 float behindFraction = getInterpolatedFraction(); 860 behindFraction = (float) Math.pow(behindFraction, 0.8f); 861 mBehindAlpha = 1; 862 mNotificationsAlpha = behindFraction * mDefaultScrimAlpha; 863 } else { 864 if (mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)) { 865 mBehindAlpha = mLargeScreenShadeInterpolator.getBehindScrimAlpha( 866 mPanelExpansionFraction * mDefaultScrimAlpha); 867 mNotificationsAlpha = 868 mLargeScreenShadeInterpolator.getNotificationScrimAlpha( 869 mPanelExpansionFraction); 870 } else { 871 // Behind scrim will finish fading in at 30% expansion. 872 float behindFraction = MathUtils 873 .constrainedMap(0f, 1f, 0f, 0.3f, mPanelExpansionFraction); 874 mBehindAlpha = behindFraction * mDefaultScrimAlpha; 875 // Delay fade-in of notification scrim a bit further, to coincide with the 876 // behind scrim finishing fading in. 877 // Also to coincide with the view starting to fade in, otherwise the empty 878 // panel can be quite jarring. 879 mNotificationsAlpha = MathUtils 880 .constrainedMap(0f, 1f, 0.3f, 0.75f, mPanelExpansionFraction); 881 } 882 } 883 mBehindTint = mState.getBehindTint(); 884 mInFrontAlpha = 0; 885 } 886 887 if (mState == ScrimState.DREAMING 888 && mBouncerHiddenFraction != KeyguardBouncerConstants.EXPANSION_HIDDEN) { 889 final float interpolatedFraction = 890 BouncerPanelExpansionCalculator.aboutToShowBouncerProgress( 891 mBouncerHiddenFraction); 892 mBehindAlpha = MathUtils.lerp(mDefaultScrimAlpha, mBehindAlpha, 893 interpolatedFraction); 894 mBehindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getBehindTint(), 895 mBehindTint, 896 interpolatedFraction); 897 } 898 } else if (mState == ScrimState.AUTH_SCRIMMED_SHADE) { 899 mNotificationsAlpha = (float) Math.pow(getInterpolatedFraction(), 0.8f); 900 } else if (mState == ScrimState.KEYGUARD || mState == ScrimState.SHADE_LOCKED 901 || mState == ScrimState.PULSING) { 902 Pair<Integer, Float> result = calculateBackStateForState(mState); 903 int behindTint = result.first; 904 float behindAlpha = result.second; 905 if (mTransitionToFullShadeProgress > 0.0f) { 906 Pair<Integer, Float> shadeResult = calculateBackStateForState( 907 ScrimState.SHADE_LOCKED); 908 behindAlpha = MathUtils.lerp(behindAlpha, shadeResult.second, 909 mTransitionToFullShadeProgress); 910 behindTint = ColorUtils.blendARGB(behindTint, shadeResult.first, 911 mTransitionToFullShadeProgress); 912 } 913 mInFrontAlpha = mState.getFrontAlpha(); 914 if (mClipsQsScrim) { 915 mNotificationsAlpha = behindAlpha; 916 mNotificationsTint = behindTint; 917 mBehindAlpha = 1; 918 mBehindTint = Color.BLACK; 919 } else { 920 mBehindAlpha = behindAlpha; 921 if (mState == ScrimState.KEYGUARD && mTransitionToFullShadeProgress > 0.0f) { 922 mNotificationsAlpha = MathUtils 923 .saturate(mTransitionToLockScreenFullShadeNotificationsProgress); 924 } else if (mState == ScrimState.SHADE_LOCKED) { 925 // going from KEYGUARD to SHADE_LOCKED state 926 mNotificationsAlpha = getInterpolatedFraction(); 927 } else { 928 mNotificationsAlpha = Math.max(1.0f - getInterpolatedFraction(), mQsExpansion); 929 } 930 mNotificationsTint = mState.getNotifTint(); 931 mBehindTint = behindTint; 932 } 933 934 // At the end of a launch animation over the lockscreen, the state is either KEYGUARD or 935 // SHADE_LOCKED and this code is called. We have to set the notification alpha to 0 936 // otherwise there is a flicker to its previous value. 937 boolean hideNotificationScrim = (mState == ScrimState.KEYGUARD 938 && mTransitionToFullShadeProgress == 0 939 && mQsExpansion == 0 940 && !mClipsQsScrim); 941 if (mKeyguardOccluded || hideNotificationScrim) { 942 mNotificationsAlpha = 0; 943 } 944 } 945 if (mState != ScrimState.UNLOCKED) { 946 mAnimatingPanelExpansionOnUnlock = false; 947 } 948 949 assertAlphasValid(); 950 } 951 assertAlphasValid()952 private void assertAlphasValid() { 953 if (isNaN(mBehindAlpha) || isNaN(mInFrontAlpha) || isNaN(mNotificationsAlpha)) { 954 throw new IllegalStateException("Scrim opacity is NaN for state: " + mState 955 + ", front: " + mInFrontAlpha + ", back: " + mBehindAlpha + ", notif: " 956 + mNotificationsAlpha); 957 } 958 } 959 calculateBackStateForState(ScrimState state)960 private Pair<Integer, Float> calculateBackStateForState(ScrimState state) { 961 // Either darken of make the scrim transparent when you 962 // pull down the shade 963 float interpolatedFract = getInterpolatedFraction(); 964 965 float stateBehind = mClipsQsScrim ? state.getNotifAlpha() : state.getBehindAlpha(); 966 float behindAlpha; 967 int behindTint = state.getBehindTint(); 968 if (mDarkenWhileDragging) { 969 behindAlpha = MathUtils.lerp(mDefaultScrimAlpha, stateBehind, 970 interpolatedFract); 971 } else { 972 behindAlpha = MathUtils.lerp(0 /* start */, stateBehind, 973 interpolatedFract); 974 } 975 if (mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()) { 976 if (mClipsQsScrim) { 977 behindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getNotifTint(), 978 state.getNotifTint(), interpolatedFract); 979 } else { 980 behindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getBehindTint(), 981 state.getBehindTint(), interpolatedFract); 982 } 983 } 984 if (mQsExpansion > 0) { 985 behindAlpha = MathUtils.lerp(behindAlpha, mDefaultScrimAlpha, mQsExpansion); 986 float tintProgress = mQsExpansion; 987 if (mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()) { 988 // this is case of - on lockscreen - going from expanded QS to bouncer. 989 // Because mQsExpansion is already interpolated and transition between tints 990 // is too slow, we want to speed it up and make it more aligned to bouncer 991 // showing up progress. This issue is visible on large screens, both portrait and 992 // split shade because then transition is between very different tints 993 tintProgress = BouncerPanelExpansionCalculator 994 .showBouncerProgress(mPanelExpansionFraction); 995 } 996 int stateTint = mClipsQsScrim ? ScrimState.SHADE_LOCKED.getNotifTint() 997 : ScrimState.SHADE_LOCKED.getBehindTint(); 998 behindTint = ColorUtils.blendARGB(behindTint, stateTint, tintProgress); 999 } 1000 1001 // If the keyguard is going away, we should not be opaque. 1002 if (mKeyguardStateController.isKeyguardGoingAway()) { 1003 behindAlpha = 0f; 1004 } 1005 1006 return new Pair<>(behindTint, behindAlpha); 1007 } 1008 1009 applyAndDispatchState()1010 private void applyAndDispatchState() { 1011 applyState(); 1012 if (mUpdatePending) { 1013 return; 1014 } 1015 setOrAdaptCurrentAnimation(mScrimBehind); 1016 setOrAdaptCurrentAnimation(mNotificationsScrim); 1017 setOrAdaptCurrentAnimation(mScrimInFront); 1018 dispatchBackScrimState(mScrimBehind.getViewAlpha()); 1019 1020 // Reset wallpaper timeout if it's already timeout like expanding panel while PULSING 1021 // and docking. 1022 if (mWallpaperVisibilityTimedOut) { 1023 mWallpaperVisibilityTimedOut = false; 1024 DejankUtils.postAfterTraversal(() -> { 1025 mTimeTicker.schedule(mDozeParameters.getWallpaperAodDuration(), 1026 AlarmTimeout.MODE_IGNORE_IF_SCHEDULED); 1027 }); 1028 } 1029 } 1030 1031 /** 1032 * Sets the front scrim opacity in AOD so it's not as bright. 1033 * <p> 1034 * Displays usually don't support multiple dimming settings when in low power mode. 1035 * The workaround is to modify the front scrim opacity when in AOD, so it's not as 1036 * bright when you're at the movies or lying down on bed. 1037 * <p> 1038 * This value will be lost during transitions and only updated again after the the 1039 * device is dozing when the light sensor is on. 1040 */ setAodFrontScrimAlpha(float alpha)1041 public void setAodFrontScrimAlpha(float alpha) { 1042 if (mInFrontAlpha != alpha && shouldUpdateFrontScrimAlpha()) { 1043 mInFrontAlpha = alpha; 1044 updateScrims(); 1045 } 1046 1047 mState.AOD.setAodFrontScrimAlpha(alpha); 1048 mState.PULSING.setAodFrontScrimAlpha(alpha); 1049 } 1050 shouldUpdateFrontScrimAlpha()1051 private boolean shouldUpdateFrontScrimAlpha() { 1052 if (mState == ScrimState.AOD 1053 && (mDozeParameters.getAlwaysOn() || mDockManager.isDocked())) { 1054 return true; 1055 } 1056 1057 if (mState == ScrimState.PULSING) { 1058 return true; 1059 } 1060 1061 return false; 1062 } 1063 1064 /** 1065 * If the lock screen sensor is active. 1066 */ setWakeLockScreenSensorActive(boolean active)1067 public void setWakeLockScreenSensorActive(boolean active) { 1068 for (ScrimState state : ScrimState.values()) { 1069 state.setWakeLockScreenSensorActive(active); 1070 } 1071 1072 if (mState == ScrimState.PULSING) { 1073 float newBehindAlpha = mState.getBehindAlpha(); 1074 if (mBehindAlpha != newBehindAlpha) { 1075 mBehindAlpha = newBehindAlpha; 1076 if (isNaN(mBehindAlpha)) { 1077 throw new IllegalStateException("Scrim opacity is NaN for state: " + mState 1078 + ", back: " + mBehindAlpha); 1079 } 1080 updateScrims(); 1081 } 1082 } 1083 } 1084 scheduleUpdate()1085 protected void scheduleUpdate() { 1086 if (mUpdatePending || mScrimBehind == null) return; 1087 1088 // Make sure that a frame gets scheduled. 1089 mScrimBehind.invalidate(); 1090 mScrimBehind.getViewTreeObserver().addOnPreDrawListener(this); 1091 mUpdatePending = true; 1092 } 1093 updateScrims()1094 protected void updateScrims() { 1095 // Make sure we have the right gradients and their opacities will satisfy GAR. 1096 if (mNeedsDrawableColorUpdate) { 1097 mNeedsDrawableColorUpdate = false; 1098 // Only animate scrim color if the scrim view is actually visible 1099 boolean animateScrimInFront = mScrimInFront.getViewAlpha() != 0 && !mBlankScreen; 1100 boolean animateBehindScrim = mScrimBehind.getViewAlpha() != 0 && !mBlankScreen; 1101 boolean animateScrimNotifications = mNotificationsScrim.getViewAlpha() != 0 1102 && !mBlankScreen; 1103 1104 mScrimInFront.setColors(mColors, animateScrimInFront); 1105 mScrimBehind.setColors(mColors, animateBehindScrim); 1106 mNotificationsScrim.setColors(mColors, animateScrimNotifications); 1107 1108 dispatchBackScrimState(mScrimBehind.getViewAlpha()); 1109 } 1110 1111 // We want to override the back scrim opacity for the AOD state 1112 // when it's time to fade the wallpaper away. 1113 boolean aodWallpaperTimeout = (mState == ScrimState.AOD || mState == ScrimState.PULSING) 1114 && mWallpaperVisibilityTimedOut; 1115 // We also want to hide FLAG_SHOW_WHEN_LOCKED activities under the scrim. 1116 boolean hideFlagShowWhenLockedActivities = 1117 (mState == ScrimState.PULSING || mState == ScrimState.AOD) 1118 && mKeyguardOccluded; 1119 if (aodWallpaperTimeout || hideFlagShowWhenLockedActivities) { 1120 mBehindAlpha = 1; 1121 } 1122 // Prevent notification scrim flicker when transitioning away from keyguard. 1123 if (mKeyguardStateController.isKeyguardGoingAway()) { 1124 mNotificationsAlpha = 0; 1125 } 1126 1127 // Prevent flickering for activities above keyguard and quick settings in keyguard. 1128 if (mKeyguardOccluded 1129 && (mState == ScrimState.KEYGUARD || mState == ScrimState.SHADE_LOCKED)) { 1130 mBehindAlpha = 0; 1131 mNotificationsAlpha = 0; 1132 } 1133 1134 setScrimAlpha(mScrimInFront, mInFrontAlpha); 1135 setScrimAlpha(mScrimBehind, mBehindAlpha); 1136 setScrimAlpha(mNotificationsScrim, mNotificationsAlpha); 1137 1138 // The animation could have all already finished, let's call onFinished just in case 1139 onFinished(mState); 1140 dispatchScrimsVisible(); 1141 } 1142 dispatchBackScrimState(float alpha)1143 private void dispatchBackScrimState(float alpha) { 1144 // When clipping QS, the notification scrim is the one that feels behind. 1145 // mScrimBehind will be drawing black and its opacity will always be 1. 1146 if (mClipsQsScrim && mQsBottomVisible) { 1147 alpha = mNotificationsAlpha; 1148 } 1149 mScrimStateListener.accept(mState, alpha, mScrimInFront.getColors()); 1150 } 1151 dispatchScrimsVisible()1152 private void dispatchScrimsVisible() { 1153 final ScrimView backScrim = mClipsQsScrim ? mNotificationsScrim : mScrimBehind; 1154 final int currentScrimVisibility; 1155 if (mScrimInFront.getViewAlpha() == 1 || backScrim.getViewAlpha() == 1) { 1156 currentScrimVisibility = OPAQUE; 1157 } else if (mScrimInFront.getViewAlpha() == 0 && backScrim.getViewAlpha() == 0) { 1158 currentScrimVisibility = TRANSPARENT; 1159 } else { 1160 currentScrimVisibility = SEMI_TRANSPARENT; 1161 } 1162 1163 if (mScrimsVisibility != currentScrimVisibility) { 1164 mScrimsVisibility = currentScrimVisibility; 1165 mScrimVisibleListener.accept(currentScrimVisibility); 1166 } 1167 } 1168 getInterpolatedFraction()1169 private float getInterpolatedFraction() { 1170 if (mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()) { 1171 return BouncerPanelExpansionCalculator 1172 .aboutToShowBouncerProgress(mPanelExpansionFraction); 1173 } 1174 return ShadeInterpolation.getNotificationScrimAlpha(mPanelExpansionFraction); 1175 } 1176 setScrimAlpha(ScrimView scrim, float alpha)1177 private void setScrimAlpha(ScrimView scrim, float alpha) { 1178 if (alpha == 0f) { 1179 scrim.setClickable(false); 1180 } else { 1181 // Eat touch events (unless dozing). 1182 scrim.setClickable(mState != ScrimState.AOD); 1183 } 1184 updateScrim(scrim, alpha); 1185 } 1186 getScrimName(ScrimView scrim)1187 private String getScrimName(ScrimView scrim) { 1188 if (scrim == mScrimInFront) { 1189 return "front_scrim"; 1190 } else if (scrim == mScrimBehind) { 1191 return "behind_scrim"; 1192 } else if (scrim == mNotificationsScrim) { 1193 return "notifications_scrim"; 1194 } 1195 return "unknown_scrim"; 1196 } 1197 updateScrimColor(View scrim, float alpha, int tint)1198 private void updateScrimColor(View scrim, float alpha, int tint) { 1199 alpha = Math.max(0, Math.min(1.0f, alpha)); 1200 if (scrim instanceof ScrimView) { 1201 ScrimView scrimView = (ScrimView) scrim; 1202 if (DEBUG_MODE) { 1203 tint = getDebugScrimTint(scrimView); 1204 } 1205 1206 Trace.traceCounter(Trace.TRACE_TAG_APP, getScrimName(scrimView) + "_alpha", 1207 (int) (alpha * 255)); 1208 1209 Trace.traceCounter(Trace.TRACE_TAG_APP, getScrimName(scrimView) + "_tint", 1210 Color.alpha(tint)); 1211 scrimView.setTint(tint); 1212 if (!mIsBouncerToGoneTransitionRunning) { 1213 scrimView.setViewAlpha(alpha); 1214 } 1215 } else { 1216 scrim.setAlpha(alpha); 1217 } 1218 dispatchScrimsVisible(); 1219 } 1220 getDebugScrimTint(ScrimView scrim)1221 private int getDebugScrimTint(ScrimView scrim) { 1222 if (scrim == mScrimBehind) return DEBUG_BEHIND_TINT; 1223 if (scrim == mScrimInFront) return DEBUG_FRONT_TINT; 1224 if (scrim == mNotificationsScrim) return DEBUG_NOTIFICATIONS_TINT; 1225 throw new RuntimeException("scrim can't be matched with known scrims"); 1226 } 1227 startScrimAnimation(final View scrim, float current)1228 private void startScrimAnimation(final View scrim, float current) { 1229 ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f); 1230 if (mAnimatorListener != null) { 1231 anim.addListener(mAnimatorListener); 1232 } 1233 final int initialScrimTint = scrim instanceof ScrimView ? ((ScrimView) scrim).getTint() : 1234 Color.TRANSPARENT; 1235 anim.addUpdateListener(animation -> { 1236 final float startAlpha = (Float) scrim.getTag(TAG_START_ALPHA); 1237 final float animAmount = (float) animation.getAnimatedValue(); 1238 final int finalScrimTint = getCurrentScrimTint(scrim); 1239 final float finalScrimAlpha = getCurrentScrimAlpha(scrim); 1240 float alpha = MathUtils.lerp(startAlpha, finalScrimAlpha, animAmount); 1241 alpha = MathUtils.constrain(alpha, 0f, 1f); 1242 int tint = ColorUtils.blendARGB(initialScrimTint, finalScrimTint, animAmount); 1243 updateScrimColor(scrim, alpha, tint); 1244 dispatchScrimsVisible(); 1245 }); 1246 anim.setInterpolator(mInterpolator); 1247 anim.setStartDelay(mAnimationDelay); 1248 anim.setDuration(mAnimationDuration); 1249 anim.addListener(new AnimatorListenerAdapter() { 1250 private final ScrimState mLastState = mState; 1251 private final Callback mLastCallback = mCallback; 1252 1253 @Override 1254 public void onAnimationEnd(Animator animation) { 1255 scrim.setTag(TAG_KEY_ANIM, null); 1256 onFinished(mLastCallback, mLastState); 1257 1258 dispatchScrimsVisible(); 1259 } 1260 }); 1261 1262 // Cache alpha values because we might want to update this animator in the future if 1263 // the user expands the panel while the animation is still running. 1264 scrim.setTag(TAG_START_ALPHA, current); 1265 scrim.setTag(TAG_END_ALPHA, getCurrentScrimAlpha(scrim)); 1266 1267 scrim.setTag(TAG_KEY_ANIM, anim); 1268 anim.start(); 1269 } 1270 getCurrentScrimAlpha(View scrim)1271 private float getCurrentScrimAlpha(View scrim) { 1272 if (scrim == mScrimInFront) { 1273 return mInFrontAlpha; 1274 } else if (scrim == mScrimBehind) { 1275 return mBehindAlpha; 1276 } else if (scrim == mNotificationsScrim) { 1277 return mNotificationsAlpha; 1278 } else { 1279 throw new IllegalArgumentException("Unknown scrim view"); 1280 } 1281 } 1282 getCurrentScrimTint(View scrim)1283 private int getCurrentScrimTint(View scrim) { 1284 if (scrim == mScrimInFront) { 1285 return mInFrontTint; 1286 } else if (scrim == mScrimBehind) { 1287 return mBehindTint; 1288 } else if (scrim == mNotificationsScrim) { 1289 return mNotificationsTint; 1290 } else { 1291 throw new IllegalArgumentException("Unknown scrim view"); 1292 } 1293 } 1294 1295 @Override onPreDraw()1296 public boolean onPreDraw() { 1297 mScrimBehind.getViewTreeObserver().removeOnPreDrawListener(this); 1298 mUpdatePending = false; 1299 if (mCallback != null) { 1300 mCallback.onStart(); 1301 } 1302 updateScrims(); 1303 return true; 1304 } 1305 1306 /** 1307 * @param state that finished 1308 */ onFinished(ScrimState state)1309 private void onFinished(ScrimState state) { 1310 onFinished(mCallback, state); 1311 } 1312 onFinished(Callback callback, ScrimState state)1313 private void onFinished(Callback callback, ScrimState state) { 1314 if (mPendingFrameCallback != null) { 1315 // No animations can finish while we're waiting on the blanking to finish 1316 return; 1317 1318 } 1319 if (isAnimating(mScrimBehind) 1320 || isAnimating(mNotificationsScrim) 1321 || isAnimating(mScrimInFront)) { 1322 if (callback != null && callback != mCallback) { 1323 // Since we only notify the callback that we're finished once everything has 1324 // finished, we need to make sure that any changing callbacks are also invoked 1325 callback.onFinished(); 1326 } 1327 return; 1328 } 1329 if (mWakeLockHeld) { 1330 mWakeLock.release(TAG); 1331 mWakeLockHeld = false; 1332 } 1333 1334 if (callback != null) { 1335 callback.onFinished(); 1336 1337 if (callback == mCallback) { 1338 mCallback = null; 1339 } 1340 } 1341 1342 // When unlocking with fingerprint, we'll fade the scrims from black to transparent. 1343 // At the end of the animation we need to remove the tint. 1344 if (state == ScrimState.UNLOCKED) { 1345 mInFrontTint = Color.TRANSPARENT; 1346 mBehindTint = mState.getBehindTint(); 1347 mNotificationsTint = mState.getNotifTint(); 1348 updateScrimColor(mScrimInFront, mInFrontAlpha, mInFrontTint); 1349 updateScrimColor(mScrimBehind, mBehindAlpha, mBehindTint); 1350 updateScrimColor(mNotificationsScrim, mNotificationsAlpha, mNotificationsTint); 1351 } 1352 } 1353 isAnimating(@ullable View scrim)1354 private boolean isAnimating(@Nullable View scrim) { 1355 return scrim != null && scrim.getTag(TAG_KEY_ANIM) != null; 1356 } 1357 1358 @VisibleForTesting setAnimatorListener(Animator.AnimatorListener animatorListener)1359 void setAnimatorListener(Animator.AnimatorListener animatorListener) { 1360 mAnimatorListener = animatorListener; 1361 } 1362 updateScrim(ScrimView scrim, float alpha)1363 private void updateScrim(ScrimView scrim, float alpha) { 1364 final float currentAlpha = scrim.getViewAlpha(); 1365 1366 ValueAnimator previousAnimator = ViewState.getChildTag(scrim, TAG_KEY_ANIM); 1367 if (previousAnimator != null) { 1368 // Previous animators should always be cancelled. Not doing so would cause 1369 // overlap, especially on states that don't animate, leading to flickering, 1370 // and in the worst case, an internal state that doesn't represent what 1371 // transitionTo requested. 1372 cancelAnimator(previousAnimator); 1373 } 1374 1375 if (mPendingFrameCallback != null) { 1376 // Display is off and we're waiting. 1377 return; 1378 } else if (mBlankScreen) { 1379 // Need to blank the display before continuing. 1380 blankDisplay(); 1381 return; 1382 } else if (!mScreenBlankingCallbackCalled) { 1383 // Not blanking the screen. Letting the callback know that we're ready 1384 // to replace what was on the screen before. 1385 if (mCallback != null) { 1386 mCallback.onDisplayBlanked(); 1387 mScreenBlankingCallbackCalled = true; 1388 } 1389 } 1390 1391 if (scrim == mScrimBehind) { 1392 dispatchBackScrimState(alpha); 1393 } 1394 1395 final boolean wantsAlphaUpdate = alpha != currentAlpha; 1396 final boolean wantsTintUpdate = scrim.getTint() != getCurrentScrimTint(scrim); 1397 1398 if (wantsAlphaUpdate || wantsTintUpdate) { 1399 if (mAnimateChange) { 1400 startScrimAnimation(scrim, currentAlpha); 1401 } else { 1402 // update the alpha directly 1403 updateScrimColor(scrim, alpha, getCurrentScrimTint(scrim)); 1404 } 1405 } 1406 } 1407 cancelAnimator(ValueAnimator previousAnimator)1408 private void cancelAnimator(ValueAnimator previousAnimator) { 1409 if (previousAnimator != null) { 1410 previousAnimator.cancel(); 1411 } 1412 } 1413 blankDisplay()1414 private void blankDisplay() { 1415 updateScrimColor(mScrimInFront, 1, Color.BLACK); 1416 1417 // Notify callback that the screen is completely black and we're 1418 // ready to change the display power mode 1419 mPendingFrameCallback = () -> { 1420 if (mCallback != null) { 1421 mCallback.onDisplayBlanked(); 1422 mScreenBlankingCallbackCalled = true; 1423 } 1424 1425 mBlankingTransitionRunnable = () -> { 1426 mBlankingTransitionRunnable = null; 1427 mPendingFrameCallback = null; 1428 mBlankScreen = false; 1429 // Try again. 1430 updateScrims(); 1431 }; 1432 1433 // Setting power states can happen after we push out the frame. Make sure we 1434 // stay fully opaque until the power state request reaches the lower levels. 1435 final int delay = mScreenOn ? 32 : 500; 1436 if (DEBUG) { 1437 Log.d(TAG, "Fading out scrims with delay: " + delay); 1438 } 1439 mHandler.postDelayed(mBlankingTransitionRunnable, delay); 1440 }; 1441 doOnTheNextFrame(mPendingFrameCallback); 1442 } 1443 1444 /** 1445 * Executes a callback after the frame has hit the display. 1446 * 1447 * @param callback What to run. 1448 */ 1449 @VisibleForTesting doOnTheNextFrame(Runnable callback)1450 protected void doOnTheNextFrame(Runnable callback) { 1451 // Just calling View#postOnAnimation isn't enough because the frame might not have reached 1452 // the display yet. A timeout is the safest solution. 1453 mScrimBehind.postOnAnimationDelayed(callback, 32 /* delayMillis */); 1454 } 1455 setScrimBehindChangeRunnable(Runnable changeRunnable)1456 public void setScrimBehindChangeRunnable(Runnable changeRunnable) { 1457 // TODO: remove this. This is necessary because of an order-of-operations limitation. 1458 // The fix is to move more of these class into @CentralSurfacesScope 1459 if (mScrimBehind == null) { 1460 mScrimBehindChangeRunnable = changeRunnable; 1461 } else { 1462 mScrimBehind.setChangeRunnable(changeRunnable, mMainExecutor); 1463 } 1464 } 1465 setCurrentUser(int currentUser)1466 public void setCurrentUser(int currentUser) { 1467 // Don't care in the base class. 1468 } 1469 updateThemeColors()1470 private void updateThemeColors() { 1471 if (mScrimBehind == null) return; 1472 int background = Utils.getColorAttr(mScrimBehind.getContext(), 1473 android.R.attr.colorBackgroundFloating).getDefaultColor(); 1474 int accent = Utils.getColorAccent(mScrimBehind.getContext()).getDefaultColor(); 1475 mColors.setMainColor(background); 1476 mColors.setSecondaryColor(accent); 1477 mColors.setSupportsDarkText( 1478 ColorUtils.calculateContrast(mColors.getMainColor(), Color.WHITE) > 4.5); 1479 mNeedsDrawableColorUpdate = true; 1480 } 1481 onThemeChanged()1482 private void onThemeChanged() { 1483 updateThemeColors(); 1484 scheduleUpdate(); 1485 } 1486 1487 @Override dump(PrintWriter pw, String[] args)1488 public void dump(PrintWriter pw, String[] args) { 1489 pw.println(" ScrimController: "); 1490 pw.print(" state: "); 1491 pw.println(mState); 1492 pw.println(" mClipQsScrim = " + mState.mClipQsScrim); 1493 1494 pw.print(" frontScrim:"); 1495 pw.print(" viewAlpha="); 1496 pw.print(mScrimInFront.getViewAlpha()); 1497 pw.print(" alpha="); 1498 pw.print(mInFrontAlpha); 1499 pw.print(" tint=0x"); 1500 pw.println(Integer.toHexString(mScrimInFront.getTint())); 1501 1502 pw.print(" behindScrim:"); 1503 pw.print(" viewAlpha="); 1504 pw.print(mScrimBehind.getViewAlpha()); 1505 pw.print(" alpha="); 1506 pw.print(mBehindAlpha); 1507 pw.print(" tint=0x"); 1508 pw.println(Integer.toHexString(mScrimBehind.getTint())); 1509 1510 pw.print(" notificationsScrim:"); 1511 pw.print(" viewAlpha="); 1512 pw.print(mNotificationsScrim.getViewAlpha()); 1513 pw.print(" alpha="); 1514 pw.print(mNotificationsAlpha); 1515 pw.print(" tint=0x"); 1516 pw.println(Integer.toHexString(mNotificationsScrim.getTint())); 1517 pw.print(" expansionProgress="); 1518 pw.println(mTransitionToLockScreenFullShadeNotificationsProgress); 1519 1520 pw.print(" mDefaultScrimAlpha="); 1521 pw.println(mDefaultScrimAlpha); 1522 pw.print(" mPanelExpansionFraction="); 1523 pw.println(mPanelExpansionFraction); 1524 pw.print(" mExpansionAffectsAlpha="); 1525 pw.println(mExpansionAffectsAlpha); 1526 1527 pw.print(" mState.getMaxLightRevealScrimAlpha="); 1528 pw.println(mState.getMaxLightRevealScrimAlpha()); 1529 } 1530 setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode)1531 public void setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode) { 1532 mWallpaperSupportsAmbientMode = wallpaperSupportsAmbientMode; 1533 ScrimState[] states = ScrimState.values(); 1534 for (int i = 0; i < states.length; i++) { 1535 states[i].setWallpaperSupportsAmbientMode(wallpaperSupportsAmbientMode); 1536 } 1537 } 1538 1539 /** 1540 * Interrupts blanking transitions once the display notifies that it's already on. 1541 */ onScreenTurnedOn()1542 public void onScreenTurnedOn() { 1543 mScreenOn = true; 1544 if (mHandler.hasCallbacks(mBlankingTransitionRunnable)) { 1545 if (DEBUG) { 1546 Log.d(TAG, "Shorter blanking because screen turned on. All good."); 1547 } 1548 mHandler.removeCallbacks(mBlankingTransitionRunnable); 1549 mBlankingTransitionRunnable.run(); 1550 } 1551 } 1552 onScreenTurnedOff()1553 public void onScreenTurnedOff() { 1554 mScreenOn = false; 1555 } 1556 setExpansionAffectsAlpha(boolean expansionAffectsAlpha)1557 public void setExpansionAffectsAlpha(boolean expansionAffectsAlpha) { 1558 mExpansionAffectsAlpha = expansionAffectsAlpha; 1559 } 1560 setKeyguardOccluded(boolean keyguardOccluded)1561 public void setKeyguardOccluded(boolean keyguardOccluded) { 1562 if (mKeyguardOccluded == keyguardOccluded) { 1563 return; 1564 } 1565 mKeyguardOccluded = keyguardOccluded; 1566 updateScrims(); 1567 } 1568 setHasBackdrop(boolean hasBackdrop)1569 public void setHasBackdrop(boolean hasBackdrop) { 1570 for (ScrimState state : ScrimState.values()) { 1571 state.setHasBackdrop(hasBackdrop); 1572 } 1573 1574 // Backdrop event may arrive after state was already applied, 1575 // in this case, back-scrim needs to be re-evaluated 1576 if (mState == ScrimState.AOD || mState == ScrimState.PULSING) { 1577 float newBehindAlpha = mState.getBehindAlpha(); 1578 if (isNaN(newBehindAlpha)) { 1579 throw new IllegalStateException("Scrim opacity is NaN for state: " + mState 1580 + ", back: " + mBehindAlpha); 1581 } 1582 if (mBehindAlpha != newBehindAlpha) { 1583 mBehindAlpha = newBehindAlpha; 1584 updateScrims(); 1585 } 1586 } 1587 } 1588 setKeyguardFadingAway(boolean fadingAway, long duration)1589 private void setKeyguardFadingAway(boolean fadingAway, long duration) { 1590 for (ScrimState state : ScrimState.values()) { 1591 state.setKeyguardFadingAway(fadingAway, duration); 1592 } 1593 } 1594 setLaunchingAffordanceWithPreview(boolean launchingAffordanceWithPreview)1595 public void setLaunchingAffordanceWithPreview(boolean launchingAffordanceWithPreview) { 1596 for (ScrimState state : ScrimState.values()) { 1597 state.setLaunchingAffordanceWithPreview(launchingAffordanceWithPreview); 1598 } 1599 } 1600 1601 public interface Callback { onStart()1602 default void onStart() { 1603 } 1604 onDisplayBlanked()1605 default void onDisplayBlanked() { 1606 } 1607 onFinished()1608 default void onFinished() { 1609 } 1610 onCancelled()1611 default void onCancelled() { 1612 } 1613 } 1614 1615 /** 1616 * Simple keyguard callback that updates scrims when keyguard visibility changes. 1617 */ 1618 private class KeyguardVisibilityCallback extends KeyguardUpdateMonitorCallback { 1619 1620 @Override onKeyguardVisibilityChanged(boolean visible)1621 public void onKeyguardVisibilityChanged(boolean visible) { 1622 mNeedsDrawableColorUpdate = true; 1623 scheduleUpdate(); 1624 } 1625 } 1626 } 1627