1 /* <lambda>null2 * Copyright (C) 2021 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.keyguard 18 19 import android.animation.Animator 20 import android.animation.AnimatorListenerAdapter 21 import android.animation.ValueAnimator 22 import android.content.Context 23 import android.graphics.Matrix 24 import android.view.RemoteAnimationTarget 25 import android.view.SyncRtSurfaceTransactionApplier 26 import android.view.View 27 import androidx.core.math.MathUtils 28 import com.android.internal.R 29 import com.android.keyguard.KeyguardClockSwitchController 30 import com.android.keyguard.KeyguardViewController 31 import com.android.systemui.animation.Interpolators 32 import com.android.systemui.dagger.SysUISingleton 33 import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController 34 import com.android.systemui.statusbar.FeatureFlags 35 import com.android.systemui.statusbar.policy.KeyguardStateController 36 import dagger.Lazy 37 import javax.inject.Inject 38 39 /** 40 * Starting scale factor for the app/launcher surface behind the keyguard, when it's animating 41 * in during keyguard exit. 42 */ 43 const val SURFACE_BEHIND_START_SCALE_FACTOR = 0.95f 44 45 /** 46 * How much to translate the surface behind the keyguard at the beginning of the exit animation, 47 * in terms of percentage of the surface's height. 48 */ 49 const val SURFACE_BEHIND_START_TRANSLATION_Y = 0.05f 50 51 /** 52 * Y coordinate of the pivot point for the scale effect on the surface behind the keyguard. This 53 * is expressed as percentage of the surface's height, so 0.66f means the surface will scale up 54 * from the point at (width / 2, height * 0.66). 55 */ 56 const val SURFACE_BEHIND_SCALE_PIVOT_Y = 0.66f 57 58 /** 59 * Dismiss amount at which to fade in the surface behind the keyguard. The surface will then animate 60 * along with the dismiss amount until [DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD] is reached. 61 * 62 * The dismiss amount is the inverse of the notification panel expansion, which decreases as the 63 * lock screen is swiped away. 64 */ 65 const val DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD = 0.1f 66 67 /** 68 * Dismiss amount at which to complete the keyguard exit animation and hide the keyguard. 69 * 70 * The dismiss amount is the inverse of the notification panel expansion, which decreases as the 71 * lock screen is swiped away. 72 */ 73 const val DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD = 0.3f 74 75 /** 76 * Initiates, controls, and ends the keyguard unlock animation. 77 * 78 * The unlock animation transitions between the keyguard (lock screen) and the app/launcher surface 79 * behind the keyguard. If the user is swiping away the keyguard, this controller will decide when 80 * to animate in the surface, and synchronize its appearance with the swipe gesture. If the keyguard 81 * is animating away via a canned animation (due to biometric unlock, tapping a notification, etc.) 82 * this controller will play a canned animation on the surface as well. 83 * 84 * The surface behind the keyguard is manipulated via a RemoteAnimation passed to 85 * [notifyStartKeyguardExitAnimation] by [KeyguardViewMediator]. 86 */ 87 @SysUISingleton 88 class KeyguardUnlockAnimationController @Inject constructor( 89 context: Context, 90 private val keyguardStateController: KeyguardStateController, 91 private val keyguardViewMediator: Lazy<KeyguardViewMediator>, 92 private val keyguardViewController: KeyguardViewController, 93 private val smartspaceTransitionController: SmartspaceTransitionController, 94 private val featureFlags: FeatureFlags 95 ) : KeyguardStateController.Callback { 96 97 /** 98 * Information used to start, run, and finish a RemoteAnimation on the app or launcher surface 99 * behind the keyguard. 100 * 101 * If we're swiping to unlock, the "animation" is controlled via the gesture, tied to the 102 * dismiss amounts received in [onKeyguardDismissAmountChanged]. It does not have a fixed 103 * duration, and it ends when the gesture reaches a certain threshold or is cancelled. 104 * 105 * If we're unlocking via biometrics, PIN entry, or from clicking a notification, a canned 106 * animation is started in [notifyStartKeyguardExitAnimation]. 107 */ 108 private var surfaceTransactionApplier: SyncRtSurfaceTransactionApplier? = null 109 private var surfaceBehindRemoteAnimationTarget: RemoteAnimationTarget? = null 110 private var surfaceBehindRemoteAnimationStartTime: Long = 0 111 112 /** 113 * Alpha value applied to [surfaceBehindRemoteAnimationTarget], which is the surface of the 114 * app/launcher behind the keyguard. 115 * 116 * If we're doing a swipe gesture, we fade in the surface when the swipe passes a certain 117 * threshold. If we're doing a canned animation, it'll be faded in while a translate/scale 118 * animation plays. 119 */ 120 private var surfaceBehindAlpha = 1f 121 private var surfaceBehindAlphaAnimator = ValueAnimator.ofFloat(0f, 1f) 122 123 /** 124 * Matrix applied to [surfaceBehindRemoteAnimationTarget], which is the surface of the 125 * app/launcher behind the keyguard. 126 * 127 * This is used during the unlock animation/swipe gesture to scale and translate the surface. 128 */ 129 private val surfaceBehindMatrix = Matrix() 130 131 /** 132 * Animator that animates in the surface behind the keyguard. This is used to play a canned 133 * animation on the surface, if we're not doing a swipe gesture. 134 */ 135 private val surfaceBehindEntryAnimator = ValueAnimator.ofFloat(0f, 1f) 136 137 /** Rounded corner radius to apply to the surface behind the keyguard. */ 138 private var roundedCornerRadius = 0f 139 140 /** The SmartSpace view on the lockscreen, provided by [KeyguardClockSwitchController]. */ 141 public var lockscreenSmartSpace: View? = null 142 143 /** 144 * Whether we are currently in the process of unlocking the keyguard, and we are performing the 145 * shared element SmartSpace transition. 146 */ 147 private var unlockingWithSmartSpaceTransition: Boolean = false 148 149 /** 150 * Whether we tried to start the SmartSpace shared element transition for this unlock swipe. 151 * It's possible we're unable to do so (if the Launcher SmartSpace is not available). 152 */ 153 private var attemptedSmartSpaceTransitionForThisSwipe = false 154 155 init { 156 surfaceBehindAlphaAnimator.duration = 150 157 surfaceBehindAlphaAnimator.interpolator = Interpolators.ALPHA_IN 158 surfaceBehindAlphaAnimator.addUpdateListener { valueAnimator: ValueAnimator -> 159 surfaceBehindAlpha = valueAnimator.animatedValue as Float 160 updateSurfaceBehindAppearAmount() 161 } 162 surfaceBehindAlphaAnimator.addListener(object : AnimatorListenerAdapter() { 163 override fun onAnimationEnd(animation: Animator) { 164 // If the surface alpha is 0f, it's no longer visible so we can safely be done with 165 // the animation. 166 if (surfaceBehindAlpha == 0f) { 167 keyguardViewMediator.get().finishSurfaceBehindRemoteAnimation( 168 false /* cancelled */) 169 } 170 } 171 }) 172 173 surfaceBehindEntryAnimator.duration = 450 174 surfaceBehindEntryAnimator.interpolator = Interpolators.DECELERATE_QUINT 175 surfaceBehindEntryAnimator.addUpdateListener { valueAnimator: ValueAnimator -> 176 surfaceBehindAlpha = valueAnimator.animatedValue as Float 177 setSurfaceBehindAppearAmount(valueAnimator.animatedValue as Float) 178 } 179 surfaceBehindEntryAnimator.addListener(object : AnimatorListenerAdapter() { 180 override fun onAnimationEnd(animation: Animator) { 181 keyguardViewMediator.get().onKeyguardExitRemoteAnimationFinished( 182 false /* cancelled */) 183 } 184 }) 185 186 // Listen for changes in the dismiss amount. 187 keyguardStateController.addCallback(this) 188 189 roundedCornerRadius = 190 context.resources.getDimensionPixelSize(R.dimen.rounded_corner_radius).toFloat() 191 } 192 193 /** 194 * Called from [KeyguardViewMediator] to tell us that the RemoteAnimation on the surface behind 195 * the keyguard has started successfully. We can use these parameters to directly manipulate the 196 * surface for the unlock gesture/animation. 197 * 198 * When we're done with it, we'll call [KeyguardViewMediator.finishSurfaceBehindRemoteAnimation] 199 * to end the RemoteAnimation. 200 * 201 * [requestedShowSurfaceBehindKeyguard] denotes whether the exit animation started because of a 202 * call to [KeyguardViewMediator.showSurfaceBehindKeyguard], as happens during a swipe gesture, 203 * as opposed to the keyguard hiding. 204 */ 205 fun notifyStartKeyguardExitAnimation( 206 target: RemoteAnimationTarget, 207 startTime: Long, 208 requestedShowSurfaceBehindKeyguard: Boolean 209 ) { 210 211 if (surfaceTransactionApplier == null) { 212 surfaceTransactionApplier = SyncRtSurfaceTransactionApplier( 213 keyguardViewController.viewRootImpl.view) 214 } 215 216 surfaceBehindRemoteAnimationTarget = target 217 surfaceBehindRemoteAnimationStartTime = startTime 218 219 // If the surface behind wasn't made visible during a swipe, we'll do a canned animation 220 // to animate it in. Otherwise, the swipe touch events will continue animating it. 221 if (!requestedShowSurfaceBehindKeyguard) { 222 keyguardViewController.hide(startTime, 350) 223 surfaceBehindEntryAnimator.start() 224 } 225 } 226 227 fun notifyCancelKeyguardExitAnimation() { 228 surfaceBehindRemoteAnimationTarget = null 229 } 230 231 fun notifyFinishedKeyguardExitAnimation() { 232 surfaceBehindRemoteAnimationTarget = null 233 } 234 235 fun hideKeyguardViewAfterRemoteAnimation() { 236 keyguardViewController.hide(surfaceBehindRemoteAnimationStartTime, 350) 237 } 238 239 /** 240 * Whether we are currently in the process of unlocking the keyguard, and we are performing the 241 * shared element SmartSpace transition. 242 */ 243 fun isUnlockingWithSmartSpaceTransition(): Boolean { 244 return unlockingWithSmartSpaceTransition 245 } 246 247 /** 248 * Update the lockscreen SmartSpace to be positioned according to the current dismiss amount. As 249 * the dismiss amount increases, we will increase our SmartSpace's progress to the destination 250 * bounds (the location of the Launcher SmartSpace). 251 */ 252 fun updateLockscreenSmartSpacePosition() { 253 smartspaceTransitionController.setProgressToDestinationBounds( 254 keyguardStateController.dismissAmount / DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD) 255 } 256 257 /** 258 * Scales in and translates up the surface behind the keyguard. This is used during unlock 259 * animations and swipe gestures to animate the surface's entry (and exit, if the swipe is 260 * cancelled). 261 */ 262 private fun setSurfaceBehindAppearAmount(amount: Float) { 263 if (surfaceBehindRemoteAnimationTarget == null) { 264 return 265 } 266 267 val surfaceHeight: Int = surfaceBehindRemoteAnimationTarget!!.screenSpaceBounds.height() 268 val scaleFactor = (SURFACE_BEHIND_START_SCALE_FACTOR + 269 (1f - SURFACE_BEHIND_START_SCALE_FACTOR) * 270 MathUtils.clamp(amount, 0f, 1f)) 271 272 // Scale up from a point at the center-bottom of the surface. 273 surfaceBehindMatrix.setScale( 274 scaleFactor, 275 scaleFactor, 276 surfaceBehindRemoteAnimationTarget!!.screenSpaceBounds.width() / 2f, 277 surfaceHeight * SURFACE_BEHIND_SCALE_PIVOT_Y) 278 279 // Translate up from the bottom. 280 surfaceBehindMatrix.postTranslate(0f, 281 surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount)) 282 283 // If we're snapping the keyguard back, immediately begin fading it out. 284 val animationAlpha = 285 if (keyguardStateController.isSnappingKeyguardBackAfterSwipe) amount 286 else surfaceBehindAlpha 287 288 val params = SyncRtSurfaceTransactionApplier.SurfaceParams.Builder( 289 surfaceBehindRemoteAnimationTarget!!.leash) 290 .withMatrix(surfaceBehindMatrix) 291 .withCornerRadius(roundedCornerRadius) 292 .withAlpha(animationAlpha) 293 .build() 294 surfaceTransactionApplier!!.scheduleApply(params) 295 } 296 297 /** 298 * Sets the appearance amount of the surface behind the keyguard, according to the current 299 * keyguard dismiss amount and the method of dismissal. 300 */ 301 private fun updateSurfaceBehindAppearAmount() { 302 if (surfaceBehindRemoteAnimationTarget == null) { 303 return 304 } 305 306 // For fling animations, we want to animate the surface in over the full distance. If we're 307 // dismissing the keyguard via a swipe gesture (or cancelling the swipe gesture), we want to 308 // bring in the surface behind over a relatively short swipe distance (~15%), to keep the 309 // interaction tight. 310 if (keyguardStateController.isFlingingToDismissKeyguard) { 311 setSurfaceBehindAppearAmount(keyguardStateController.dismissAmount) 312 } else if (keyguardStateController.isDismissingFromSwipe || 313 keyguardStateController.isSnappingKeyguardBackAfterSwipe) { 314 val totalSwipeDistanceToDismiss = 315 (DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD - DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD) 316 val swipedDistanceSoFar: Float = 317 keyguardStateController.dismissAmount - DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD 318 val progress = swipedDistanceSoFar / totalSwipeDistanceToDismiss 319 setSurfaceBehindAppearAmount(progress) 320 } 321 } 322 323 override fun onKeyguardDismissAmountChanged() { 324 if (!KeyguardService.sEnableRemoteKeyguardGoingAwayAnimation) { 325 return 326 } 327 328 if (keyguardViewController.isShowing) { 329 updateKeyguardViewMediatorIfThresholdsReached() 330 331 // If the surface is visible or it's about to be, start updating its appearance to 332 // reflect the new dismiss amount. 333 if (keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard() || 334 keyguardViewMediator.get().isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe) { 335 updateSurfaceBehindAppearAmount() 336 } 337 } 338 339 // The end of the SmartSpace transition can occur after the keyguard is hidden (when we tell 340 // Launcher's SmartSpace to become visible again), so update it even if the keyguard view is 341 // no longer showing. 342 updateSmartSpaceTransition() 343 } 344 345 /** 346 * Lets the KeyguardViewMediator know if the dismiss amount has crossed a threshold of interest, 347 * such as reaching the point in the dismiss swipe where we need to make the surface behind the 348 * keyguard visible. 349 */ 350 private fun updateKeyguardViewMediatorIfThresholdsReached() { 351 if (!featureFlags.isNewKeyguardSwipeAnimationEnabled) { 352 return 353 } 354 355 val dismissAmount = keyguardStateController.dismissAmount 356 357 // Hide the keyguard if we're fully dismissed, or if we're swiping to dismiss and have 358 // crossed the threshold to finish the dismissal. 359 val reachedHideKeyguardThreshold = (dismissAmount >= 1f || 360 (keyguardStateController.isDismissingFromSwipe && 361 // Don't hide if we're flinging during a swipe, since we need to finish 362 // animating it out. This will be called again after the fling ends. 363 !keyguardStateController.isFlingingToDismissKeyguardDuringSwipeGesture && 364 dismissAmount >= DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD)) 365 366 if (dismissAmount >= DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD && 367 !keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard()) { 368 // We passed the threshold, and we're not yet showing the surface behind the 369 // keyguard. Animate it in. 370 keyguardViewMediator.get().showSurfaceBehindKeyguard() 371 fadeInSurfaceBehind() 372 } else if (dismissAmount < DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD && 373 keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard()) { 374 // We're no longer past the threshold but we are showing the surface. Animate it 375 // out. 376 keyguardViewMediator.get().hideSurfaceBehindKeyguard() 377 fadeOutSurfaceBehind() 378 } else if (keyguardViewMediator.get() 379 .isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe && 380 reachedHideKeyguardThreshold) { 381 keyguardViewMediator.get().onKeyguardExitRemoteAnimationFinished(false /* cancelled */) 382 } 383 } 384 385 /** 386 * Updates flags related to the SmartSpace transition in response to a change in keyguard 387 * dismiss amount, and also updates the SmartSpaceTransitionController, which will let Launcher 388 * know if it needs to do something as a result. 389 */ 390 private fun updateSmartSpaceTransition() { 391 if (!featureFlags.isSmartSpaceSharedElementTransitionEnabled) { 392 return 393 } 394 395 val dismissAmount = keyguardStateController.dismissAmount 396 397 // If we've begun a swipe, and are capable of doing the SmartSpace transition, start it! 398 if (!attemptedSmartSpaceTransitionForThisSwipe && 399 dismissAmount > 0f && 400 dismissAmount < 1f && 401 keyguardViewController.isShowing) { 402 attemptedSmartSpaceTransitionForThisSwipe = true 403 404 smartspaceTransitionController.prepareForUnlockTransition() 405 if (keyguardStateController.canPerformSmartSpaceTransition()) { 406 unlockingWithSmartSpaceTransition = true 407 smartspaceTransitionController.launcherSmartspace?.setVisibility( 408 View.INVISIBLE) 409 } 410 } else if (attemptedSmartSpaceTransitionForThisSwipe && 411 (dismissAmount == 0f || dismissAmount == 1f)) { 412 attemptedSmartSpaceTransitionForThisSwipe = false 413 unlockingWithSmartSpaceTransition = false 414 smartspaceTransitionController.launcherSmartspace?.setVisibility(View.VISIBLE) 415 } 416 } 417 418 private fun fadeInSurfaceBehind() { 419 surfaceBehindAlphaAnimator.cancel() 420 surfaceBehindAlphaAnimator.start() 421 } 422 423 private fun fadeOutSurfaceBehind() { 424 surfaceBehindAlphaAnimator.cancel() 425 surfaceBehindAlphaAnimator.reverse() 426 } 427 }