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