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.keyguard; 18 19 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; 20 21 import static com.android.keyguard.LockIconView.ICON_LOCK; 22 import static com.android.keyguard.LockIconView.ICON_UNLOCK; 23 import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION; 24 25 import static org.mockito.ArgumentMatchers.any; 26 import static org.mockito.Mockito.anyBoolean; 27 import static org.mockito.Mockito.anyInt; 28 import static org.mockito.Mockito.eq; 29 import static org.mockito.Mockito.reset; 30 import static org.mockito.Mockito.verify; 31 import static org.mockito.Mockito.when; 32 33 import android.graphics.Point; 34 import android.hardware.biometrics.BiometricSourceType; 35 import android.testing.AndroidTestingRunner; 36 import android.testing.TestableLooper; 37 import android.util.Pair; 38 import android.view.HapticFeedbackConstants; 39 import android.view.View; 40 41 import androidx.test.filters.SmallTest; 42 43 import com.android.settingslib.udfps.UdfpsOverlayParams; 44 import com.android.systemui.biometrics.UdfpsController; 45 import com.android.systemui.doze.util.BurnInHelperKt; 46 47 import org.junit.Test; 48 import org.junit.runner.RunWith; 49 50 @SmallTest 51 @RunWith(AndroidTestingRunner.class) 52 @TestableLooper.RunWithLooper 53 public class LockIconViewControllerTest extends LockIconViewControllerBaseTest { 54 55 @Test testUpdateFingerprintLocationOnInit()56 public void testUpdateFingerprintLocationOnInit() { 57 // GIVEN fp sensor location is available pre-attached 58 Pair<Float, Point> udfps = setupUdfps(); // first = radius, second = udfps location 59 60 // WHEN lock icon view controller is initialized and attached 61 init(/* useMigrationFlag= */false); 62 63 // THEN lock icon view location is updated to the udfps location with UDFPS radius 64 verify(mLockIconView).setCenterLocation(eq(udfps.second), eq(udfps.first), 65 eq(PADDING)); 66 } 67 68 @Test testUpdatePaddingBasedOnResolutionScale()69 public void testUpdatePaddingBasedOnResolutionScale() { 70 // GIVEN fp sensor location is available pre-attached & scaled resolution factor is 5 71 Pair<Float, Point> udfps = setupUdfps(); // first = radius, second = udfps location 72 when(mAuthController.getScaleFactor()).thenReturn(5f); 73 74 // WHEN lock icon view controller is initialized and attached 75 init(/* useMigrationFlag= */false); 76 77 // THEN lock icon view location is updated with the scaled radius 78 verify(mLockIconView).setCenterLocation(eq(udfps.second), eq(udfps.first), 79 eq(PADDING * 5)); 80 } 81 82 @Test testUpdateLockIconLocationOnAuthenticatorsRegistered()83 public void testUpdateLockIconLocationOnAuthenticatorsRegistered() { 84 // GIVEN fp sensor location is not available pre-init 85 when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false); 86 when(mAuthController.getFingerprintSensorLocation()).thenReturn(null); 87 init(/* useMigrationFlag= */false); 88 resetLockIconView(); // reset any method call counts for when we verify method calls later 89 90 // GIVEN fp sensor location is available post-attached 91 captureAuthControllerCallback(); 92 Pair<Float, Point> udfps = setupUdfps(); 93 94 // WHEN all authenticators are registered 95 mAuthControllerCallback.onAllAuthenticatorsRegistered(TYPE_FINGERPRINT); 96 mDelayableExecutor.runAllReady(); 97 98 // THEN lock icon view location is updated with the same coordinates as auth controller vals 99 verify(mLockIconView).setCenterLocation(eq(udfps.second), eq(udfps.first), 100 eq(PADDING)); 101 } 102 103 @Test testUpdateLockIconLocationOnUdfpsLocationChanged()104 public void testUpdateLockIconLocationOnUdfpsLocationChanged() { 105 // GIVEN fp sensor location is not available pre-init 106 when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false); 107 when(mAuthController.getFingerprintSensorLocation()).thenReturn(null); 108 init(/* useMigrationFlag= */false); 109 resetLockIconView(); // reset any method call counts for when we verify method calls later 110 111 // GIVEN fp sensor location is available post-attached 112 captureAuthControllerCallback(); 113 Pair<Float, Point> udfps = setupUdfps(); 114 115 // WHEN udfps location changes 116 mAuthControllerCallback.onUdfpsLocationChanged(new UdfpsOverlayParams()); 117 mDelayableExecutor.runAllReady(); 118 119 // THEN lock icon view location is updated with the same coordinates as auth controller vals 120 verify(mLockIconView).setCenterLocation(eq(udfps.second), eq(udfps.first), 121 eq(PADDING)); 122 } 123 124 @Test testLockIconViewBackgroundEnabledWhenUdfpsIsSupported()125 public void testLockIconViewBackgroundEnabledWhenUdfpsIsSupported() { 126 // GIVEN Udpfs sensor location is available 127 setupUdfps(); 128 129 // WHEN the view is attached 130 init(/* useMigrationFlag= */false); 131 132 // THEN the lock icon view background should be enabled 133 verify(mLockIconView).setUseBackground(true); 134 } 135 136 @Test testLockIconViewBackgroundDisabledWhenUdfpsIsNotSupported()137 public void testLockIconViewBackgroundDisabledWhenUdfpsIsNotSupported() { 138 // GIVEN Udfps sensor location is not supported 139 when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false); 140 141 // WHEN the view is attached 142 init(/* useMigrationFlag= */false); 143 144 // THEN the lock icon view background should be disabled 145 verify(mLockIconView).setUseBackground(false); 146 } 147 148 @Test testLockIconStartState()149 public void testLockIconStartState() { 150 // GIVEN lock icon state 151 setupShowLockIcon(); 152 153 // WHEN lock icon controller is initialized 154 init(/* useMigrationFlag= */false); 155 156 // THEN the lock icon should show 157 verify(mLockIconView).updateIcon(ICON_LOCK, false); 158 } 159 160 @Test testLockIcon_updateToUnlock()161 public void testLockIcon_updateToUnlock() { 162 // GIVEN starting state for the lock icon 163 setupShowLockIcon(); 164 165 // GIVEN lock icon controller is initialized and view is attached 166 init(/* useMigrationFlag= */false); 167 captureKeyguardStateCallback(); 168 reset(mLockIconView); 169 170 // WHEN the unlocked state changes to canDismissLockScreen=true 171 when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true); 172 mKeyguardStateCallback.onUnlockedChanged(); 173 174 // THEN the unlock should show 175 verify(mLockIconView).updateIcon(ICON_UNLOCK, false); 176 } 177 178 @Test testLockIcon_clearsIconOnAod_whenUdfpsNotEnrolled()179 public void testLockIcon_clearsIconOnAod_whenUdfpsNotEnrolled() { 180 // GIVEN udfps not enrolled 181 setupUdfps(); 182 when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(false); 183 184 // GIVEN starting state for the lock icon 185 setupShowLockIcon(); 186 187 // GIVEN lock icon controller is initialized and view is attached 188 init(/* useMigrationFlag= */false); 189 captureStatusBarStateListener(); 190 reset(mLockIconView); 191 192 // WHEN the dozing state changes 193 mStatusBarStateListener.onDozingChanged(true /* isDozing */); 194 195 // THEN the icon is cleared 196 verify(mLockIconView).clearIcon(); 197 } 198 199 @Test testLockIcon_updateToAodLock_whenUdfpsEnrolled()200 public void testLockIcon_updateToAodLock_whenUdfpsEnrolled() { 201 // GIVEN udfps enrolled 202 setupUdfps(); 203 when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(true); 204 205 // GIVEN starting state for the lock icon 206 setupShowLockIcon(); 207 208 // GIVEN lock icon controller is initialized and view is attached 209 init(/* useMigrationFlag= */false); 210 captureStatusBarStateListener(); 211 reset(mLockIconView); 212 213 // WHEN the dozing state changes 214 mStatusBarStateListener.onDozingChanged(true /* isDozing */); 215 216 // THEN the AOD lock icon should show 217 verify(mLockIconView).updateIcon(ICON_LOCK, true); 218 } 219 220 @Test testBurnInOffsetsUpdated_onDozeAmountChanged()221 public void testBurnInOffsetsUpdated_onDozeAmountChanged() { 222 // GIVEN udfps enrolled 223 setupUdfps(); 224 when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(true); 225 226 // GIVEN burn-in offset = 5 227 int burnInOffset = 5; 228 when(BurnInHelperKt.getBurnInOffset(anyInt(), anyBoolean())).thenReturn(burnInOffset); 229 230 // GIVEN starting state for the lock icon (keyguard) 231 setupShowLockIcon(); 232 init(/* useMigrationFlag= */false); 233 captureStatusBarStateListener(); 234 reset(mLockIconView); 235 236 // WHEN dozing updates 237 mStatusBarStateListener.onDozingChanged(true /* isDozing */); 238 mStatusBarStateListener.onDozeAmountChanged(1f, 1f); 239 240 // THEN the view's translation is updated to use the AoD burn-in offsets 241 verify(mLockIconView).setTranslationY(burnInOffset); 242 verify(mLockIconView).setTranslationX(burnInOffset); 243 reset(mLockIconView); 244 245 // WHEN the device is no longer dozing 246 mStatusBarStateListener.onDozingChanged(false /* isDozing */); 247 mStatusBarStateListener.onDozeAmountChanged(0f, 0f); 248 249 // THEN the view is updated to NO translation (no burn-in offsets anymore) 250 verify(mLockIconView).setTranslationY(0); 251 verify(mLockIconView).setTranslationX(0); 252 } 253 254 @Test lockIconShows_afterUnlockStateChanges()255 public void lockIconShows_afterUnlockStateChanges() { 256 // GIVEN lock icon controller is initialized and view is attached 257 init(/* useMigrationFlag= */false); 258 captureKeyguardStateCallback(); 259 captureKeyguardUpdateMonitorCallback(); 260 261 // GIVEN user has unlocked with a biometric auth (ie: face auth) 262 // and biometric running state changes 263 when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true); 264 mKeyguardUpdateMonitorCallback.onBiometricRunningStateChanged(false, 265 BiometricSourceType.FACE); 266 reset(mLockIconView); 267 268 // WHEN the unlocked state changes 269 when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(false); 270 mKeyguardStateCallback.onUnlockedChanged(); 271 272 // THEN the lock icon is shown 273 verify(mLockIconView).setContentDescription(LOCKED_LABEL); 274 } 275 276 @Test lockIconAccessibility_notVisibleToUser()277 public void lockIconAccessibility_notVisibleToUser() { 278 // GIVEN lock icon controller is initialized and view is attached 279 init(/* useMigrationFlag= */false); 280 captureKeyguardStateCallback(); 281 captureKeyguardUpdateMonitorCallback(); 282 283 // GIVEN user has unlocked with a biometric auth (ie: face auth) 284 // and biometric running state changes 285 when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true); 286 mKeyguardUpdateMonitorCallback.onBiometricRunningStateChanged(false, 287 BiometricSourceType.FACE); 288 reset(mLockIconView); 289 when(mLockIconView.isVisibleToUser()).thenReturn(false); 290 291 // WHEN the unlocked state changes 292 when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(false); 293 mKeyguardStateCallback.onUnlockedChanged(); 294 295 // THEN the lock icon is shown 296 verify(mLockIconView).setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); 297 } 298 299 @Test lockIconAccessibility_bouncerAnimatingAway()300 public void lockIconAccessibility_bouncerAnimatingAway() { 301 // GIVEN lock icon controller is initialized and view is attached 302 init(/* useMigrationFlag= */false); 303 captureKeyguardStateCallback(); 304 captureKeyguardUpdateMonitorCallback(); 305 306 // GIVEN user has unlocked with a biometric auth (ie: face auth) 307 // and biometric running state changes 308 when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true); 309 mKeyguardUpdateMonitorCallback.onBiometricRunningStateChanged(false, 310 BiometricSourceType.FACE); 311 reset(mLockIconView); 312 when(mLockIconView.isVisibleToUser()).thenReturn(true); 313 when(mPrimaryBouncerInteractor.isAnimatingAway()).thenReturn(true); 314 315 // WHEN the unlocked state changes 316 when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(false); 317 mKeyguardStateCallback.onUnlockedChanged(); 318 319 // THEN the lock icon is shown 320 verify(mLockIconView).setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); 321 } 322 323 @Test lockIconAccessibility_bouncerNotAnimatingAway_viewVisible()324 public void lockIconAccessibility_bouncerNotAnimatingAway_viewVisible() { 325 // GIVEN lock icon controller is initialized and view is attached 326 init(/* useMigrationFlag= */false); 327 captureKeyguardStateCallback(); 328 captureKeyguardUpdateMonitorCallback(); 329 330 // GIVEN user has unlocked with a biometric auth (ie: face auth) 331 // and biometric running state changes 332 when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true); 333 mKeyguardUpdateMonitorCallback.onBiometricRunningStateChanged(false, 334 BiometricSourceType.FACE); 335 reset(mLockIconView); 336 when(mLockIconView.isVisibleToUser()).thenReturn(true); 337 when(mPrimaryBouncerInteractor.isAnimatingAway()).thenReturn(false); 338 339 // WHEN the unlocked state changes 340 when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(false); 341 mKeyguardStateCallback.onUnlockedChanged(); 342 343 // THEN the lock icon is shown 344 verify(mLockIconView).setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); 345 } 346 347 @Test playHaptic_onTouchExploration_NoOneWayHaptics_usesVibrate()348 public void playHaptic_onTouchExploration_NoOneWayHaptics_usesVibrate() { 349 mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false); 350 351 // WHEN request to vibrate on touch exploration 352 mUnderTest.vibrateOnTouchExploration(); 353 354 // THEN vibrates 355 verify(mVibrator).vibrate( 356 anyInt(), 357 any(), 358 eq(UdfpsController.EFFECT_CLICK), 359 eq("lock-icon-down"), 360 any()); 361 } 362 363 @Test playHaptic_onTouchExploration_withOneWayHaptics_performHapticFeedback()364 public void playHaptic_onTouchExploration_withOneWayHaptics_performHapticFeedback() { 365 mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true); 366 367 // WHEN request to vibrate on touch exploration 368 mUnderTest.vibrateOnTouchExploration(); 369 370 // THEN performHapticFeedback is used 371 verify(mVibrator).performHapticFeedback(any(), eq(HapticFeedbackConstants.CONTEXT_CLICK)); 372 } 373 374 @Test playHaptic_onLongPress_NoOneWayHaptics_usesVibrate()375 public void playHaptic_onLongPress_NoOneWayHaptics_usesVibrate() { 376 mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false); 377 378 // WHEN request to vibrate on long press 379 mUnderTest.vibrateOnLongPress(); 380 381 // THEN uses vibrate 382 verify(mVibrator).vibrate( 383 anyInt(), 384 any(), 385 eq(UdfpsController.EFFECT_CLICK), 386 eq("lock-screen-lock-icon-longpress"), 387 any()); 388 } 389 390 @Test playHaptic_onLongPress_withOneWayHaptics_performHapticFeedback()391 public void playHaptic_onLongPress_withOneWayHaptics_performHapticFeedback() { 392 mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true); 393 394 // WHEN request to vibrate on long press 395 mUnderTest.vibrateOnLongPress(); 396 397 // THEN uses perform haptic feedback 398 verify(mVibrator).performHapticFeedback(any(), eq(UdfpsController.LONG_PRESS)); 399 400 } 401 } 402