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