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