• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * 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.Context
20 import android.content.res.Configuration
21 import android.graphics.PointF
22 import android.hardware.biometrics.BiometricSourceType
23 import androidx.annotation.VisibleForTesting
24 import com.android.keyguard.KeyguardUpdateMonitor
25 import com.android.keyguard.KeyguardUpdateMonitorCallback
26 import com.android.settingslib.Utils
27 import com.android.systemui.statusbar.CircleReveal
28 import com.android.systemui.statusbar.LightRevealEffect
29 import com.android.systemui.statusbar.NotificationShadeWindowController
30 import com.android.systemui.statusbar.commandline.Command
31 import com.android.systemui.statusbar.commandline.CommandRegistry
32 import com.android.systemui.statusbar.phone.BiometricUnlockController
33 import com.android.systemui.statusbar.phone.KeyguardBypassController
34 import com.android.systemui.statusbar.phone.StatusBar
35 import com.android.systemui.statusbar.phone.dagger.StatusBarComponent.StatusBarScope
36 import com.android.systemui.statusbar.policy.ConfigurationController
37 import com.android.systemui.util.ViewController
38 import java.io.PrintWriter
39 import javax.inject.Inject
40 
41 /***
42  * Controls the ripple effect that shows when authentication is successful.
43  * The ripple uses the accent color of the current theme.
44  */
45 @StatusBarScope
46 class AuthRippleController @Inject constructor(
47     private val statusBar: StatusBar,
48     private val sysuiContext: Context,
49     private val authController: AuthController,
50     private val configurationController: ConfigurationController,
51     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
52     private val commandRegistry: CommandRegistry,
53     private val notificationShadeWindowController: NotificationShadeWindowController,
54     private val bypassController: KeyguardBypassController,
55     private val biometricUnlockController: BiometricUnlockController,
56     rippleView: AuthRippleView?
57 ) : ViewController<AuthRippleView>(rippleView) {
58     var fingerprintSensorLocation: PointF? = null
59     private var faceSensorLocation: PointF? = null
60     private var circleReveal: LightRevealEffect? = null
61 
62     @VisibleForTesting
onViewAttachednull63     public override fun onViewAttached() {
64         updateRippleColor()
65         updateSensorLocation()
66         authController.addCallback(authControllerCallback)
67         configurationController.addCallback(configurationChangedListener)
68         keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
69         commandRegistry.registerCommand("auth-ripple") { AuthRippleCommand() }
70     }
71 
72     @VisibleForTesting
onViewDetachednull73     public override fun onViewDetached() {
74         authController.removeCallback(authControllerCallback)
75         keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback)
76         configurationController.removeCallback(configurationChangedListener)
77         commandRegistry.unregisterCommand("auth-ripple")
78 
79         notificationShadeWindowController.setForcePluginOpen(false, this)
80     }
81 
showRipplenull82     private fun showRipple(biometricSourceType: BiometricSourceType?) {
83         if (!keyguardUpdateMonitor.isKeyguardVisible ||
84             keyguardUpdateMonitor.userNeedsStrongAuth()) {
85             return
86         }
87 
88         if (biometricSourceType == BiometricSourceType.FINGERPRINT &&
89             fingerprintSensorLocation != null) {
90             mView.setSensorLocation(fingerprintSensorLocation!!)
91             showRipple()
92         } else if (biometricSourceType == BiometricSourceType.FACE &&
93             faceSensorLocation != null) {
94             if (!bypassController.canBypass()) {
95                 return
96             }
97             mView.setSensorLocation(faceSensorLocation!!)
98             showRipple()
99         }
100     }
101 
showRipplenull102     private fun showRipple() {
103         notificationShadeWindowController.setForcePluginOpen(true, this)
104         val biometricUnlockMode = biometricUnlockController.mode
105         val useCircleReveal = circleReveal != null &&
106             (biometricUnlockMode == BiometricUnlockController.MODE_WAKE_AND_UNLOCK ||
107                 biometricUnlockMode == BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING ||
108                 biometricUnlockMode == BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM)
109         val lightRevealScrim = statusBar.lightRevealScrim
110         if (useCircleReveal) {
111             lightRevealScrim?.revealEffect = circleReveal!!
112         }
113 
114         mView.startRipple(
115             /* end runnable */
116             Runnable {
117                 notificationShadeWindowController.setForcePluginOpen(false, this)
118             },
119             /* circleReveal */
120             if (useCircleReveal) {
121                 lightRevealScrim
122             } else {
123                 null
124             }
125         )
126     }
127 
updateSensorLocationnull128     fun updateSensorLocation() {
129         fingerprintSensorLocation = authController.fingerprintSensorLocation
130         faceSensorLocation = authController.faceAuthSensorLocation
131         fingerprintSensorLocation?.let {
132             circleReveal = CircleReveal(
133                 it.x,
134                 it.y,
135                 0f,
136                 Math.max(
137                     Math.max(it.x, statusBar.displayWidth - it.x),
138                     Math.max(it.y, statusBar.displayHeight - it.y)
139                 )
140             )
141         }
142     }
143 
updateRippleColornull144     private fun updateRippleColor() {
145         mView.setColor(
146             Utils.getColorAttr(sysuiContext, android.R.attr.colorAccent).defaultColor)
147     }
148 
149     val keyguardUpdateMonitorCallback =
150         object : KeyguardUpdateMonitorCallback() {
onBiometricAuthenticatednull151             override fun onBiometricAuthenticated(
152                 userId: Int,
153                 biometricSourceType: BiometricSourceType?,
154                 isStrongBiometric: Boolean
155             ) {
156                 showRipple(biometricSourceType)
157             }
158     }
159 
160     val configurationChangedListener =
161         object : ConfigurationController.ConfigurationListener {
onConfigChangednull162             override fun onConfigChanged(newConfig: Configuration?) {
163                 updateSensorLocation()
164             }
onUiModeChangednull165             override fun onUiModeChanged() {
166                 updateRippleColor()
167             }
onThemeChangednull168             override fun onThemeChanged() {
169                 updateRippleColor()
170             }
onOverlayChangednull171             override fun onOverlayChanged() {
172                 updateRippleColor()
173             }
174     }
175 
<lambda>null176     private val authControllerCallback = AuthController.Callback { updateSensorLocation() }
177 
178     inner class AuthRippleCommand : Command {
executenull179         override fun execute(pw: PrintWriter, args: List<String>) {
180             if (args.isEmpty()) {
181                 invalidCommand(pw)
182             } else {
183                 when (args[0]) {
184                     "fingerprint" -> {
185                         pw.println("fingerprint ripple sensorLocation=$fingerprintSensorLocation")
186                         showRipple(BiometricSourceType.FINGERPRINT)
187                     }
188                     "face" -> {
189                         pw.println("face ripple sensorLocation=$faceSensorLocation")
190                         showRipple(BiometricSourceType.FACE)
191                     }
192                     "custom" -> {
193                         if (args.size != 3 ||
194                             args[1].toFloatOrNull() == null ||
195                             args[2].toFloatOrNull() == null) {
196                             invalidCommand(pw)
197                             return
198                         }
199                         pw.println("custom ripple sensorLocation=" + args[1].toFloat() + ", " +
200                             args[2].toFloat())
201                         mView.setSensorLocation(PointF(args[1].toFloat(), args[2].toFloat()))
202                         showRipple()
203                     }
204                     else -> invalidCommand(pw)
205                 }
206             }
207         }
208 
helpnull209         override fun help(pw: PrintWriter) {
210             pw.println("Usage: adb shell cmd statusbar auth-ripple <command>")
211             pw.println("Available commands:")
212             pw.println("  fingerprint")
213             pw.println("  face")
214             pw.println("  custom <x-location: int> <y-location: int>")
215         }
216 
invalidCommandnull217         fun invalidCommand(pw: PrintWriter) {
218             pw.println("invalid command")
219             help(pw)
220         }
221     }
222 }
223