• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 }