• 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.Animator
20 import android.animation.AnimatorListenerAdapter
21 import android.animation.ValueAnimator
22 import android.content.Context
23 import android.graphics.Point
24 import android.hardware.biometrics.BiometricFingerprintConstants
25 import android.hardware.biometrics.BiometricSourceType
26 import androidx.annotation.VisibleForTesting
27 import com.android.keyguard.KeyguardUpdateMonitor
28 import com.android.keyguard.KeyguardUpdateMonitorCallback
29 import com.android.keyguard.logging.KeyguardLogger
30 import com.android.settingslib.Utils
31 import com.android.systemui.R
32 import com.android.systemui.animation.Interpolators
33 import com.android.systemui.flags.FeatureFlags
34 import com.android.systemui.flags.Flags
35 import com.android.systemui.keyguard.WakefulnessLifecycle
36 import com.android.systemui.plugins.statusbar.StatusBarStateController
37 import com.android.systemui.statusbar.CircleReveal
38 import com.android.systemui.statusbar.LiftReveal
39 import com.android.systemui.statusbar.LightRevealEffect
40 import com.android.systemui.statusbar.NotificationShadeWindowController
41 import com.android.systemui.statusbar.commandline.Command
42 import com.android.systemui.statusbar.commandline.CommandRegistry
43 import com.android.systemui.statusbar.phone.BiometricUnlockController
44 import com.android.systemui.statusbar.phone.CentralSurfaces
45 import com.android.systemui.statusbar.phone.KeyguardBypassController
46 import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
47 import com.android.systemui.statusbar.policy.ConfigurationController
48 import com.android.systemui.statusbar.policy.KeyguardStateController
49 import com.android.systemui.util.ViewController
50 import java.io.PrintWriter
51 import javax.inject.Inject
52 import javax.inject.Provider
53 
54 /***
55  * Controls two ripple effects:
56  *   1. Unlocked ripple: shows when authentication is successful
57  *   2. UDFPS dwell ripple: shows when the user has their finger down on the UDFPS area and reacts
58  *   to errors and successes
59  *
60  * The ripple uses the accent color of the current theme.
61  */
62 @CentralSurfacesScope
63 class AuthRippleController @Inject constructor(
64     private val centralSurfaces: CentralSurfaces,
65     private val sysuiContext: Context,
66     private val authController: AuthController,
67     private val configurationController: ConfigurationController,
68     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
69     private val keyguardStateController: KeyguardStateController,
70     private val wakefulnessLifecycle: WakefulnessLifecycle,
71     private val commandRegistry: CommandRegistry,
72     private val notificationShadeWindowController: NotificationShadeWindowController,
73     private val bypassController: KeyguardBypassController,
74     private val biometricUnlockController: BiometricUnlockController,
75     private val udfpsControllerProvider: Provider<UdfpsController>,
76     private val statusBarStateController: StatusBarStateController,
77     private val featureFlags: FeatureFlags,
78     private val logger: KeyguardLogger,
79         rippleView: AuthRippleView?
80 ) : ViewController<AuthRippleView>(rippleView), KeyguardStateController.Callback,
81     WakefulnessLifecycle.Observer {
82 
83     @VisibleForTesting
84     internal var startLightRevealScrimOnKeyguardFadingAway = false
85     var lightRevealScrimAnimator: ValueAnimator? = null
86     var fingerprintSensorLocation: Point? = null
87     private var faceSensorLocation: Point? = null
88     private var circleReveal: LightRevealEffect? = null
89 
90     private var udfpsController: UdfpsController? = null
91     private var udfpsRadius: Float = -1f
92 
93     @VisibleForTesting
94     public override fun onViewAttached() {
95         authController.addCallback(authControllerCallback)
96         updateRippleColor()
97         updateUdfpsDependentParams()
98         udfpsController?.addCallback(udfpsControllerCallback)
99         configurationController.addCallback(configurationChangedListener)
100         keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
101         keyguardStateController.addCallback(this)
102         wakefulnessLifecycle.addObserver(this)
103         commandRegistry.registerCommand("auth-ripple") { AuthRippleCommand() }
104     }
105 
106     @VisibleForTesting
107     public override fun onViewDetached() {
108         udfpsController?.removeCallback(udfpsControllerCallback)
109         authController.removeCallback(authControllerCallback)
110         keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback)
111         configurationController.removeCallback(configurationChangedListener)
112         keyguardStateController.removeCallback(this)
113         wakefulnessLifecycle.removeObserver(this)
114         commandRegistry.unregisterCommand("auth-ripple")
115 
116         notificationShadeWindowController.setForcePluginOpen(false, this)
117     }
118 
119     fun showUnlockRipple(biometricSourceType: BiometricSourceType) {
120         val keyguardNotShowing = !keyguardStateController.isShowing
121         val unlockNotAllowed = !keyguardUpdateMonitor
122                 .isUnlockingWithBiometricAllowed(biometricSourceType)
123         if (keyguardNotShowing || unlockNotAllowed) {
124             logger.notShowingUnlockRipple(keyguardNotShowing, unlockNotAllowed)
125             return
126         }
127 
128         updateSensorLocation()
129         if (biometricSourceType == BiometricSourceType.FINGERPRINT) {
130             fingerprintSensorLocation?.let {
131                 mView.setFingerprintSensorLocation(it, udfpsRadius)
132                 circleReveal = CircleReveal(
133                         it.x,
134                         it.y,
135                         0,
136                         Math.max(
137                                 Math.max(it.x, centralSurfaces.displayWidth.toInt() - it.x),
138                                 Math.max(it.y, centralSurfaces.displayHeight.toInt() - it.y)
139                         )
140                 )
141                 logger.showingUnlockRippleAt(it.x, it.y, "FP sensor radius: $udfpsRadius")
142                 showUnlockedRipple()
143             }
144         } else if (biometricSourceType == BiometricSourceType.FACE) {
145             if (!bypassController.canBypass() && !authController.isUdfpsFingerDown) {
146                 return
147             }
148             faceSensorLocation?.let {
149                 mView.setSensorLocation(it)
150                 circleReveal = CircleReveal(
151                         it.x,
152                         it.y,
153                         0,
154                         Math.max(
155                                 Math.max(it.x, centralSurfaces.displayWidth.toInt() - it.x),
156                                 Math.max(it.y, centralSurfaces.displayHeight.toInt() - it.y)
157                         )
158                 )
159                 logger.showingUnlockRippleAt(it.x, it.y, "Face unlock ripple")
160                 showUnlockedRipple()
161             }
162         }
163     }
164 
165     private fun showUnlockedRipple() {
166         notificationShadeWindowController.setForcePluginOpen(true, this)
167 
168         // This code path is not used if the KeyguardTransitionRepository is managing the light
169         // reveal scrim.
170         if (!featureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)) {
171             val lightRevealScrim = centralSurfaces.lightRevealScrim
172             if (statusBarStateController.isDozing || biometricUnlockController.isWakeAndUnlock) {
173                 circleReveal?.let {
174                     lightRevealScrim?.revealAmount = 0f
175                     lightRevealScrim?.revealEffect = it
176                     startLightRevealScrimOnKeyguardFadingAway = true
177                 }
178             }
179         }
180 
181         mView.startUnlockedRipple(
182             /* end runnable */
183             Runnable {
184                 notificationShadeWindowController.setForcePluginOpen(false, this)
185             }
186         )
187     }
188 
189     override fun onKeyguardFadingAwayChanged() {
190         if (featureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)) {
191             return
192         }
193 
194         if (keyguardStateController.isKeyguardFadingAway) {
195             val lightRevealScrim = centralSurfaces.lightRevealScrim
196             if (startLightRevealScrimOnKeyguardFadingAway && lightRevealScrim != null) {
197                 lightRevealScrimAnimator?.cancel()
198                 lightRevealScrimAnimator = ValueAnimator.ofFloat(.1f, 1f).apply {
199                     interpolator = Interpolators.LINEAR_OUT_SLOW_IN
200                     duration = RIPPLE_ANIMATION_DURATION
201                     startDelay = keyguardStateController.keyguardFadingAwayDelay
202                     addUpdateListener { animator ->
203                         if (lightRevealScrim.revealEffect != circleReveal) {
204                             // if something else took over the reveal, let's cancel ourselves
205                             cancel()
206                             return@addUpdateListener
207                         }
208                         lightRevealScrim.revealAmount = animator.animatedValue as Float
209                     }
210                     addListener(object : AnimatorListenerAdapter() {
211                         override fun onAnimationEnd(animation: Animator?) {
212                             // Reset light reveal scrim to the default, so the CentralSurfaces
213                             // can handle any subsequent light reveal changes
214                             // (ie: from dozing changes)
215                             if (lightRevealScrim.revealEffect == circleReveal) {
216                                 lightRevealScrim.revealEffect = LiftReveal
217                             }
218 
219                             lightRevealScrimAnimator = null
220                         }
221                     })
222                     start()
223                 }
224                 startLightRevealScrimOnKeyguardFadingAway = false
225             }
226         }
227     }
228 
229     /**
230      * Whether we're animating the light reveal scrim from a call to [onKeyguardFadingAwayChanged].
231      */
232     fun isAnimatingLightRevealScrim(): Boolean {
233         return lightRevealScrimAnimator?.isRunning ?: false
234     }
235 
236     override fun onStartedGoingToSleep() {
237         // reset the light reveal start in case we were pending an unlock
238         startLightRevealScrimOnKeyguardFadingAway = false
239     }
240 
241     fun updateSensorLocation() {
242         fingerprintSensorLocation = authController.fingerprintSensorLocation
243         faceSensorLocation = authController.faceSensorLocation
244     }
245 
246     private fun updateRippleColor() {
247         mView.setLockScreenColor(Utils.getColorAttrDefaultColor(sysuiContext,
248                 R.attr.wallpaperTextColorAccent))
249     }
250 
251     private fun showDwellRipple() {
252         updateSensorLocation()
253         fingerprintSensorLocation?.let {
254             mView.setFingerprintSensorLocation(it, udfpsRadius)
255             mView.startDwellRipple(statusBarStateController.isDozing)
256         }
257     }
258 
259     private val keyguardUpdateMonitorCallback =
260         object : KeyguardUpdateMonitorCallback() {
261             override fun onBiometricAuthenticated(
262                 userId: Int,
263                 biometricSourceType: BiometricSourceType,
264                 isStrongBiometric: Boolean
265             ) {
266                 if (biometricSourceType == BiometricSourceType.FINGERPRINT) {
267                     mView.fadeDwellRipple()
268                 }
269                 showUnlockRipple(biometricSourceType)
270             }
271 
272         override fun onBiometricAuthFailed(biometricSourceType: BiometricSourceType) {
273             if (biometricSourceType == BiometricSourceType.FINGERPRINT) {
274                 mView.retractDwellRipple()
275             }
276         }
277 
278         override fun onBiometricAcquired(
279             biometricSourceType: BiometricSourceType,
280             acquireInfo: Int
281         ) {
282             if (biometricSourceType == BiometricSourceType.FINGERPRINT &&
283                     BiometricFingerprintConstants.shouldDisableUdfpsDisplayMode(acquireInfo) &&
284                     acquireInfo != BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD) {
285                 // received an 'acquiredBad' message, so immediately retract
286                 mView.retractDwellRipple()
287             }
288         }
289 
290         override fun onKeyguardBouncerStateChanged(bouncerIsOrWillBeShowing: Boolean) {
291             if (bouncerIsOrWillBeShowing) {
292                 mView.fadeDwellRipple()
293             }
294         }
295     }
296 
297     private val configurationChangedListener =
298         object : ConfigurationController.ConfigurationListener {
299             override fun onUiModeChanged() {
300                 updateRippleColor()
301             }
302             override fun onThemeChanged() {
303                 updateRippleColor()
304             }
305     }
306 
307     private val udfpsControllerCallback =
308         object : UdfpsController.Callback {
309             override fun onFingerDown() {
310                 showDwellRipple()
311             }
312 
313             override fun onFingerUp() {
314                 mView.retractDwellRipple()
315             }
316         }
317 
318     private val authControllerCallback =
319         object : AuthController.Callback {
320             override fun onAllAuthenticatorsRegistered() {
321                 updateUdfpsDependentParams()
322             }
323 
324             override fun onUdfpsLocationChanged() {
325                 updateUdfpsDependentParams()
326             }
327         }
328 
329     private fun updateUdfpsDependentParams() {
330         authController.udfpsProps?.let {
331             if (it.size > 0) {
332                 udfpsController = udfpsControllerProvider.get()
333                 udfpsRadius = authController.udfpsRadius
334 
335                 if (mView.isAttachedToWindow) {
336                     udfpsController?.addCallback(udfpsControllerCallback)
337                 }
338             }
339         }
340     }
341 
342     inner class AuthRippleCommand : Command {
343         override fun execute(pw: PrintWriter, args: List<String>) {
344             if (args.isEmpty()) {
345                 invalidCommand(pw)
346             } else {
347                 when (args[0]) {
348                     "dwell" -> {
349                         showDwellRipple()
350                         pw.println("lock screen dwell ripple: " +
351                                 "\n\tsensorLocation=$fingerprintSensorLocation" +
352                                 "\n\tudfpsRadius=$udfpsRadius")
353                     }
354                     "fingerprint" -> {
355                         pw.println("fingerprint ripple sensorLocation=$fingerprintSensorLocation")
356                         showUnlockRipple(BiometricSourceType.FINGERPRINT)
357                     }
358                     "face" -> {
359                         // note: only shows when about to proceed to the home screen
360                         pw.println("face ripple sensorLocation=$faceSensorLocation")
361                         showUnlockRipple(BiometricSourceType.FACE)
362                     }
363                     "custom" -> {
364                         if (args.size != 3 ||
365                             args[1].toFloatOrNull() == null ||
366                             args[2].toFloatOrNull() == null) {
367                             invalidCommand(pw)
368                             return
369                         }
370                         pw.println("custom ripple sensorLocation=" + args[1] + ", " + args[2])
371                         mView.setSensorLocation(Point(args[1].toInt(), args[2].toInt()))
372                         showUnlockedRipple()
373                     }
374                     else -> invalidCommand(pw)
375                 }
376             }
377         }
378 
379         override fun help(pw: PrintWriter) {
380             pw.println("Usage: adb shell cmd statusbar auth-ripple <command>")
381             pw.println("Available commands:")
382             pw.println("  dwell")
383             pw.println("  fingerprint")
384             pw.println("  face")
385             pw.println("  custom <x-location: int> <y-location: int>")
386         }
387 
388         fun invalidCommand(pw: PrintWriter) {
389             pw.println("invalid command")
390             help(pw)
391         }
392     }
393 
394     companion object {
395         const val RIPPLE_ANIMATION_DURATION: Long = 800
396         const val TAG = "AuthRippleController"
397     }
398 }
399