1 /* <lambda>null2 * Copyright (C) 2020 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 18 19 import android.animation.Animator 20 import android.animation.AnimatorListenerAdapter 21 import android.animation.ValueAnimator 22 import android.os.SystemClock 23 import android.util.IndentingPrintWriter 24 import android.util.Log 25 import android.util.MathUtils 26 import android.view.Choreographer 27 import android.view.Display 28 import android.view.View 29 import androidx.annotation.VisibleForTesting 30 import androidx.dynamicanimation.animation.FloatPropertyCompat 31 import androidx.dynamicanimation.animation.SpringAnimation 32 import androidx.dynamicanimation.animation.SpringForce 33 import com.android.app.animation.Interpolators 34 import com.android.app.tracing.coroutines.TrackTracer 35 import com.android.systemui.Dumpable 36 import com.android.systemui.Flags 37 import com.android.systemui.Flags.spatialModelAppPushback 38 import com.android.systemui.animation.ShadeInterpolation 39 import com.android.systemui.dagger.SysUISingleton 40 import com.android.systemui.dagger.qualifiers.Application 41 import com.android.systemui.dump.DumpManager 42 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor 43 import com.android.systemui.plugins.statusbar.StatusBarStateController 44 import com.android.systemui.shade.ShadeExpansionChangeEvent 45 import com.android.systemui.shade.ShadeExpansionListener 46 import com.android.systemui.shade.data.repository.ShadeDisplaysRepository 47 import com.android.systemui.shade.domain.interactor.ShadeModeInteractor 48 import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround 49 import com.android.systemui.statusbar.phone.BiometricUnlockController 50 import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK 51 import com.android.systemui.statusbar.phone.DozeParameters 52 import com.android.systemui.statusbar.phone.ScrimController 53 import com.android.systemui.statusbar.policy.KeyguardStateController 54 import com.android.systemui.util.WallpaperController 55 import com.android.systemui.wallpapers.domain.interactor.WallpaperInteractor 56 import com.android.systemui.window.domain.interactor.WindowRootViewBlurInteractor 57 import com.android.wm.shell.appzoomout.AppZoomOut 58 import dagger.Lazy 59 import java.io.PrintWriter 60 import java.util.Optional 61 import javax.inject.Inject 62 import kotlin.math.max 63 import kotlin.math.sign 64 import kotlinx.coroutines.CoroutineScope 65 import kotlinx.coroutines.launch 66 67 /** 68 * Responsible for blurring the notification shade window, and applying a zoom effect to the 69 * wallpaper. 70 */ 71 @SysUISingleton 72 class NotificationShadeDepthController 73 @Inject 74 constructor( 75 private val statusBarStateController: StatusBarStateController, 76 private val blurUtils: BlurUtils, 77 private val biometricUnlockController: BiometricUnlockController, 78 private val keyguardStateController: KeyguardStateController, 79 private val keyguardInteractor: KeyguardInteractor, 80 private val choreographer: Choreographer, 81 private val wallpaperController: WallpaperController, 82 private val wallpaperInteractor: WallpaperInteractor, 83 private val notificationShadeWindowController: NotificationShadeWindowController, 84 private val dozeParameters: DozeParameters, 85 private val shadeModeInteractor: ShadeModeInteractor, 86 private val windowRootViewBlurInteractor: WindowRootViewBlurInteractor, 87 private val appZoomOutOptional: Optional<AppZoomOut>, 88 @Application private val applicationScope: CoroutineScope, 89 dumpManager: DumpManager, 90 private val shadeDisplaysRepository: Lazy<ShadeDisplaysRepository>, 91 ) : ShadeExpansionListener, Dumpable { 92 companion object { 93 private const val WAKE_UP_ANIMATION_ENABLED = true 94 private const val VELOCITY_SCALE = 100f 95 private const val MAX_VELOCITY = 3000f 96 private const val MIN_VELOCITY = -MAX_VELOCITY 97 private const val INTERACTION_BLUR_FRACTION = 0.8f 98 private const val ANIMATION_BLUR_FRACTION = 1f - INTERACTION_BLUR_FRACTION 99 private const val TRANSITION_THRESHOLD = 0.98f 100 private const val TAG = "DepthController" 101 } 102 103 lateinit var root: View 104 private var keyguardAnimator: Animator? = null 105 private var notificationAnimator: Animator? = null 106 private var updateScheduled: Boolean = false 107 @VisibleForTesting var shadeExpansion = 0f 108 private var isClosed: Boolean = true 109 private var isOpen: Boolean = false 110 private var isBlurred: Boolean = false 111 private var listeners = mutableListOf<DepthListener>() 112 113 private var prevTracking: Boolean = false 114 private var prevTimestamp: Long = -1 115 private var prevShadeDirection = 0 116 private var prevShadeVelocity = 0f 117 private var prevDozeAmount: Float = 0f 118 @VisibleForTesting var wallpaperSupportsAmbientMode: Boolean = false 119 // tracks whether app launch transition is in progress. This involves two independent factors 120 // that control blur, shade expansion and app launch animation from outside sysui. 121 // They can complete out of order, this flag will be reset by the animation that finishes later. 122 private var appLaunchTransitionIsInProgress = false 123 124 // Only for dumpsys 125 private var lastAppliedBlur = 0 126 127 val maxBlurRadiusPx = blurUtils.maxBlurRadius 128 129 // Shade expansion offset that happens when pulling down on a HUN. 130 var panelPullDownMinFraction = 0f 131 132 var shadeAnimation = DepthAnimation() 133 134 @VisibleForTesting var brightnessMirrorSpring = DepthAnimation() 135 var brightnessMirrorVisible: Boolean = false 136 set(value) { 137 field = value 138 brightnessMirrorSpring.animateTo( 139 if (value) blurUtils.blurRadiusOfRatio(1f).toInt() else 0 140 ) 141 } 142 143 var qsPanelExpansion = 0f 144 set(value) { 145 if (value.isNaN()) { 146 Log.w(TAG, "Invalid qs expansion") 147 return 148 } 149 if (field == value) return 150 field = value 151 scheduleUpdate() 152 } 153 154 /** How much we're transitioning to the full shade */ 155 var transitionToFullShadeProgress = 0f 156 set(value) { 157 if (field == value) return 158 field = value 159 scheduleUpdate() 160 } 161 162 /** 163 * When launching an app from the shade, the animations progress should affect how blurry the 164 * shade is, overriding the expansion amount. 165 * 166 * TODO(b/399617511): remove this once [Flags.notificationShadeBlur] is launched and the Shade 167 * closing is actually instantaneous. 168 */ 169 var blursDisabledForAppLaunch: Boolean = false 170 set(value) { 171 if (field == value) { 172 return 173 } 174 // Set this to true now, this will be reset when the next shade expansion finishes or 175 // when the app launch finishes, whichever happens later. 176 if (value) { 177 appLaunchTransitionIsInProgress = true 178 } else { 179 // App was launching and now it has finished launching 180 if (shadeExpansion == 0.0f) { 181 // this means shade expansion finished before app launch was done. 182 // reset the flag here 183 appLaunchTransitionIsInProgress = false 184 } 185 } 186 field = value 187 scheduleUpdate() 188 189 if (shadeExpansion == 0f && shadeAnimation.radius == 0f) { 190 return 191 } 192 // Do not remove blurs when we're re-enabling them 193 if (!value) { 194 return 195 } 196 197 if (Flags.notificationShadeBlur()) { 198 shadeAnimation.skipTo(0) 199 } else { 200 shadeAnimation.animateTo(0) 201 shadeAnimation.finishIfRunning() 202 } 203 } 204 @Deprecated( 205 message = 206 "This might get reset to false before shade expansion is fully done, " + 207 "consider using areBlursDisabledForAppLaunch" 208 ) 209 get() = field 210 211 private var zoomOutCalculatedFromShadeRadius: Float = 0.0f 212 213 /** We're unlocking, and should not blur as the panel expansion changes. */ 214 var blursDisabledForUnlock: Boolean = false 215 set(value) { 216 if (field == value) return 217 field = value 218 scheduleUpdate() 219 } 220 221 private val areBlursDisabledForAppLaunch: Boolean 222 get() = 223 blursDisabledForAppLaunch || 224 (Flags.bouncerUiRevamp() && appLaunchTransitionIsInProgress) 225 226 /** Force stop blur effect when necessary. */ 227 private var scrimsVisible: Boolean = false 228 set(value) { 229 if (field == value) return 230 field = value 231 scheduleUpdate() 232 } 233 234 private data class WakeAndUnlockBlurData(val radius: Float, val useZoom: Boolean = true) 235 236 private val isShadeOnDefaultDisplay: Boolean 237 get() = 238 if (ShadeWindowGoesAround.isEnabled) { 239 shadeDisplaysRepository.get().displayId.value == Display.DEFAULT_DISPLAY 240 } else { 241 true 242 } 243 244 /** Blur radius of the wake and unlock animation on this frame, and whether to zoom out. */ 245 private var wakeAndUnlockBlurData = WakeAndUnlockBlurData(0f) 246 set(value) { 247 if (field == value) return 248 field = value 249 scheduleUpdate() 250 } 251 252 private fun computeBlurAndZoomOut(): Pair<Int, Float> { 253 val animationRadius = 254 MathUtils.constrain( 255 shadeAnimation.radius, 256 blurUtils.minBlurRadius, 257 blurUtils.maxBlurRadius, 258 ) 259 val expansionRadius = 260 blurUtils.blurRadiusOfRatio( 261 ShadeInterpolation.getNotificationScrimAlpha( 262 if (shouldApplyShadeBlur()) shadeExpansion else 0f 263 ) 264 ) 265 var combinedBlur = 266 (expansionRadius * INTERACTION_BLUR_FRACTION + 267 animationRadius * ANIMATION_BLUR_FRACTION) 268 val qsExpandedRatio = 269 ShadeInterpolation.getNotificationScrimAlpha(qsPanelExpansion) * shadeExpansion 270 combinedBlur = max(combinedBlur, blurUtils.blurRadiusOfRatio(qsExpandedRatio)) 271 combinedBlur = max(combinedBlur, blurUtils.blurRadiusOfRatio(transitionToFullShadeProgress)) 272 var shadeRadius = max(combinedBlur, wakeAndUnlockBlurData.radius) 273 274 if (areBlursDisabledForAppLaunch || blursDisabledForUnlock) { 275 shadeRadius = 0f 276 } 277 278 var blur = shadeRadius.toInt() 279 // If the blur comes from waking up, we don't want to zoom out the background 280 val zoomOut = 281 when { 282 // When the shade is in another display, we don't want to zoom out the background. 283 // Only the default display is supported right now. 284 !isShadeOnDefaultDisplay -> 0f 285 shadeRadius != wakeAndUnlockBlurData.radius || wakeAndUnlockBlurData.useZoom -> 286 blurRadiusToZoomOut(blurRadius = shadeRadius) 287 else -> 0f 288 } 289 // Make blur be 0 if it is necessary to stop blur effect. 290 if (scrimsVisible) { 291 if (!Flags.notificationShadeBlur()) { 292 blur = 0 293 } 294 } 295 296 if (!blurUtils.supportsBlursOnWindows()) { 297 blur = 0 298 } 299 300 // Brightness slider removes blur, but doesn't affect zooms 301 blur = (blur * (1f - brightnessMirrorSpring.ratio)).toInt() 302 303 return Pair(blur, zoomOut) 304 } 305 306 private fun blurRadiusToZoomOut(blurRadius: Float): Float { 307 var zoomOut = MathUtils.saturate(blurUtils.ratioOfBlurRadius(blurRadius)) 308 if (shadeModeInteractor.isSplitShade) { 309 zoomOut = 0f 310 } 311 312 if (scrimsVisible) { 313 zoomOut = 0f 314 } 315 return zoomOut 316 } 317 318 private val shouldBlurBeOpaque: Boolean 319 get() = 320 if (Flags.notificationShadeBlur()) false 321 else scrimsVisible && !areBlursDisabledForAppLaunch 322 323 /** Callback that updates the window blur value and is called only once per frame. */ 324 @VisibleForTesting 325 val updateBlurCallback = 326 Choreographer.FrameCallback { 327 updateScheduled = false 328 val (blur, zoomOutFromShadeRadius) = computeBlurAndZoomOut() 329 val opaque = shouldBlurBeOpaque 330 TrackTracer.instantForGroup("shade", "shade_blur_radius", blur) 331 blurUtils.applyBlur(root.viewRootImpl, blur, opaque) 332 onBlurApplied(blur, zoomOutFromShadeRadius) 333 } 334 335 private fun onBlurApplied(appliedBlurRadius: Int, zoomOutFromShadeRadius: Float) { 336 lastAppliedBlur = appliedBlurRadius 337 onZoomOutChanged(zoomOutFromShadeRadius) 338 listeners.forEach { it.onBlurRadiusChanged(appliedBlurRadius) } 339 notificationShadeWindowController.setBackgroundBlurRadius(appliedBlurRadius) 340 } 341 342 private fun onZoomOutChanged(zoomOutFromShadeRadius: Float) { 343 TrackTracer.instantForGroup("shade", "zoom_out", zoomOutFromShadeRadius) 344 Log.v(TAG, "onZoomOutChanged $zoomOutFromShadeRadius") 345 wallpaperController.setNotificationShadeZoom(zoomOutFromShadeRadius) 346 if (spatialModelAppPushback()) { 347 appZoomOutOptional.ifPresent { appZoomOut -> 348 appZoomOut.setProgress(zoomOutFromShadeRadius) 349 } 350 keyguardInteractor.setZoomOut(zoomOutFromShadeRadius) 351 } 352 } 353 354 private val applyZoomOutForFrame = 355 Choreographer.FrameCallback { 356 updateScheduled = false 357 val (_, zoomOutFromShadeRadius) = computeBlurAndZoomOut() 358 onZoomOutChanged(zoomOutFromShadeRadius) 359 } 360 361 /** Animate blurs when unlocking. */ 362 private val keyguardStateCallback = 363 object : KeyguardStateController.Callback { 364 override fun onKeyguardFadingAwayChanged() { 365 if ( 366 !keyguardStateController.isKeyguardFadingAway || 367 biometricUnlockController.mode != MODE_WAKE_AND_UNLOCK 368 ) { 369 return 370 } 371 372 keyguardAnimator?.cancel() 373 keyguardAnimator = 374 ValueAnimator.ofFloat(1f, 0f).apply { 375 // keyguardStateController.keyguardFadingAwayDuration might be zero when 376 // unlock by fingerprint due to there is no window container, see 377 // AppTransition#goodToGo. We use DozeParameters.wallpaperFadeOutDuration as 378 // an alternative. 379 duration = dozeParameters.wallpaperFadeOutDuration 380 startDelay = keyguardStateController.keyguardFadingAwayDelay 381 interpolator = Interpolators.FAST_OUT_SLOW_IN 382 addUpdateListener { animation: ValueAnimator -> 383 wakeAndUnlockBlurData = 384 WakeAndUnlockBlurData( 385 blurUtils.blurRadiusOfRatio(animation.animatedValue as Float) 386 ) 387 } 388 addListener( 389 object : AnimatorListenerAdapter() { 390 override fun onAnimationEnd(animation: Animator) { 391 keyguardAnimator = null 392 wakeAndUnlockBlurData = WakeAndUnlockBlurData(0f) 393 } 394 } 395 ) 396 start() 397 } 398 } 399 400 override fun onKeyguardShowingChanged() { 401 if (keyguardStateController.isShowing) { 402 keyguardAnimator?.cancel() 403 notificationAnimator?.cancel() 404 } 405 } 406 } 407 408 private val statusBarStateCallback = 409 object : StatusBarStateController.StateListener { 410 override fun onStateChanged(newState: Int) { 411 updateShadeAnimationBlur( 412 shadeExpansion, 413 prevTracking, 414 prevShadeVelocity, 415 prevShadeDirection, 416 ) 417 scheduleUpdate() 418 } 419 420 override fun onDozingChanged(isDozing: Boolean) { 421 if (isDozing) { 422 shadeAnimation.finishIfRunning() 423 brightnessMirrorSpring.finishIfRunning() 424 } 425 } 426 427 override fun onDozeAmountChanged(linear: Float, eased: Float) { 428 prevDozeAmount = eased 429 updateWakeBlurRadius(prevDozeAmount) 430 } 431 } 432 433 private fun updateWakeBlurRadius(ratio: Float) { 434 wakeAndUnlockBlurData = WakeAndUnlockBlurData(getNewWakeBlurRadius(ratio), false) 435 } 436 437 private fun getNewWakeBlurRadius(ratio: Float): Float { 438 return if (!wallpaperSupportsAmbientMode) { 439 0f 440 } else { 441 blurUtils.blurRadiusOfRatio(ratio) 442 } 443 } 444 445 init { 446 dumpManager.registerCriticalDumpable(javaClass.name, this) 447 if (WAKE_UP_ANIMATION_ENABLED) { 448 keyguardStateController.addCallback(keyguardStateCallback) 449 } 450 statusBarStateController.addCallback(statusBarStateCallback) 451 notificationShadeWindowController.setScrimsVisibilityListener { 452 // Stop blur effect when scrims is opaque to avoid unnecessary GPU composition. 453 visibility -> 454 scrimsVisible = visibility == ScrimController.OPAQUE 455 } 456 shadeAnimation.setStiffness(SpringForce.STIFFNESS_LOW) 457 shadeAnimation.setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY) 458 applicationScope.launch { 459 wallpaperInteractor.wallpaperSupportsAmbientMode.collect { supported -> 460 wallpaperSupportsAmbientMode = supported 461 if ( 462 getNewWakeBlurRadius(prevDozeAmount) == wakeAndUnlockBlurData.radius && 463 !wakeAndUnlockBlurData.useZoom 464 ) { 465 // Update wake and unlock radius only if the previous value comes from wake-up. 466 updateWakeBlurRadius(prevDozeAmount) 467 } 468 } 469 } 470 initBlurListeners() 471 } 472 473 private fun initBlurListeners() { 474 if (!Flags.bouncerUiRevamp()) return 475 476 windowRootViewBlurInteractor.registerBlurAppliedListener { appliedBlurRadius -> 477 if (updateScheduled) { 478 // Process the blur applied event only if we scheduled the update 479 TrackTracer.instantForGroup("shade", "shade_blur_radius", appliedBlurRadius) 480 updateScheduled = false 481 onBlurApplied(appliedBlurRadius, zoomOutCalculatedFromShadeRadius) 482 } else { 483 // Try scheduling an update now, maybe our blur request will be scheduled now. 484 scheduleUpdate() 485 } 486 } 487 488 applicationScope.launch { 489 windowRootViewBlurInteractor.isBlurCurrentlySupported.collect { supported -> 490 if (supported) { 491 // when battery saver changes, try scheduling an update. 492 scheduleUpdate() 493 } else { 494 // when blur becomes unsupported, no more updates will be scheduled, 495 // reset updateScheduled state. 496 updateScheduled = false 497 // reset blur and internal state to 0 498 onBlurApplied(0, 0.0f) 499 } 500 } 501 } 502 } 503 504 fun addListener(listener: DepthListener) { 505 listeners.add(listener) 506 } 507 508 fun removeListener(listener: DepthListener) { 509 listeners.remove(listener) 510 } 511 512 /** Update blurs when pulling down the shade */ 513 override fun onPanelExpansionChanged(event: ShadeExpansionChangeEvent) { 514 val rawFraction = event.fraction 515 val tracking = event.tracking 516 val timestamp = SystemClock.elapsedRealtimeNanos() 517 val expansion = 518 MathUtils.saturate( 519 (rawFraction - panelPullDownMinFraction) / (1f - panelPullDownMinFraction) 520 ) 521 522 if (shadeExpansion == expansion && prevTracking == tracking) { 523 prevTimestamp = timestamp 524 return 525 } 526 527 var deltaTime = 1f 528 if (prevTimestamp < 0) { 529 prevTimestamp = timestamp 530 } else { 531 deltaTime = 532 MathUtils.constrain(((timestamp - prevTimestamp) / 1E9).toFloat(), 0.00001f, 1f) 533 } 534 535 val diff = expansion - shadeExpansion 536 val shadeDirection = sign(diff).toInt() 537 val shadeVelocity = 538 MathUtils.constrain(VELOCITY_SCALE * diff / deltaTime, MIN_VELOCITY, MAX_VELOCITY) 539 if (expansion == 0.0f && appLaunchTransitionIsInProgress && !blursDisabledForAppLaunch) { 540 // Shade expansion finished but the app launch is already done, then this should mark 541 // the transition as done. 542 Log.d(TAG, "appLaunchTransitionIsInProgress is now false from shade expansion event") 543 appLaunchTransitionIsInProgress = false 544 } 545 546 updateShadeAnimationBlur(expansion, tracking, shadeVelocity, shadeDirection) 547 548 prevShadeDirection = shadeDirection 549 prevShadeVelocity = shadeVelocity 550 shadeExpansion = expansion 551 prevTracking = tracking 552 prevTimestamp = timestamp 553 554 scheduleUpdate() 555 } 556 557 fun onTransitionAnimationProgress(progress: Float) { 558 if (!Flags.notificationShadeBlur() || !Flags.moveTransitionAnimationLayer()) return 559 // Because the Shade takes a few frames to actually trigger the unblur after a transition 560 // has ended, we need to disable it manually, or the opening window itself will be blurred 561 // for a few frames due to relative ordering. We do this towards the end, so that the 562 // window is already covering the background and the unblur is not visible. 563 if (progress >= TRANSITION_THRESHOLD && shadeAnimation.radius > 0) { 564 blursDisabledForAppLaunch = true 565 } 566 } 567 568 fun onTransitionAnimationEnd() { 569 if (!Flags.notificationShadeBlur() || !Flags.moveTransitionAnimationLayer()) return 570 blursDisabledForAppLaunch = false 571 } 572 573 private fun updateShadeAnimationBlur( 574 expansion: Float, 575 tracking: Boolean, 576 velocity: Float, 577 direction: Int, 578 ) { 579 if (shouldApplyShadeBlur()) { 580 if (expansion > 0f) { 581 // Blur view if user starts animating in the shade. 582 if (isClosed) { 583 animateBlur(true, velocity) 584 isClosed = false 585 } 586 587 // If we were blurring out and the user stopped the animation, blur view. 588 if (tracking && !isBlurred) { 589 animateBlur(true, 0f) 590 } 591 592 // If shade is being closed and the user isn't interacting with it, un-blur. 593 if (!tracking && direction < 0 && isBlurred) { 594 animateBlur(false, velocity) 595 } 596 597 if (expansion == 1f) { 598 if (!isOpen) { 599 isOpen = true 600 // If shade is open and view is not blurred, blur. 601 if (!isBlurred) { 602 animateBlur(true, velocity) 603 } 604 } 605 } else { 606 isOpen = false 607 } 608 // Automatic animation when the user closes the shade. 609 } else if (!isClosed) { 610 isClosed = true 611 // If shade is closed and view is not blurred, blur. 612 if (isBlurred) { 613 animateBlur(false, velocity) 614 } 615 } 616 } else { 617 animateBlur(false, 0f) 618 isClosed = true 619 isOpen = false 620 } 621 } 622 623 private fun animateBlur(blur: Boolean, velocity: Float) { 624 isBlurred = blur 625 626 val targetBlurNormalized = 627 if (blur && shouldApplyShadeBlur()) { 628 1f 629 } else { 630 0f 631 } 632 633 shadeAnimation.setStartVelocity(velocity) 634 shadeAnimation.animateTo(blurUtils.blurRadiusOfRatio(targetBlurNormalized).toInt()) 635 } 636 637 private fun scheduleUpdate() { 638 val (blur, zoomOutFromShadeRadius) = computeBlurAndZoomOut() 639 zoomOutCalculatedFromShadeRadius = zoomOutFromShadeRadius 640 if (Flags.bouncerUiRevamp() || Flags.glanceableHubBlurredBackground()) { 641 if (windowRootViewBlurInteractor.isBlurCurrentlySupported.value) { 642 updateScheduled = 643 windowRootViewBlurInteractor.requestBlurForShade(blur, shouldBlurBeOpaque) 644 return 645 } 646 // When blur is not supported, zoom out still needs to happen when scheduleUpdate 647 // is invoked and a separate frame callback has to be wired-up to support that. 648 if (!updateScheduled) { 649 updateScheduled = true 650 choreographer.postFrameCallback(applyZoomOutForFrame) 651 } 652 return 653 } 654 if (updateScheduled) { 655 return 656 } 657 updateScheduled = true 658 blurUtils.prepareBlur(root.viewRootImpl, blur) 659 choreographer.postFrameCallback(updateBlurCallback) 660 } 661 662 /** 663 * Should blur be applied to the shade currently. This is mainly used to make sure that on the 664 * lockscreen, the wallpaper isn't blurred. 665 */ 666 private fun shouldApplyShadeBlur(): Boolean { 667 val state = statusBarStateController.state 668 return (state == StatusBarState.SHADE || state == StatusBarState.SHADE_LOCKED) && 669 !keyguardStateController.isKeyguardFadingAway 670 } 671 672 override fun dump(pw: PrintWriter, args: Array<out String>) { 673 IndentingPrintWriter(pw, " ").let { 674 it.println("StatusBarWindowBlurController:") 675 it.increaseIndent() 676 it.println("shadeExpansion: $shadeExpansion") 677 it.println("shouldApplyShadeBlur: ${shouldApplyShadeBlur()}") 678 it.println("shadeAnimation: ${shadeAnimation.radius}") 679 it.println("brightnessMirrorRadius: ${brightnessMirrorSpring.radius}") 680 it.println("wakeAndUnlockBlurRadius: ${wakeAndUnlockBlurData.radius}") 681 it.println("wakeAndUnlockBlurUsesZoom: ${wakeAndUnlockBlurData.useZoom}") 682 it.println("blursDisabledForAppLaunch: $blursDisabledForAppLaunch") 683 it.println("appLaunchTransitionIsInProgress: $appLaunchTransitionIsInProgress") 684 it.println("qsPanelExpansion: $qsPanelExpansion") 685 it.println("transitionToFullShadeProgress: $transitionToFullShadeProgress") 686 it.println("lastAppliedBlur: $lastAppliedBlur") 687 } 688 } 689 690 /** 691 * Animation helper that smoothly animates the depth using a spring and deals with frame 692 * invalidation. 693 */ 694 inner class DepthAnimation() { 695 /** Blur radius visible on the UI, in pixels. */ 696 var radius = 0f 697 698 /** Depth ratio of the current blur radius. */ 699 val ratio 700 get() = blurUtils.ratioOfBlurRadius(radius) 701 702 /** Radius that we're animating to. */ 703 private var pendingRadius = -1 704 705 private var springAnimation = 706 SpringAnimation( 707 this, 708 object : FloatPropertyCompat<DepthAnimation>("blurRadius") { 709 override fun setValue(rect: DepthAnimation?, value: Float) { 710 radius = value 711 scheduleUpdate() 712 } 713 714 override fun getValue(rect: DepthAnimation?): Float { 715 return radius 716 } 717 }, 718 ) 719 720 init { 721 springAnimation.spring = SpringForce(0.0f) 722 springAnimation.spring.dampingRatio = SpringForce.DAMPING_RATIO_NO_BOUNCY 723 springAnimation.spring.stiffness = SpringForce.STIFFNESS_HIGH 724 springAnimation.addEndListener { _, _, _, _ -> pendingRadius = -1 } 725 } 726 727 /** 728 * Starts an animation to [newRadius], or updates the current one if already ongoing. 729 * IMPORTANT: do NOT use this method + [finishIfRunning] to instantaneously change the value 730 * of the animation. The change will NOT be instantaneous. Use [skipTo] instead. 731 * 732 * Explanation: 733 * 1. If idle, [SpringAnimation.animateToFinalPosition] requests a start to the animation. 734 * 2. On the first frame after an idle animation is requested to start, the animation simply 735 * acquires the starting value and does nothing else. 736 * 3. [SpringAnimation.skipToEnd] requests a fast-forward to the end value, but this happens 737 * during calculation of the next animation value. Because on the first frame no such 738 * calculation happens (point #2), there is one lagging frame where we still see the old 739 * value. 740 */ 741 fun animateTo(newRadius: Int) { 742 if (pendingRadius == newRadius) { 743 return 744 } 745 pendingRadius = newRadius 746 springAnimation.animateToFinalPosition(newRadius.toFloat()) 747 } 748 749 /** 750 * Instantaneously set a new blur radius to this animation. Always use this instead of 751 * [animateTo] and [finishIfRunning] to make sure that the change takes effect in the next 752 * frame. See the doc for [animateTo] for an explanation. 753 */ 754 fun skipTo(newRadius: Int) { 755 if (pendingRadius == newRadius) return 756 pendingRadius = newRadius 757 springAnimation.cancel() 758 springAnimation.setStartValue(newRadius.toFloat()) 759 springAnimation.animateToFinalPosition(newRadius.toFloat()) 760 } 761 762 fun finishIfRunning() { 763 if (springAnimation.isRunning) { 764 springAnimation.skipToEnd() 765 } 766 } 767 768 fun setStiffness(stiffness: Float) { 769 springAnimation.spring.stiffness = stiffness 770 } 771 772 fun setDampingRatio(dampingRation: Float) { 773 springAnimation.spring.dampingRatio = dampingRation 774 } 775 776 fun setStartVelocity(velocity: Float) { 777 springAnimation.setStartVelocity(velocity) 778 } 779 } 780 781 /** Invoked when changes are needed in z-space */ 782 interface DepthListener { 783 fun onBlurRadiusChanged(blurRadius: Int) {} 784 } 785 } 786