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