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