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 }