blob: 25f1a974bc36bc7b7e078ed223ffa5a8313fc75a [file] [log] [blame]
Lucas Dupin43d01242020-02-03 11:58:33 -08001/*
2 * 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
17package com.android.systemui.statusbar
18
19import android.animation.Animator
20import android.animation.AnimatorListenerAdapter
21import android.animation.ValueAnimator
Lucas Dupin13f4b8a2020-02-19 13:41:52 -080022import android.app.WallpaperManager
Lucas Dupin7ee64cc2020-03-31 14:48:43 -070023import android.util.Log
Lucas Dupin43d01242020-02-03 11:58:33 -080024import android.view.Choreographer
25import android.view.View
Lucas Dupinb7685c52020-03-14 17:41:19 -070026import androidx.annotation.VisibleForTesting
Lucas Dupin15a75142020-03-05 18:11:26 -080027import androidx.dynamicanimation.animation.FloatPropertyCompat
28import androidx.dynamicanimation.animation.SpringAnimation
29import androidx.dynamicanimation.animation.SpringForce
Lucas Dupin43d01242020-02-03 11:58:33 -080030import com.android.internal.util.IndentingPrintWriter
Lucas Dupin43d01242020-02-03 11:58:33 -080031import com.android.systemui.Dumpable
32import com.android.systemui.Interpolators
Ned Burnsaaeb44b2020-02-12 23:48:26 -050033import com.android.systemui.dump.DumpManager
Lucas Dupinb7685c52020-03-14 17:41:19 -070034import com.android.systemui.plugins.statusbar.StatusBarStateController
Lucas Dupinb16c49a2020-04-01 21:46:18 -070035import com.android.systemui.statusbar.notification.ActivityLaunchAnimator
Lucas Dupin43d01242020-02-03 11:58:33 -080036import com.android.systemui.statusbar.phone.BiometricUnlockController
37import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK
Lucas Dupin1bd84d32020-03-13 17:28:18 -070038import com.android.systemui.statusbar.phone.NotificationShadeWindowController
Lucas Dupin43d01242020-02-03 11:58:33 -080039import com.android.systemui.statusbar.phone.PanelExpansionListener
Ahan Wu765f1782020-04-02 23:08:15 +080040import com.android.systemui.statusbar.phone.ScrimController
Lucas Dupin43d01242020-02-03 11:58:33 -080041import com.android.systemui.statusbar.policy.KeyguardStateController
42import java.io.FileDescriptor
43import java.io.PrintWriter
44import javax.inject.Inject
45import javax.inject.Singleton
46import kotlin.math.max
47
48/**
49 * Controller responsible for statusbar window blur.
50 */
51@Singleton
Lucas Dupin13f4b8a2020-02-19 13:41:52 -080052class NotificationShadeDepthController @Inject constructor(
Lucas Dupinb7685c52020-03-14 17:41:19 -070053 private val statusBarStateController: StatusBarStateController,
Lucas Dupin43d01242020-02-03 11:58:33 -080054 private val blurUtils: BlurUtils,
55 private val biometricUnlockController: BiometricUnlockController,
56 private val keyguardStateController: KeyguardStateController,
Lucas Dupin43d01242020-02-03 11:58:33 -080057 private val choreographer: Choreographer,
Lucas Dupin13f4b8a2020-02-19 13:41:52 -080058 private val wallpaperManager: WallpaperManager,
Lucas Dupin1bd84d32020-03-13 17:28:18 -070059 private val notificationShadeWindowController: NotificationShadeWindowController,
Ned Burnsaaeb44b2020-02-12 23:48:26 -050060 dumpManager: DumpManager
Lucas Dupin43d01242020-02-03 11:58:33 -080061) : PanelExpansionListener, Dumpable {
62 companion object {
63 private const val WAKE_UP_ANIMATION_ENABLED = true
Lucas Dupin7ee64cc2020-03-31 14:48:43 -070064 private const val TAG = "DepthController"
Lucas Dupin43d01242020-02-03 11:58:33 -080065 }
66
67 lateinit var root: View
Santiago Etchebehere68eb53e2020-02-25 14:25:34 -080068 private var blurRoot: View? = null
Lucas Dupin43d01242020-02-03 11:58:33 -080069 private var keyguardAnimator: Animator? = null
70 private var notificationAnimator: Animator? = null
71 private var updateScheduled: Boolean = false
Lucas Dupinb7685c52020-03-14 17:41:19 -070072 private var shadeExpansion = 0f
Lucas Dupinb16c49a2020-04-01 21:46:18 -070073 private var ignoreShadeBlurUntilHidden: Boolean = false
Lucas Dupinb7685c52020-03-14 17:41:19 -070074 @VisibleForTesting
Lucas Dupinb079daa2020-03-24 15:56:23 -070075 var shadeSpring = DepthAnimation()
76 @VisibleForTesting
77 var globalActionsSpring = DepthAnimation()
Lucas Dupin3a8531e2020-04-06 17:18:09 -070078 var showingHomeControls: Boolean = false
Lucas Dupinb7685c52020-03-14 17:41:19 -070079
Lucas Dupinee92cc22020-03-31 20:45:42 -070080 @VisibleForTesting
81 var brightnessMirrorSpring = DepthAnimation()
82 var brightnessMirrorVisible: Boolean = false
83 set(value) {
84 field = value
85 brightnessMirrorSpring.animateTo(if (value) blurUtils.blurRadiusOfRatio(1f)
86 else 0)
87 }
88
Lucas Dupinb7685c52020-03-14 17:41:19 -070089 /**
Lucas Dupinb16c49a2020-04-01 21:46:18 -070090 * When launching an app from the shade, the animations progress should affect how blurry the
91 * shade is, overriding the expansion amount.
92 */
93 var notificationLaunchAnimationParams: ActivityLaunchAnimator.ExpandAnimationParameters? = null
94 set(value) {
95 field = value
96 if (value != null) {
97 scheduleUpdate()
98 return
99 }
100
101 if (shadeSpring.radius == 0) {
102 return
103 }
104 ignoreShadeBlurUntilHidden = true
105 shadeSpring.animateTo(0)
106 shadeSpring.finishIfRunning()
107 }
108
109 /**
Ahan Wu765f1782020-04-02 23:08:15 +0800110 * Force stop blur effect when necessary.
111 */
112 private var scrimsVisible: Boolean = false
113 set(value) {
114 if (field == value) return
115 field = value
116 scheduleUpdate()
117 }
118
119 /**
Lucas Dupinb7685c52020-03-14 17:41:19 -0700120 * Blur radius of the wake-up animation on this frame.
121 */
Lucas Dupin43d01242020-02-03 11:58:33 -0800122 private var wakeAndUnlockBlurRadius = 0
123 set(value) {
124 if (field == value) return
125 field = value
126 scheduleUpdate()
127 }
Lucas Dupin43d01242020-02-03 11:58:33 -0800128
129 /**
130 * Callback that updates the window blur value and is called only once per frame.
131 */
Lucas Dupin7ee64cc2020-03-31 14:48:43 -0700132 @VisibleForTesting
133 val updateBlurCallback = Choreographer.FrameCallback {
Lucas Dupin43d01242020-02-03 11:58:33 -0800134 updateScheduled = false
135
Lucas Dupinb16c49a2020-04-01 21:46:18 -0700136 var shadeRadius = max(shadeSpring.radius, wakeAndUnlockBlurRadius).toFloat()
137 shadeRadius *= 1f - brightnessMirrorSpring.ratio
138 val launchProgress = notificationLaunchAnimationParams?.linearProgress ?: 0f
139 shadeRadius *= (1f - launchProgress) * (1f - launchProgress)
140
141 if (ignoreShadeBlurUntilHidden) {
142 if (shadeRadius == 0f) {
143 ignoreShadeBlurUntilHidden = false
144 } else {
145 shadeRadius = 0f
146 }
147 }
Lucas Dupin3a8531e2020-04-06 17:18:09 -0700148
149 // Home controls have black background, this means that we should not have blur when they
150 // are fully visible, otherwise we'll enter Client Composition unnecessarily.
151 var globalActionsRadius = globalActionsSpring.radius
152 if (showingHomeControls) {
153 globalActionsRadius = 0
154 }
Ahan Wu765f1782020-04-02 23:08:15 +0800155 var blur = max(shadeRadius.toInt(), globalActionsRadius)
156
157 // Make blur be 0 if it is necessary to stop blur effect.
158 if (scrimsVisible) {
159 blur = 0
160 }
161
Santiago Etchebehere68eb53e2020-02-25 14:25:34 -0800162 blurUtils.applyBlur(blurRoot?.viewRootImpl ?: root.viewRootImpl, blur)
Lucas Dupin7ee64cc2020-03-31 14:48:43 -0700163 try {
164 wallpaperManager.setWallpaperZoomOut(root.windowToken,
165 blurUtils.ratioOfBlurRadius(blur))
166 } catch (e: IllegalArgumentException) {
167 Log.w(TAG, "Can't set zoom. Window is gone: ${root.windowToken}", e)
168 }
Lucas Dupin1bd84d32020-03-13 17:28:18 -0700169 notificationShadeWindowController.setBackgroundBlurRadius(blur)
Lucas Dupin43d01242020-02-03 11:58:33 -0800170 }
171
172 /**
173 * Animate blurs when unlocking.
174 */
175 private val keyguardStateCallback = object : KeyguardStateController.Callback {
176 override fun onKeyguardFadingAwayChanged() {
177 if (!keyguardStateController.isKeyguardFadingAway ||
178 biometricUnlockController.mode != MODE_WAKE_AND_UNLOCK) {
179 return
180 }
181
182 keyguardAnimator?.cancel()
183 keyguardAnimator = ValueAnimator.ofFloat(1f, 0f).apply {
184 duration = keyguardStateController.keyguardFadingAwayDuration
185 startDelay = keyguardStateController.keyguardFadingAwayDelay
186 interpolator = Interpolators.DECELERATE_QUINT
187 addUpdateListener { animation: ValueAnimator ->
188 wakeAndUnlockBlurRadius =
Lucas Dupin13f4b8a2020-02-19 13:41:52 -0800189 blurUtils.blurRadiusOfRatio(animation.animatedValue as Float)
Lucas Dupin43d01242020-02-03 11:58:33 -0800190 }
191 addListener(object : AnimatorListenerAdapter() {
192 override fun onAnimationEnd(animation: Animator?) {
193 keyguardAnimator = null
194 scheduleUpdate()
195 }
196 })
197 start()
198 }
199 }
200
201 override fun onKeyguardShowingChanged() {
202 if (keyguardStateController.isShowing) {
203 keyguardAnimator?.cancel()
204 notificationAnimator?.cancel()
205 }
206 }
207 }
208
Lucas Dupinb7685c52020-03-14 17:41:19 -0700209 private val statusBarStateCallback = object : StatusBarStateController.StateListener {
210 override fun onStateChanged(newState: Int) {
211 updateShadeBlur()
212 }
213
214 override fun onDozingChanged(isDozing: Boolean) {
Lucas Dupinb079daa2020-03-24 15:56:23 -0700215 if (isDozing) {
216 shadeSpring.finishIfRunning()
217 globalActionsSpring.finishIfRunning()
Lucas Dupinee92cc22020-03-31 20:45:42 -0700218 brightnessMirrorSpring.finishIfRunning()
Lucas Dupinb7685c52020-03-14 17:41:19 -0700219 }
220 }
Ahan Wu765f1782020-04-02 23:08:15 +0800221
222 override fun onDozeAmountChanged(linear: Float, eased: Float) {
223 wakeAndUnlockBlurRadius = blurUtils.blurRadiusOfRatio(eased)
224 }
Lucas Dupinb7685c52020-03-14 17:41:19 -0700225 }
226
Lucas Dupin43d01242020-02-03 11:58:33 -0800227 init {
Ned Burnsaaeb44b2020-02-12 23:48:26 -0500228 dumpManager.registerDumpable(javaClass.name, this)
Lucas Dupin43d01242020-02-03 11:58:33 -0800229 if (WAKE_UP_ANIMATION_ENABLED) {
230 keyguardStateController.addCallback(keyguardStateCallback)
231 }
Lucas Dupinb7685c52020-03-14 17:41:19 -0700232 statusBarStateController.addCallback(statusBarStateCallback)
Ahan Wu765f1782020-04-02 23:08:15 +0800233 notificationShadeWindowController.setScrimsVisibilityListener {
234 // Stop blur effect when scrims is opaque to avoid unnecessary GPU composition.
235 visibility -> scrimsVisible = visibility == ScrimController.OPAQUE
236 }
Lucas Dupin43d01242020-02-03 11:58:33 -0800237 }
238
239 /**
240 * Update blurs when pulling down the shade
241 */
242 override fun onPanelExpansionChanged(expansion: Float, tracking: Boolean) {
Lucas Dupinb7685c52020-03-14 17:41:19 -0700243 if (expansion == shadeExpansion) {
Lucas Dupin43d01242020-02-03 11:58:33 -0800244 return
245 }
Lucas Dupinb7685c52020-03-14 17:41:19 -0700246 shadeExpansion = expansion
247 updateShadeBlur()
248 }
Lucas Dupin43d01242020-02-03 11:58:33 -0800249
Lucas Dupinb7685c52020-03-14 17:41:19 -0700250 private fun updateShadeBlur() {
Lucas Dupin43d01242020-02-03 11:58:33 -0800251 var newBlur = 0
Ahan Wu765f1782020-04-02 23:08:15 +0800252 val state = statusBarStateController.state
Lucas Dupinb0b7a042020-04-15 12:23:26 -0700253 if ((state == StatusBarState.SHADE || state == StatusBarState.SHADE_LOCKED) &&
254 !keyguardStateController.isKeyguardFadingAway) {
Lucas Dupin029cc6a2020-04-13 17:11:15 +0000255 newBlur = blurUtils.blurRadiusOfRatio(shadeExpansion)
Lucas Dupin43d01242020-02-03 11:58:33 -0800256 }
Lucas Dupinb079daa2020-03-24 15:56:23 -0700257 shadeSpring.animateTo(newBlur)
Lucas Dupin43d01242020-02-03 11:58:33 -0800258 }
259
Santiago Etchebehere68eb53e2020-02-25 14:25:34 -0800260 private fun scheduleUpdate(viewToBlur: View? = null) {
Lucas Dupin43d01242020-02-03 11:58:33 -0800261 if (updateScheduled) {
262 return
263 }
264 updateScheduled = true
Santiago Etchebehere68eb53e2020-02-25 14:25:34 -0800265 blurRoot = viewToBlur
Lucas Dupin43d01242020-02-03 11:58:33 -0800266 choreographer.postFrameCallback(updateBlurCallback)
267 }
268
Lucas Dupin77198562020-03-31 14:16:16 -0700269 fun updateGlobalDialogVisibility(visibility: Float, dialogView: View?) {
Lucas Dupinb079daa2020-03-24 15:56:23 -0700270 globalActionsSpring.animateTo(blurUtils.blurRadiusOfRatio(visibility), dialogView)
Santiago Etchebehere68eb53e2020-02-25 14:25:34 -0800271 }
272
Lucas Dupin43d01242020-02-03 11:58:33 -0800273 override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
Lucas Dupin13f4b8a2020-02-19 13:41:52 -0800274 IndentingPrintWriter(pw, " ").let {
Lucas Dupin43d01242020-02-03 11:58:33 -0800275 it.println("StatusBarWindowBlurController:")
276 it.increaseIndent()
Lucas Dupinb079daa2020-03-24 15:56:23 -0700277 it.println("shadeRadius: ${shadeSpring.radius}")
278 it.println("globalActionsRadius: ${globalActionsSpring.radius}")
Lucas Dupinee92cc22020-03-31 20:45:42 -0700279 it.println("brightnessMirrorRadius: ${brightnessMirrorSpring.radius}")
Lucas Dupin43d01242020-02-03 11:58:33 -0800280 it.println("wakeAndUnlockBlur: $wakeAndUnlockBlurRadius")
Lucas Dupinb16c49a2020-04-01 21:46:18 -0700281 it.println("notificationLaunchAnimationProgress: " +
282 "${notificationLaunchAnimationParams?.linearProgress}")
283 it.println("ignoreShadeBlurUntilHidden: $ignoreShadeBlurUntilHidden")
Lucas Dupin43d01242020-02-03 11:58:33 -0800284 }
285 }
Lucas Dupinb079daa2020-03-24 15:56:23 -0700286
287 /**
288 * Animation helper that smoothly animates the depth using a spring and deals with frame
289 * invalidation.
290 */
291 inner class DepthAnimation() {
292 /**
293 * Blur radius visible on the UI, in pixels.
294 */
295 var radius = 0
Lucas Dupinee92cc22020-03-31 20:45:42 -0700296
297 /**
298 * Depth ratio of the current blur radius.
299 */
300 val ratio
301 get() = blurUtils.ratioOfBlurRadius(radius)
Lucas Dupinb079daa2020-03-24 15:56:23 -0700302
303 /**
304 * Radius that we're animating to.
305 */
306 private var pendingRadius = -1
307
308 /**
309 * View on {@link Surface} that wants depth.
310 */
311 private var view: View? = null
312
313 private var springAnimation = SpringAnimation(this, object :
314 FloatPropertyCompat<DepthAnimation>("blurRadius") {
315 override fun setValue(rect: DepthAnimation?, value: Float) {
316 radius = value.toInt()
317 scheduleUpdate(view)
318 }
319
320 override fun getValue(rect: DepthAnimation?): Float {
321 return radius.toFloat()
322 }
323 })
324
325 init {
326 springAnimation.spring = SpringForce(0.0f)
327 springAnimation.spring.dampingRatio = SpringForce.DAMPING_RATIO_NO_BOUNCY
Lucas Dupin19e7e552020-04-01 13:16:58 -0700328 springAnimation.spring.stiffness = SpringForce.STIFFNESS_HIGH
Lucas Dupinb079daa2020-03-24 15:56:23 -0700329 springAnimation.addEndListener { _, _, _, _ -> pendingRadius = -1 }
330 }
331
332 fun animateTo(newRadius: Int, viewToBlur: View? = null) {
333 if (pendingRadius == newRadius && view == viewToBlur) {
334 return
335 }
336 view = viewToBlur
337 pendingRadius = newRadius
338 springAnimation.animateToFinalPosition(newRadius.toFloat())
339 }
340
341 fun finishIfRunning() {
342 if (springAnimation.isRunning) {
343 springAnimation.skipToEnd()
344 }
345 }
346 }
Lucas Dupin13f4b8a2020-02-19 13:41:52 -0800347}