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.graphics.Point
20 import android.hardware.biometrics.BiometricSourceType
21 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
22 import android.testing.AndroidTestingRunner
23 import android.testing.TestableLooper.RunWithLooper
24 import android.util.DisplayMetrics
25 import androidx.test.filters.SmallTest
26 import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
27 import com.android.keyguard.KeyguardUpdateMonitor
28 import com.android.keyguard.KeyguardUpdateMonitorCallback
29 import com.android.keyguard.logging.KeyguardLogger
30 import com.android.systemui.SysuiTestCase
31 import com.android.systemui.dump.logcatLogBuffer
32 import com.android.systemui.flags.FeatureFlags
33 import com.android.systemui.keyguard.WakefulnessLifecycle
34 import com.android.systemui.plugins.statusbar.StatusBarStateController
35 import com.android.systemui.statusbar.LightRevealScrim
36 import com.android.systemui.statusbar.NotificationShadeWindowController
37 import com.android.systemui.statusbar.commandline.CommandRegistry
38 import com.android.systemui.statusbar.phone.BiometricUnlockController
39 import com.android.systemui.statusbar.policy.ConfigurationController
40 import com.android.systemui.statusbar.policy.KeyguardStateController
41 import com.android.systemui.util.leak.RotationUtils
42 import com.android.systemui.util.mockito.any
43 import org.junit.After
44 import org.junit.Assert.assertFalse
45 import org.junit.Assert.assertTrue
46 import org.junit.Before
47 import org.junit.Test
48 import org.junit.runner.RunWith
49 import org.mockito.ArgumentCaptor
50 import org.mockito.ArgumentMatchers
51 import org.mockito.ArgumentMatchers.eq
52 import org.mockito.Captor
53 import org.mockito.Mock
54 import org.mockito.Mockito.`when`
55 import org.mockito.Mockito.never
56 import org.mockito.Mockito.reset
57 import org.mockito.Mockito.verify
58 import org.mockito.MockitoAnnotations
59 import org.mockito.MockitoSession
60 import org.mockito.quality.Strictness
61 import javax.inject.Provider
62 
63 @SmallTest
64 @RunWith(AndroidTestingRunner::class)
65 class AuthRippleControllerTest : SysuiTestCase() {
66     private lateinit var staticMockSession: MockitoSession
67 
68     private lateinit var controller: AuthRippleController
69     @Mock private lateinit var rippleView: AuthRippleView
70     @Mock private lateinit var commandRegistry: CommandRegistry
71     @Mock private lateinit var configurationController: ConfigurationController
72     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
73     @Mock private lateinit var authController: AuthController
74     @Mock private lateinit var keyguardStateController: KeyguardStateController
75     @Mock
76     private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
77     @Mock
78     private lateinit var notificationShadeWindowController: NotificationShadeWindowController
79     @Mock
80     private lateinit var biometricUnlockController: BiometricUnlockController
81     @Mock
82     private lateinit var udfpsControllerProvider: Provider<UdfpsController>
83     @Mock
84     private lateinit var udfpsController: UdfpsController
85     @Mock
86     private lateinit var statusBarStateController: StatusBarStateController
87     @Mock
88     private lateinit var featureFlags: FeatureFlags
89     @Mock
90     private lateinit var lightRevealScrim: LightRevealScrim
91     @Mock
92     private lateinit var fpSensorProp: FingerprintSensorPropertiesInternal
93 
94     private val displayMetrics = DisplayMetrics()
95 
96     @Captor
97     private lateinit var biometricUnlockListener:
98             ArgumentCaptor<BiometricUnlockController.BiometricUnlockEventsListener>
99 
100     @Before
101     fun setUp() {
102         MockitoAnnotations.initMocks(this)
103         staticMockSession = mockitoSession()
104                 .mockStatic(RotationUtils::class.java)
105                 .strictness(Strictness.LENIENT)
106                 .startMocking()
107 
108         `when`(RotationUtils.getRotation(context)).thenReturn(RotationUtils.ROTATION_NONE)
109         `when`(authController.udfpsProps).thenReturn(listOf(fpSensorProp))
110         `when`(udfpsControllerProvider.get()).thenReturn(udfpsController)
111 
112         controller = AuthRippleController(
113             context,
114             authController,
115             configurationController,
116             keyguardUpdateMonitor,
117             keyguardStateController,
118             wakefulnessLifecycle,
119             commandRegistry,
120             notificationShadeWindowController,
121             udfpsControllerProvider,
122             statusBarStateController,
123             displayMetrics,
124             featureFlags,
125             KeyguardLogger(logcatLogBuffer(AuthRippleController.TAG)),
126             biometricUnlockController,
127             lightRevealScrim,
128             rippleView,
129         )
130         controller.init()
131     }
132 
133     @After
134     fun tearDown() {
135         staticMockSession.finishMocking()
136     }
137 
138     @Test
139     fun testFingerprintTrigger_KeyguardShowing_Ripple() {
140         // GIVEN fp exists, keyguard is showing, unlocking with fp allowed
141         val fpsLocation = Point(5, 5)
142         `when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation)
143         controller.onViewAttached()
144         `when`(keyguardStateController.isShowing).thenReturn(true)
145         `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
146                 eq(BiometricSourceType.FINGERPRINT))).thenReturn(true)
147 
148         // WHEN fingerprint authenticated
149         verify(biometricUnlockController).addListener(biometricUnlockListener.capture())
150         biometricUnlockListener.value
151                 .onBiometricUnlockedWithKeyguardDismissal(BiometricSourceType.FINGERPRINT)
152 
153         // THEN update sensor location and show ripple
154         verify(rippleView).setFingerprintSensorLocation(fpsLocation, 0f)
155         verify(rippleView).startUnlockedRipple(any())
156     }
157 
158     @Test
159     fun testFingerprintTrigger_KeyguardNotShowing_NoRipple() {
160         // GIVEN fp exists & unlocking with fp allowed
161         val fpsLocation = Point(5, 5)
162         `when`(authController.udfpsLocation).thenReturn(fpsLocation)
163         controller.onViewAttached()
164         `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
165                 eq(BiometricSourceType.FINGERPRINT))).thenReturn(true)
166 
167         // WHEN keyguard is NOT showing & fingerprint authenticated
168         `when`(keyguardStateController.isShowing).thenReturn(false)
169         val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
170         verify(keyguardUpdateMonitor).registerCallback(captor.capture())
171         captor.value.onBiometricAuthenticated(
172             0 /* userId */,
173             BiometricSourceType.FINGERPRINT /* type */,
174             false /* isStrongBiometric */)
175 
176         // THEN no ripple
177         verify(rippleView, never()).startUnlockedRipple(any())
178     }
179 
180     @Test
181     fun testFingerprintTrigger_biometricUnlockNotAllowed_NoRipple() {
182         // GIVEN fp exists & keyguard is showing
183         val fpsLocation = Point(5, 5)
184         `when`(authController.udfpsLocation).thenReturn(fpsLocation)
185         controller.onViewAttached()
186         `when`(keyguardStateController.isShowing).thenReturn(true)
187 
188         // WHEN unlocking with fingerprint is NOT allowed & fingerprint authenticated
189         `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
190                 eq(BiometricSourceType.FINGERPRINT))).thenReturn(false)
191         val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
192         verify(keyguardUpdateMonitor).registerCallback(captor.capture())
193         captor.value.onBiometricAuthenticated(
194             0 /* userId */,
195             BiometricSourceType.FINGERPRINT /* type */,
196             false /* isStrongBiometric */)
197 
198         // THEN no ripple
199         verify(rippleView, never()).startUnlockedRipple(any())
200     }
201 
202     @Test
203     fun testNullFaceSensorLocationDoesNothing() {
204         `when`(authController.faceSensorLocation).thenReturn(null)
205         controller.onViewAttached()
206 
207         val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
208         verify(keyguardUpdateMonitor).registerCallback(captor.capture())
209 
210         captor.value.onBiometricAuthenticated(
211             0 /* userId */,
212             BiometricSourceType.FACE /* type */,
213             false /* isStrongBiometric */)
214         verify(rippleView, never()).startUnlockedRipple(any())
215     }
216 
217     @Test
218     fun testNullFingerprintSensorLocationDoesNothing() {
219         `when`(authController.fingerprintSensorLocation).thenReturn(null)
220         controller.onViewAttached()
221 
222         val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
223         verify(keyguardUpdateMonitor).registerCallback(captor.capture())
224 
225         captor.value.onBiometricAuthenticated(
226             0 /* userId */,
227             BiometricSourceType.FINGERPRINT /* type */,
228             false /* isStrongBiometric */)
229         verify(rippleView, never()).startUnlockedRipple(any())
230     }
231 
232     @Test
233     fun registersAndDeregisters() {
234         controller.onViewAttached()
235         val captor = ArgumentCaptor
236             .forClass(KeyguardStateController.Callback::class.java)
237         verify(keyguardStateController).addCallback(captor.capture())
238         val captor2 = ArgumentCaptor
239             .forClass(WakefulnessLifecycle.Observer::class.java)
240         verify(wakefulnessLifecycle).addObserver(captor2.capture())
241         controller.onViewDetached()
242         verify(keyguardStateController).removeCallback(any())
243         verify(wakefulnessLifecycle).removeObserver(any())
244     }
245 
246     @Test
247     @RunWithLooper(setAsMainLooper = true)
248     fun testAnimatorRunWhenWakeAndUnlock_fingerprint() {
249         val fpsLocation = Point(5, 5)
250         `when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation)
251         controller.onViewAttached()
252         `when`(keyguardStateController.isShowing).thenReturn(true)
253         `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
254                 BiometricSourceType.FINGERPRINT)).thenReturn(true)
255         `when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
256 
257         controller.showUnlockRipple(BiometricSourceType.FINGERPRINT)
258         assertTrue("reveal didn't start on keyguardFadingAway",
259             controller.startLightRevealScrimOnKeyguardFadingAway)
260         `when`(keyguardStateController.isKeyguardFadingAway).thenReturn(true)
261         controller.onKeyguardFadingAwayChanged()
262         assertFalse("reveal triggers multiple times",
263             controller.startLightRevealScrimOnKeyguardFadingAway)
264     }
265 
266     @Test
267     @RunWithLooper(setAsMainLooper = true)
268     fun testAnimatorRunWhenWakeAndUnlock_faceUdfpsFingerDown() {
269         val faceLocation = Point(5, 5)
270         `when`(authController.faceSensorLocation).thenReturn(faceLocation)
271         controller.onViewAttached()
272         `when`(keyguardStateController.isShowing).thenReturn(true)
273         `when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
274         `when`(authController.isUdfpsFingerDown).thenReturn(true)
275         `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
276                 eq(BiometricSourceType.FACE))).thenReturn(true)
277 
278         controller.showUnlockRipple(BiometricSourceType.FACE)
279         assertTrue("reveal didn't start on keyguardFadingAway",
280                 controller.startLightRevealScrimOnKeyguardFadingAway)
281         `when`(keyguardStateController.isKeyguardFadingAway).thenReturn(true)
282         controller.onKeyguardFadingAwayChanged()
283         assertFalse("reveal triggers multiple times",
284                 controller.startLightRevealScrimOnKeyguardFadingAway)
285     }
286 
287     @Test
288     fun testUpdateRippleColor() {
289         controller.onViewAttached()
290         val captor = ArgumentCaptor
291             .forClass(ConfigurationController.ConfigurationListener::class.java)
292         verify(configurationController).addCallback(captor.capture())
293 
294         reset(rippleView)
295         captor.value.onThemeChanged()
296         verify(rippleView).setLockScreenColor(ArgumentMatchers.anyInt())
297 
298         reset(rippleView)
299         captor.value.onUiModeChanged()
300         verify(rippleView).setLockScreenColor(ArgumentMatchers.anyInt())
301     }
302 
303     @Test
304     fun testUdfps_onFingerDown_showDwellRipple() {
305         // GIVEN view is already attached
306         controller.onViewAttached()
307         val captor = ArgumentCaptor.forClass(UdfpsController.Callback::class.java)
308         verify(udfpsController).addCallback(captor.capture())
309 
310         // GIVEN fp is updated to Point(5, 5)
311         val fpsLocation = Point(5, 5)
312         `when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation)
313 
314         // WHEN finger is down
315         captor.value.onFingerDown()
316 
317         // THEN update sensor location and show ripple
318         verify(rippleView).setFingerprintSensorLocation(fpsLocation, 0f)
319         verify(rippleView).startDwellRipple(false)
320     }
321 }
322