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