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.biometrics
18 
19 import android.content.res.Configuration
20 import android.util.MathUtils
21 import android.view.View
22 import androidx.annotation.VisibleForTesting
23 import androidx.lifecycle.Lifecycle
24 import androidx.lifecycle.repeatOnLifecycle
25 import com.android.app.animation.Interpolators
26 import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress
27 import com.android.keyguard.KeyguardUpdateMonitor
28 import com.android.systemui.animation.ActivityTransitionAnimator
29 import com.android.systemui.biometrics.UdfpsKeyguardViewLegacy.ANIMATE_APPEAR_ON_SCREEN_OFF
30 import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
31 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
32 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
33 import com.android.systemui.dump.DumpManager
34 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
35 import com.android.systemui.keyguard.shared.model.Edge
36 import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
37 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
38 import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
39 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
40 import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
41 import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
42 import com.android.systemui.lifecycle.repeatWhenAttached
43 import com.android.systemui.plugins.statusbar.StatusBarStateController
44 import com.android.systemui.res.R
45 import com.android.systemui.scene.shared.model.Scenes
46 import com.android.systemui.shade.domain.interactor.ShadeInteractor
47 import com.android.systemui.statusbar.LockscreenShadeTransitionController
48 import com.android.systemui.statusbar.StatusBarState
49 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
50 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.KeyguardViewManagerCallback
51 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.OccludingAppBiometricUI
52 import com.android.systemui.statusbar.phone.SystemUIDialogManager
53 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
54 import com.android.systemui.statusbar.policy.ConfigurationController
55 import com.android.systemui.statusbar.policy.KeyguardStateController
56 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
57 import java.io.PrintWriter
58 import kotlinx.coroutines.CoroutineScope
59 import kotlinx.coroutines.ExperimentalCoroutinesApi
60 import kotlinx.coroutines.Job
61 import kotlinx.coroutines.launch
62 
63 /** Class that coordinates non-HBM animations during keyguard authentication. */
64 @ExperimentalCoroutinesApi
65 open class UdfpsKeyguardViewControllerLegacy(
66     private val view: UdfpsKeyguardViewLegacy,
67     statusBarStateController: StatusBarStateController,
68     private val keyguardViewManager: StatusBarKeyguardViewManager,
69     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
70     dumpManager: DumpManager,
71     private val lockScreenShadeTransitionController: LockscreenShadeTransitionController,
72     private val configurationController: ConfigurationController,
73     private val keyguardStateController: KeyguardStateController,
74     private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController,
75     systemUIDialogManager: SystemUIDialogManager,
76     private val udfpsController: UdfpsController,
77     private val activityTransitionAnimator: ActivityTransitionAnimator,
78     private val primaryBouncerInteractor: PrimaryBouncerInteractor,
79     private val alternateBouncerInteractor: AlternateBouncerInteractor,
80     private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate,
81     private val selectedUserInteractor: SelectedUserInteractor,
82     private val transitionInteractor: KeyguardTransitionInteractor,
83     shadeInteractor: ShadeInteractor,
84     udfpsOverlayInteractor: UdfpsOverlayInteractor,
85 ) :
86     UdfpsAnimationViewController<UdfpsKeyguardViewLegacy>(
87         view,
88         statusBarStateController,
89         shadeInteractor,
90         systemUIDialogManager,
91         dumpManager,
92         udfpsOverlayInteractor,
93     ) {
94     private val uniqueIdentifier = this.toString()
95     private var showingUdfpsBouncer = false
96     private var udfpsRequested = false
97     private var qsExpansion = 0f
98     private var faceDetectRunning = false
99     private var statusBarState = 0
100     private var transitionToFullShadeProgress = 0f
101     private var lastDozeAmount = 0f
102     private var panelExpansionFraction = 0f
103     private var launchTransitionFadingAway = false
104     private var isLaunchingActivity = false
105     private var activityLaunchProgress = 0f
106     private var inputBouncerExpansion = 0f
107 
108     private val stateListener: StatusBarStateController.StateListener =
109         object : StatusBarStateController.StateListener {
110             override fun onStateChanged(statusBarState: Int) {
111                 this@UdfpsKeyguardViewControllerLegacy.statusBarState = statusBarState
112                 updateAlpha()
113                 updatePauseAuth()
114             }
115         }
116 
117     private val configurationListener: ConfigurationController.ConfigurationListener =
118         object : ConfigurationController.ConfigurationListener {
119             override fun onUiModeChanged() {
120                 view.updateColor()
121             }
122 
123             override fun onThemeChanged() {
124                 view.updateColor()
125             }
126 
127             override fun onConfigChanged(newConfig: Configuration) {
128                 updateScaleFactor()
129                 view.updatePadding()
130                 view.updateColor()
131             }
132         }
133 
134     private val keyguardStateControllerCallback: KeyguardStateController.Callback =
135         object : KeyguardStateController.Callback {
136             override fun onUnlockedChanged() {
137                 updatePauseAuth()
138             }
139 
140             override fun onLaunchTransitionFadingAwayChanged() {
141                 launchTransitionFadingAway = keyguardStateController.isLaunchTransitionFadingAway
142                 updatePauseAuth()
143             }
144         }
145 
146     private val mActivityTransitionAnimatorListener: ActivityTransitionAnimator.Listener =
147         object : ActivityTransitionAnimator.Listener {
148             override fun onTransitionAnimationStart() {
149                 isLaunchingActivity = true
150                 activityLaunchProgress = 0f
151                 updateAlpha()
152             }
153 
154             override fun onTransitionAnimationEnd() {
155                 isLaunchingActivity = false
156                 updateAlpha()
157             }
158 
159             override fun onTransitionAnimationProgress(linearProgress: Float) {
160                 activityLaunchProgress = linearProgress
161                 updateAlpha()
162             }
163         }
164 
165     private val statusBarKeyguardViewManagerCallback: KeyguardViewManagerCallback =
166         object : KeyguardViewManagerCallback {
167             override fun onQSExpansionChanged(qsExpansion: Float) {
168                 this@UdfpsKeyguardViewControllerLegacy.qsExpansion = qsExpansion
169                 updateAlpha()
170                 updatePauseAuth()
171             }
172         }
173 
174     private val occludingAppBiometricUI: OccludingAppBiometricUI =
175         object : OccludingAppBiometricUI {
176             override fun requestUdfps(request: Boolean, color: Int) {
177                 udfpsRequested = request
178                 view.requestUdfps(request, color)
179                 updateAlpha()
180                 updatePauseAuth()
181             }
182 
183             override fun dump(pw: PrintWriter) {
184                 pw.println(tag)
185             }
186         }
187 
188     override val tag: String
189         get() = TAG
190 
191     override fun onInit() {
192         super.onInit()
193         keyguardViewManager.setOccludingAppBiometricUI(occludingAppBiometricUI)
194     }
195 
196     init {
197         com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor.assertInLegacyMode()
198         view.repeatWhenAttached {
199             // repeatOnLifecycle CREATED (as opposed to STARTED) because the Bouncer expansion
200             // can make the view not visible; and we still want to listen for events
201             // that may make the view visible again.
202             repeatOnLifecycle(Lifecycle.State.CREATED) {
203                 listenForBouncerExpansion(this)
204                 listenForAlternateBouncerVisibility(this)
205                 listenForOccludedToAodTransition(this)
206                 listenForGoneToAodTransition(this)
207                 listenForLockscreenAodTransitions(this)
208                 listenForAodToOccludedTransitions(this)
209                 listenForAlternateBouncerToAodTransitions(this)
210                 listenForDreamingToAodTransitions(this)
211                 listenForPrimaryBouncerToAodTransitions(this)
212             }
213         }
214     }
215 
216     @VisibleForTesting
217     suspend fun listenForPrimaryBouncerToAodTransitions(scope: CoroutineScope): Job {
218         return scope.launch {
219             transitionInteractor
220                 .transition(
221                     edge = Edge.create(Scenes.Bouncer, AOD),
222                     edgeWithoutSceneContainer = Edge.create(PRIMARY_BOUNCER, AOD)
223                 )
224                 .collect { transitionStep ->
225                     view.onDozeAmountChanged(
226                         transitionStep.value,
227                         transitionStep.value,
228                         ANIMATE_APPEAR_ON_SCREEN_OFF,
229                     )
230                 }
231         }
232     }
233 
234     @VisibleForTesting
235     suspend fun listenForDreamingToAodTransitions(scope: CoroutineScope): Job {
236         return scope.launch {
237             transitionInteractor.transition(Edge.create(DREAMING, AOD)).collect { transitionStep ->
238                 view.onDozeAmountChanged(
239                     transitionStep.value,
240                     transitionStep.value,
241                     ANIMATE_APPEAR_ON_SCREEN_OFF,
242                 )
243             }
244         }
245     }
246 
247     @VisibleForTesting
248     suspend fun listenForAlternateBouncerToAodTransitions(scope: CoroutineScope): Job {
249         return scope.launch {
250             transitionInteractor.transition(Edge.create(ALTERNATE_BOUNCER, AOD)).collect {
251                 transitionStep ->
252                 view.onDozeAmountChanged(
253                     transitionStep.value,
254                     transitionStep.value,
255                     UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN,
256                 )
257             }
258         }
259     }
260 
261     @VisibleForTesting
262     suspend fun listenForAodToOccludedTransitions(scope: CoroutineScope): Job {
263         return scope.launch {
264             transitionInteractor.transition(Edge.create(AOD, OCCLUDED)).collect { transitionStep ->
265                 view.onDozeAmountChanged(
266                     1f - transitionStep.value,
267                     1f - transitionStep.value,
268                     UdfpsKeyguardViewLegacy.ANIMATION_NONE,
269                 )
270             }
271         }
272     }
273 
274     @VisibleForTesting
275     suspend fun listenForOccludedToAodTransition(scope: CoroutineScope): Job {
276         return scope.launch {
277             transitionInteractor.transition(Edge.create(OCCLUDED, AOD)).collect { transitionStep ->
278                 view.onDozeAmountChanged(
279                     transitionStep.value,
280                     transitionStep.value,
281                     ANIMATE_APPEAR_ON_SCREEN_OFF,
282                 )
283             }
284         }
285     }
286 
287     @VisibleForTesting
288     suspend fun listenForGoneToAodTransition(scope: CoroutineScope): Job {
289         return scope.launch {
290             transitionInteractor
291                 .transition(
292                     edge = Edge.create(Scenes.Gone, AOD),
293                     edgeWithoutSceneContainer = Edge.create(GONE, AOD)
294                 )
295                 .collect { transitionStep ->
296                     view.onDozeAmountChanged(
297                         transitionStep.value,
298                         transitionStep.value,
299                         ANIMATE_APPEAR_ON_SCREEN_OFF,
300                     )
301                 }
302         }
303     }
304 
305     @VisibleForTesting
306     suspend fun listenForLockscreenAodTransitions(scope: CoroutineScope): Job {
307         return scope.launch {
308             transitionInteractor.transitionValue(AOD).collect {
309                 view.onDozeAmountChanged(
310                     it,
311                     it,
312                     UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN,
313                 )
314             }
315         }
316     }
317 
318     @VisibleForTesting
319     suspend fun listenForBouncerExpansion(scope: CoroutineScope): Job {
320         return scope.launch {
321             primaryBouncerInteractor.bouncerExpansion.collect { bouncerExpansion: Float ->
322                 inputBouncerExpansion = bouncerExpansion
323 
324                 panelExpansionFraction =
325                     if (keyguardViewManager.isPrimaryBouncerInTransit) {
326                         aboutToShowBouncerProgress(1f - bouncerExpansion)
327                     } else {
328                         1f - bouncerExpansion
329                     }
330                 updateAlpha()
331                 updatePauseAuth()
332             }
333         }
334     }
335 
336     @VisibleForTesting
337     suspend fun listenForAlternateBouncerVisibility(scope: CoroutineScope): Job {
338         return scope.launch {
339             alternateBouncerInteractor.isVisible.collect { isVisible: Boolean ->
340                 showUdfpsBouncer(isVisible)
341             }
342         }
343     }
344 
345     public override fun onViewAttached() {
346         super.onViewAttached()
347         alternateBouncerInteractor.setAlternateBouncerUIAvailable(true, uniqueIdentifier)
348         val dozeAmount = statusBarStateController.dozeAmount
349         lastDozeAmount = dozeAmount
350         stateListener.onDozeAmountChanged(dozeAmount, dozeAmount)
351         statusBarStateController.addCallback(stateListener)
352         udfpsRequested = false
353         launchTransitionFadingAway = keyguardStateController.isLaunchTransitionFadingAway
354         keyguardStateController.addCallback(keyguardStateControllerCallback)
355         statusBarState = statusBarStateController.state
356         qsExpansion = keyguardViewManager.qsExpansion
357         keyguardViewManager.addCallback(statusBarKeyguardViewManagerCallback)
358         configurationController.addCallback(configurationListener)
359         updateScaleFactor()
360         view.updatePadding()
361         updateAlpha()
362         updatePauseAuth()
363         keyguardViewManager.setOccludingAppBiometricUI(occludingAppBiometricUI)
364         lockScreenShadeTransitionController.mUdfpsKeyguardViewControllerLegacy = this
365         activityTransitionAnimator.addListener(mActivityTransitionAnimatorListener)
366         view.startIconAsyncInflate {
367             val animationViewInternal: View =
368                 view.requireViewById(R.id.udfps_animation_view_internal)
369             animationViewInternal.accessibilityDelegate = udfpsKeyguardAccessibilityDelegate
370         }
371     }
372 
373     public override fun onViewDetached() {
374         super.onViewDetached()
375         alternateBouncerInteractor.setAlternateBouncerUIAvailable(false, uniqueIdentifier)
376         faceDetectRunning = false
377         keyguardStateController.removeCallback(keyguardStateControllerCallback)
378         statusBarStateController.removeCallback(stateListener)
379         keyguardViewManager.removeOccludingAppBiometricUI(occludingAppBiometricUI)
380         configurationController.removeCallback(configurationListener)
381         if (lockScreenShadeTransitionController.mUdfpsKeyguardViewControllerLegacy === this) {
382             lockScreenShadeTransitionController.mUdfpsKeyguardViewControllerLegacy = null
383         }
384         activityTransitionAnimator.removeListener(mActivityTransitionAnimatorListener)
385         keyguardViewManager.removeCallback(statusBarKeyguardViewManagerCallback)
386     }
387 
388     override fun dump(pw: PrintWriter, args: Array<String>) {
389         super.dump(pw, args)
390         pw.println("showingUdfpsAltBouncer=$showingUdfpsBouncer")
391         pw.println(
392             "altBouncerInteractor#isAlternateBouncerVisible=" +
393                 "${alternateBouncerInteractor.isVisibleState()}"
394         )
395         pw.println(
396             "altBouncerInteractor#canShowAlternateBouncerForFingerprint=" +
397                 "${alternateBouncerInteractor.canShowAlternateBouncerForFingerprint()}"
398         )
399         pw.println("faceDetectRunning=$faceDetectRunning")
400         pw.println("statusBarState=" + StatusBarState.toString(statusBarState))
401         pw.println("transitionToFullShadeProgress=$transitionToFullShadeProgress")
402         pw.println("qsExpansion=$qsExpansion")
403         pw.println("panelExpansionFraction=$panelExpansionFraction")
404         pw.println("unpausedAlpha=" + view.unpausedAlpha)
405         pw.println("udfpsRequestedByApp=$udfpsRequested")
406         pw.println("launchTransitionFadingAway=$launchTransitionFadingAway")
407         pw.println("lastDozeAmount=$lastDozeAmount")
408         pw.println("inputBouncerExpansion=$inputBouncerExpansion")
409         view.dump(pw)
410     }
411 
412     /**
413      * Overrides non-bouncer show logic in shouldPauseAuth to still show icon.
414      *
415      * @return whether the udfpsBouncer has been newly shown or hidden
416      */
417     private fun showUdfpsBouncer(show: Boolean): Boolean {
418         if (showingUdfpsBouncer == show) {
419             return false
420         }
421         val udfpsAffordanceWasNotShowing = shouldPauseAuth()
422         showingUdfpsBouncer = show
423         if (showingUdfpsBouncer) {
424             if (udfpsAffordanceWasNotShowing) {
425                 view.animateInUdfpsBouncer(null)
426             }
427             view.announceForAccessibility(
428                 view.context.getString(R.string.accessibility_fingerprint_bouncer)
429             )
430         }
431         updateAlpha()
432         updatePauseAuth()
433         return true
434     }
435 
436     /**
437      * Returns true if the fingerprint manager is running but we want to temporarily pause
438      * authentication. On the keyguard, we may want to show udfps when the shade is expanded, so
439      * this can be overridden with the showBouncer method.
440      */
441     override fun shouldPauseAuth(): Boolean {
442         if (showingUdfpsBouncer) {
443             return false
444         }
445         if (
446             udfpsRequested &&
447                 !notificationShadeVisible &&
448                 !isInputBouncerFullyVisible() &&
449                 keyguardStateController.isShowing
450         ) {
451             return false
452         }
453         if (launchTransitionFadingAway) {
454             return true
455         }
456 
457         // Only pause auth if we're not on the keyguard AND we're not transitioning to doze.
458         // For the UnlockedScreenOffAnimation, the statusBarState is
459         // delayed. However, we still animate in the UDFPS affordance with the
460         // unlockedScreenOffDozeAnimator.
461         if (
462             statusBarState != StatusBarState.KEYGUARD &&
463                 !unlockedScreenOffAnimationController.isAnimationPlaying()
464         ) {
465             return true
466         }
467         if (isBouncerExpansionGreaterThan(.5f)) {
468             return true
469         }
470         if (
471             keyguardUpdateMonitor.getUserUnlockedWithBiometric(
472                 selectedUserInteractor.getSelectedUserId()
473             )
474         ) {
475             // If the device was unlocked by a biometric, immediately hide the UDFPS icon to avoid
476             // overlap with the LockIconView. Shortly afterwards, UDFPS will stop running.
477             return true
478         }
479         return view.unpausedAlpha < 255 * .1
480     }
481 
482     fun isBouncerExpansionGreaterThan(bouncerExpansionThreshold: Float): Boolean {
483         return inputBouncerExpansion >= bouncerExpansionThreshold
484     }
485 
486     fun isInputBouncerFullyVisible(): Boolean {
487         return inputBouncerExpansion == 1f
488     }
489 
490     override fun listenForTouchesOutsideView(): Boolean {
491         return true
492     }
493 
494     /**
495      * Set the progress we're currently transitioning to the full shade. 0.0f means we're not
496      * transitioning yet, while 1.0f means we've fully dragged down. For example, start swiping down
497      * to expand the notification shade from the empty space in the middle of the lock screen.
498      */
499     fun setTransitionToFullShadeProgress(progress: Float) {
500         transitionToFullShadeProgress = progress
501         updateAlpha()
502     }
503 
504     /**
505      * Update alpha for the UDFPS lock screen affordance. The AoD UDFPS visual affordance's alpha is
506      * based on the doze amount.
507      */
508     override fun updateAlpha() {
509         // Fade icon on transitions to showing the status bar or bouncer, but if mUdfpsRequested,
510         // then the keyguard is occluded by some application - so instead use the input bouncer
511         // hidden amount to determine the fade.
512         val expansion = if (udfpsRequested) getInputBouncerHiddenAmt() else panelExpansionFraction
513         var alpha: Int =
514             if (showingUdfpsBouncer) 255
515             else MathUtils.constrain(MathUtils.map(.5f, .9f, 0f, 255f, expansion), 0f, 255f).toInt()
516         if (!showingUdfpsBouncer) {
517             // swipe from top of the lockscreen to expand full QS:
518             alpha =
519                 (alpha * (1.0f - Interpolators.EMPHASIZED_DECELERATE.getInterpolation(qsExpansion)))
520                     .toInt()
521 
522             // swipe from the middle (empty space) of lockscreen to expand the notification shade:
523             alpha = (alpha * (1.0f - transitionToFullShadeProgress)).toInt()
524 
525             // Fade out the icon if we are animating an activity launch over the lockscreen and the
526             // activity didn't request the UDFPS.
527             if (isLaunchingActivity && !udfpsRequested) {
528                 val udfpsActivityLaunchAlphaMultiplier =
529                     1f -
530                         (activityLaunchProgress *
531                                 (ActivityTransitionAnimator.TIMINGS.totalDuration / 83))
532                             .coerceIn(0f, 1f)
533                 alpha = (alpha * udfpsActivityLaunchAlphaMultiplier).toInt()
534             }
535 
536             // Fade out alpha when a dialog is shown
537             // Fade in alpha when a dialog is hidden
538             alpha = (alpha * view.dialogSuggestedAlpha).toInt()
539         }
540         view.unpausedAlpha = alpha
541     }
542 
543     private fun getInputBouncerHiddenAmt(): Float {
544         return 1f - inputBouncerExpansion
545     }
546 
547     /** Update the scale factor based on the device's resolution. */
548     private fun updateScaleFactor() {
549         udfpsController.mOverlayParams?.scaleFactor?.let { view.setScaleFactor(it) }
550     }
551 
552     companion object {
553         const val TAG = "UdfpsKeyguardViewController"
554     }
555 }
556