• 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.biometrics
18 
19 import android.animation.ValueAnimator
20 import android.content.res.Configuration
21 import android.util.MathUtils
22 import android.view.MotionEvent
23 import androidx.annotation.VisibleForTesting
24 import androidx.lifecycle.Lifecycle
25 import androidx.lifecycle.repeatOnLifecycle
26 import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress
27 import com.android.keyguard.KeyguardUpdateMonitor
28 import com.android.systemui.R
29 import com.android.systemui.animation.ActivityLaunchAnimator
30 import com.android.systemui.animation.Interpolators
31 import com.android.systemui.dump.DumpManager
32 import com.android.systemui.flags.FeatureFlags
33 import com.android.systemui.flags.Flags
34 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
35 import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
36 import com.android.systemui.lifecycle.repeatWhenAttached
37 import com.android.systemui.plugins.statusbar.StatusBarStateController
38 import com.android.systemui.shade.ShadeExpansionListener
39 import com.android.systemui.shade.ShadeExpansionStateManager
40 import com.android.systemui.statusbar.LockscreenShadeTransitionController
41 import com.android.systemui.statusbar.StatusBarState
42 import com.android.systemui.statusbar.notification.stack.StackStateAnimator
43 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
44 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.KeyguardViewManagerCallback
45 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.LegacyAlternateBouncer
46 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.OccludingAppBiometricUI
47 import com.android.systemui.statusbar.phone.SystemUIDialogManager
48 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
49 import com.android.systemui.statusbar.policy.ConfigurationController
50 import com.android.systemui.statusbar.policy.KeyguardStateController
51 import java.io.PrintWriter
52 import kotlinx.coroutines.CoroutineScope
53 import kotlinx.coroutines.Job
54 import kotlinx.coroutines.launch
55 
56 /** Class that coordinates non-HBM animations during keyguard authentication. */
57 open class UdfpsKeyguardViewController
58 constructor(
59     private val view: UdfpsKeyguardView,
60     statusBarStateController: StatusBarStateController,
61     shadeExpansionStateManager: ShadeExpansionStateManager,
62     private val keyguardViewManager: StatusBarKeyguardViewManager,
63     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
64     dumpManager: DumpManager,
65     private val lockScreenShadeTransitionController: LockscreenShadeTransitionController,
66     private val configurationController: ConfigurationController,
67     private val keyguardStateController: KeyguardStateController,
68     private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController,
69     systemUIDialogManager: SystemUIDialogManager,
70     private val udfpsController: UdfpsController,
71     private val activityLaunchAnimator: ActivityLaunchAnimator,
72     featureFlags: FeatureFlags,
73     private val primaryBouncerInteractor: PrimaryBouncerInteractor,
74     private val alternateBouncerInteractor: AlternateBouncerInteractor,
75 ) :
76     UdfpsAnimationViewController<UdfpsKeyguardView>(
77         view,
78         statusBarStateController,
79         shadeExpansionStateManager,
80         systemUIDialogManager,
81         dumpManager,
82     ) {
83     private val useExpandedOverlay: Boolean =
84         featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)
85     private val isModernAlternateBouncerEnabled: Boolean =
86         featureFlags.isEnabled(Flags.MODERN_ALTERNATE_BOUNCER)
87     private var showingUdfpsBouncer = false
88     private var udfpsRequested = false
89     private var qsExpansion = 0f
90     private var faceDetectRunning = false
91     private var statusBarState = 0
92     private var transitionToFullShadeProgress = 0f
93     private var lastDozeAmount = 0f
94     private var panelExpansionFraction = 0f
95     private var launchTransitionFadingAway = false
96     private var isLaunchingActivity = false
97     private var activityLaunchProgress = 0f
98     private val unlockedScreenOffDozeAnimator =
99         ValueAnimator.ofFloat(0f, 1f).apply {
100             duration = StackStateAnimator.ANIMATION_DURATION_STANDARD.toLong()
101             interpolator = Interpolators.ALPHA_IN
102             addUpdateListener { animation ->
103                 view.onDozeAmountChanged(
104                     animation.animatedFraction,
105                     animation.animatedValue as Float,
106                     UdfpsKeyguardView.ANIMATION_UNLOCKED_SCREEN_OFF
107                 )
108             }
109         }
110     private var inputBouncerExpansion = 0f // only used for modernBouncer
111 
112     private val stateListener: StatusBarStateController.StateListener =
113         object : StatusBarStateController.StateListener {
114             override fun onDozeAmountChanged(linear: Float, eased: Float) {
115                 if (lastDozeAmount < linear) {
116                     showUdfpsBouncer(false)
117                 }
118                 unlockedScreenOffDozeAnimator.cancel()
119                 val animatingFromUnlockedScreenOff =
120                     unlockedScreenOffAnimationController.isAnimationPlaying()
121                 if (animatingFromUnlockedScreenOff && linear != 0f) {
122                     // we manually animate the fade in of the UDFPS icon since the unlocked
123                     // screen off animation prevents the doze amounts to be incrementally eased in
124                     unlockedScreenOffDozeAnimator.start()
125                 } else {
126                     view.onDozeAmountChanged(
127                         linear,
128                         eased,
129                         UdfpsKeyguardView.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN
130                     )
131                 }
132                 lastDozeAmount = linear
133                 updatePauseAuth()
134             }
135 
136             override fun onStateChanged(statusBarState: Int) {
137                 this@UdfpsKeyguardViewController.statusBarState = statusBarState
138                 updateAlpha()
139                 updatePauseAuth()
140             }
141         }
142 
143     private val configurationListener: ConfigurationController.ConfigurationListener =
144         object : ConfigurationController.ConfigurationListener {
145             override fun onUiModeChanged() {
146                 view.updateColor()
147             }
148 
149             override fun onThemeChanged() {
150                 view.updateColor()
151             }
152 
153             override fun onConfigChanged(newConfig: Configuration) {
154                 updateScaleFactor()
155                 view.updatePadding()
156                 view.updateColor()
157             }
158         }
159 
160     private val shadeExpansionListener = ShadeExpansionListener { (fraction) ->
161         panelExpansionFraction =
162             if (keyguardViewManager.isPrimaryBouncerInTransit) {
163                 aboutToShowBouncerProgress(fraction)
164             } else {
165                 fraction
166             }
167         updateAlpha()
168         updatePauseAuth()
169     }
170 
171     private val keyguardStateControllerCallback: KeyguardStateController.Callback =
172         object : KeyguardStateController.Callback {
173             override fun onLaunchTransitionFadingAwayChanged() {
174                 launchTransitionFadingAway = keyguardStateController.isLaunchTransitionFadingAway
175                 updatePauseAuth()
176             }
177         }
178 
179     private val activityLaunchAnimatorListener: ActivityLaunchAnimator.Listener =
180         object : ActivityLaunchAnimator.Listener {
181             override fun onLaunchAnimationStart() {
182                 isLaunchingActivity = true
183                 activityLaunchProgress = 0f
184                 updateAlpha()
185             }
186 
187             override fun onLaunchAnimationEnd() {
188                 isLaunchingActivity = false
189                 updateAlpha()
190             }
191 
192             override fun onLaunchAnimationProgress(linearProgress: Float) {
193                 activityLaunchProgress = linearProgress
194                 updateAlpha()
195             }
196         }
197 
198     private val statusBarKeyguardViewManagerCallback: KeyguardViewManagerCallback =
199         object : KeyguardViewManagerCallback {
200             override fun onQSExpansionChanged(qsExpansion: Float) {
201                 this@UdfpsKeyguardViewController.qsExpansion = qsExpansion
202                 updateAlpha()
203                 updatePauseAuth()
204             }
205 
206             /**
207              * Forward touches to the UdfpsController. This allows the touch to start from outside
208              * the sensor area and then slide their finger into the sensor area.
209              */
210             override fun onTouch(event: MotionEvent) {
211                 // Don't forward touches if the shade has already started expanding.
212                 if (transitionToFullShadeProgress != 0f) {
213                     return
214                 }
215 
216                 // Forwarding touches not needed with expanded overlay
217                 if (useExpandedOverlay) {
218                     return
219                 } else {
220                     udfpsController.onTouch(event)
221                 }
222             }
223         }
224 
225     private val occludingAppBiometricUI: OccludingAppBiometricUI =
226         object : OccludingAppBiometricUI {
227             override fun requestUdfps(request: Boolean, color: Int) {
228                 udfpsRequested = request
229                 view.requestUdfps(request, color)
230                 updateAlpha()
231                 updatePauseAuth()
232             }
233 
234             override fun dump(pw: PrintWriter) {
235                 pw.println(tag)
236             }
237         }
238 
239     override val tag: String
240         get() = TAG
241 
242     override fun onInit() {
243         super.onInit()
244         keyguardViewManager.setOccludingAppBiometricUI(occludingAppBiometricUI)
245     }
246 
247     init {
248         view.repeatWhenAttached {
249             // repeatOnLifecycle CREATED (as opposed to STARTED) because the Bouncer expansion
250             // can make the view not visible; and we still want to listen for events
251             // that may make the view visible again.
252             repeatOnLifecycle(Lifecycle.State.CREATED) {
253                 listenForBouncerExpansion(this)
254                 if (isModernAlternateBouncerEnabled) listenForAlternateBouncerVisibility(this)
255             }
256         }
257     }
258 
259     @VisibleForTesting
260     internal suspend fun listenForBouncerExpansion(scope: CoroutineScope): Job {
261         return scope.launch {
262             primaryBouncerInteractor.bouncerExpansion.collect { bouncerExpansion: Float ->
263                 inputBouncerExpansion = bouncerExpansion
264                 updateAlpha()
265                 updatePauseAuth()
266             }
267         }
268     }
269 
270     @VisibleForTesting
271     internal suspend fun listenForAlternateBouncerVisibility(scope: CoroutineScope): Job {
272         return scope.launch {
273             alternateBouncerInteractor.isVisible.collect { isVisible: Boolean ->
274                 showUdfpsBouncer(isVisible)
275             }
276         }
277     }
278 
279     public override fun onViewAttached() {
280         super.onViewAttached()
281         alternateBouncerInteractor.setAlternateBouncerUIAvailable(true)
282         val dozeAmount = statusBarStateController.dozeAmount
283         lastDozeAmount = dozeAmount
284         stateListener.onDozeAmountChanged(dozeAmount, dozeAmount)
285         statusBarStateController.addCallback(stateListener)
286         udfpsRequested = false
287         launchTransitionFadingAway = keyguardStateController.isLaunchTransitionFadingAway
288         keyguardStateController.addCallback(keyguardStateControllerCallback)
289         statusBarState = statusBarStateController.state
290         qsExpansion = keyguardViewManager.qsExpansion
291         keyguardViewManager.addCallback(statusBarKeyguardViewManagerCallback)
292         configurationController.addCallback(configurationListener)
293         shadeExpansionStateManager.addExpansionListener(shadeExpansionListener)
294         updateScaleFactor()
295         view.updatePadding()
296         updateAlpha()
297         updatePauseAuth()
298         keyguardViewManager.setLegacyAlternateBouncer(legacyAlternateBouncer)
299         keyguardViewManager.setOccludingAppBiometricUI(occludingAppBiometricUI)
300         lockScreenShadeTransitionController.udfpsKeyguardViewController = this
301         activityLaunchAnimator.addListener(activityLaunchAnimatorListener)
302         view.mUseExpandedOverlay = useExpandedOverlay
303     }
304 
305     override fun onViewDetached() {
306         super.onViewDetached()
307         alternateBouncerInteractor.setAlternateBouncerUIAvailable(false)
308         faceDetectRunning = false
309         keyguardStateController.removeCallback(keyguardStateControllerCallback)
310         statusBarStateController.removeCallback(stateListener)
311         keyguardViewManager.removeLegacyAlternateBouncer(legacyAlternateBouncer)
312         keyguardViewManager.removeOccludingAppBiometricUI(occludingAppBiometricUI)
313         keyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false)
314         configurationController.removeCallback(configurationListener)
315         shadeExpansionStateManager.removeExpansionListener(shadeExpansionListener)
316         if (lockScreenShadeTransitionController.udfpsKeyguardViewController === this) {
317             lockScreenShadeTransitionController.udfpsKeyguardViewController = null
318         }
319         activityLaunchAnimator.removeListener(activityLaunchAnimatorListener)
320         keyguardViewManager.removeCallback(statusBarKeyguardViewManagerCallback)
321     }
322 
323     override fun dump(pw: PrintWriter, args: Array<String>) {
324         super.dump(pw, args)
325         pw.println("isModernAlternateBouncerEnabled=$isModernAlternateBouncerEnabled")
326         pw.println("showingUdfpsAltBouncer=$showingUdfpsBouncer")
327         pw.println(
328             "altBouncerInteractor#isAlternateBouncerVisible=" +
329                 "${alternateBouncerInteractor.isVisibleState()}"
330         )
331         pw.println(
332             "altBouncerInteractor#canShowAlternateBouncerForFingerprint=" +
333                 "${alternateBouncerInteractor.canShowAlternateBouncerForFingerprint()}"
334         )
335         pw.println("faceDetectRunning=$faceDetectRunning")
336         pw.println("statusBarState=" + StatusBarState.toString(statusBarState))
337         pw.println("transitionToFullShadeProgress=$transitionToFullShadeProgress")
338         pw.println("qsExpansion=$qsExpansion")
339         pw.println("panelExpansionFraction=$panelExpansionFraction")
340         pw.println("unpausedAlpha=" + view.unpausedAlpha)
341         pw.println("udfpsRequestedByApp=$udfpsRequested")
342         pw.println("launchTransitionFadingAway=$launchTransitionFadingAway")
343         pw.println("lastDozeAmount=$lastDozeAmount")
344         pw.println("inputBouncerExpansion=$inputBouncerExpansion")
345         view.dump(pw)
346     }
347 
348     /**
349      * Overrides non-bouncer show logic in shouldPauseAuth to still show icon.
350      *
351      * @return whether the udfpsBouncer has been newly shown or hidden
352      */
353     private fun showUdfpsBouncer(show: Boolean): Boolean {
354         if (showingUdfpsBouncer == show) {
355             return false
356         }
357         val udfpsAffordanceWasNotShowing = shouldPauseAuth()
358         showingUdfpsBouncer = show
359         if (showingUdfpsBouncer) {
360             if (udfpsAffordanceWasNotShowing) {
361                 view.animateInUdfpsBouncer(null)
362             }
363             if (keyguardStateController.isOccluded) {
364                 keyguardUpdateMonitor.requestFaceAuthOnOccludingApp(true)
365             }
366             view.announceForAccessibility(
367                 view.context.getString(R.string.accessibility_fingerprint_bouncer)
368             )
369         } else {
370             keyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false)
371         }
372         updateAlpha()
373         updatePauseAuth()
374         return true
375     }
376 
377     /**
378      * Returns true if the fingerprint manager is running but we want to temporarily pause
379      * authentication. On the keyguard, we may want to show udfps when the shade is expanded, so
380      * this can be overridden with the showBouncer method.
381      */
382     override fun shouldPauseAuth(): Boolean {
383         if (showingUdfpsBouncer) {
384             return false
385         }
386         if (
387             udfpsRequested &&
388                 !notificationShadeVisible &&
389                 !isInputBouncerFullyVisible() &&
390                 keyguardStateController.isShowing
391         ) {
392             return false
393         }
394         if (launchTransitionFadingAway) {
395             return true
396         }
397 
398         // Only pause auth if we're not on the keyguard AND we're not transitioning to doze
399         // (ie: dozeAmount = 0f). For the UnlockedScreenOffAnimation, the statusBarState is
400         // delayed. However, we still animate in the UDFPS affordance with the
401         // mUnlockedScreenOffDozeAnimator.
402         if (statusBarState != StatusBarState.KEYGUARD && lastDozeAmount == 0f) {
403             return true
404         }
405         if (isBouncerExpansionGreaterThan(.5f)) {
406             return true
407         }
408         return view.unpausedAlpha < 255 * .1
409     }
410 
411     fun isBouncerExpansionGreaterThan(bouncerExpansionThreshold: Float): Boolean {
412         return inputBouncerExpansion >= bouncerExpansionThreshold
413     }
414 
415     fun isInputBouncerFullyVisible(): Boolean {
416         return inputBouncerExpansion == 1f
417     }
418 
419     override fun listenForTouchesOutsideView(): Boolean {
420         return true
421     }
422 
423     /**
424      * Set the progress we're currently transitioning to the full shade. 0.0f means we're not
425      * transitioning yet, while 1.0f means we've fully dragged down. For example, start swiping down
426      * to expand the notification shade from the empty space in the middle of the lock screen.
427      */
428     fun setTransitionToFullShadeProgress(progress: Float) {
429         transitionToFullShadeProgress = progress
430         updateAlpha()
431     }
432 
433     /**
434      * Update alpha for the UDFPS lock screen affordance. The AoD UDFPS visual affordance's alpha is
435      * based on the doze amount.
436      */
437     override fun updateAlpha() {
438         // Fade icon on transitions to showing the status bar or bouncer, but if mUdfpsRequested,
439         // then the keyguard is occluded by some application - so instead use the input bouncer
440         // hidden amount to determine the fade.
441         val expansion = if (udfpsRequested) getInputBouncerHiddenAmt() else panelExpansionFraction
442         var alpha: Int =
443             if (showingUdfpsBouncer) 255
444             else MathUtils.constrain(MathUtils.map(.5f, .9f, 0f, 255f, expansion), 0f, 255f).toInt()
445         if (!showingUdfpsBouncer) {
446             // swipe from top of the lockscreen to expand full QS:
447             alpha =
448                 (alpha * (1.0f - Interpolators.EMPHASIZED_DECELERATE.getInterpolation(qsExpansion)))
449                     .toInt()
450 
451             // swipe from the middle (empty space) of lockscreen to expand the notification shade:
452             alpha = (alpha * (1.0f - transitionToFullShadeProgress)).toInt()
453 
454             // Fade out the icon if we are animating an activity launch over the lockscreen and the
455             // activity didn't request the UDFPS.
456             if (isLaunchingActivity && !udfpsRequested) {
457                 alpha = (alpha * (1.0f - activityLaunchProgress)).toInt()
458             }
459 
460             // Fade out alpha when a dialog is shown
461             // Fade in alpha when a dialog is hidden
462             alpha = (alpha * view.dialogSuggestedAlpha).toInt()
463         }
464         view.unpausedAlpha = alpha
465     }
466 
467     private fun getInputBouncerHiddenAmt(): Float {
468         return 1f - inputBouncerExpansion
469     }
470 
471     /** Update the scale factor based on the device's resolution. */
472     private fun updateScaleFactor() {
473         udfpsController.mOverlayParams?.scaleFactor?.let { view.setScaleFactor(it) }
474     }
475 
476     private val legacyAlternateBouncer: LegacyAlternateBouncer =
477         object : LegacyAlternateBouncer {
478             override fun showAlternateBouncer(): Boolean {
479                 return showUdfpsBouncer(true)
480             }
481 
482             override fun hideAlternateBouncer(): Boolean {
483                 return showUdfpsBouncer(false)
484             }
485 
486             override fun isShowingAlternateBouncer(): Boolean {
487                 return showingUdfpsBouncer
488             }
489         }
490 
491     companion object {
492         const val TAG = "UdfpsKeyguardViewController"
493     }
494 }
495