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