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.server.policy;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.mockito.Mockito.eq;
22 import static org.mockito.Mockito.mock;
23 import static org.mockito.Mockito.never;
24 import static org.mockito.Mockito.verify;
25 import static org.mockito.Mockito.when;
26 
27 import android.content.pm.PackageManager;
28 import android.hardware.biometrics.BiometricStateListener;
29 import android.hardware.fingerprint.FingerprintManager;
30 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
31 import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
32 import android.os.Handler;
33 import android.os.PowerManager;
34 import android.os.test.TestLooper;
35 import android.test.suitebuilder.annotation.SmallTest;
36 import android.testing.AndroidTestingRunner;
37 import android.testing.TestableContext;
38 import android.testing.TestableResources;
39 import android.view.Window;
40 
41 import androidx.test.InstrumentationRegistry;
42 
43 import com.android.internal.R;
44 
45 import org.junit.Before;
46 import org.junit.Rule;
47 import org.junit.Test;
48 import org.junit.runner.RunWith;
49 import org.mockito.ArgumentCaptor;
50 import org.mockito.Mock;
51 import org.mockito.MockitoAnnotations;
52 
53 import java.util.List;
54 
55 /**
56  * Unit tests for {@link SideFpsEventHandler}.
57  *
58  * <p>Run with <code>atest SideFpsEventHandlerTest</code>.
59  */
60 @SmallTest
61 @RunWith(AndroidTestingRunner.class)
62 public class SideFpsEventHandlerTest {
63 
64     private static final List<Integer> sAllStates =
65             List.of(
66                     BiometricStateListener.STATE_IDLE,
67                     BiometricStateListener.STATE_ENROLLING,
68                     BiometricStateListener.STATE_KEYGUARD_AUTH,
69                     BiometricStateListener.STATE_BP_AUTH,
70                     BiometricStateListener.STATE_AUTH_OTHER);
71 
72     private static final Integer AUTO_DISMISS_DIALOG = 500;
73 
74     @Rule
75     public TestableContext mContext =
76             new TestableContext(InstrumentationRegistry.getContext(), null);
77 
78     @Mock
79     private PackageManager mPackageManager;
80     @Mock
81     private FingerprintManager mFingerprintManager;
82     @Mock
83     private SideFpsToast mDialog;
84     @Mock
85     private Window mWindow;
86 
87     private final TestLooper mLooper = new TestLooper();
88     private SideFpsEventHandler mEventHandler;
89     private BiometricStateListener mBiometricStateListener;
90 
91     @Before
setup()92     public void setup() {
93         MockitoAnnotations.initMocks(this);
94 
95         mContext.addMockSystemService(PackageManager.class, mPackageManager);
96         mContext.addMockSystemService(FingerprintManager.class, mFingerprintManager);
97         TestableResources resources = mContext.getOrCreateTestableResources();
98         resources.addOverride(R.integer.config_sideFpsToastTimeout, AUTO_DISMISS_DIALOG);
99 
100         when(mDialog.getWindow()).thenReturn(mWindow);
101 
102         mEventHandler =
103                 new SideFpsEventHandler(
104                         mContext,
105                         new Handler(mLooper.getLooper()),
106                         mContext.getSystemService(PowerManager.class),
107                         (ctx) -> mDialog);
108     }
109 
110     @Test
ignoresWithoutFingerprintFeature()111     public void ignoresWithoutFingerprintFeature() {
112         when(mPackageManager.hasSystemFeature(eq(PackageManager.FEATURE_FINGERPRINT)))
113                 .thenReturn(false);
114 
115         assertThat(mEventHandler.shouldConsumeSinglePress(60L)).isFalse();
116 
117         mLooper.dispatchAll();
118         verify(mDialog, never()).show();
119     }
120 
121     @Test
ignoresWithoutSfps()122     public void ignoresWithoutSfps() throws Exception {
123         setupWithSensor(false /* hasSfps */, true /* initialized */);
124 
125         for (int state : sAllStates) {
126             setBiometricState(state);
127             assertThat(mEventHandler.shouldConsumeSinglePress(200L)).isFalse();
128 
129             mLooper.dispatchAll();
130             verify(mDialog, never()).show();
131         }
132     }
133 
134     @Test
ignoresWhileWaitingForSfps()135     public void ignoresWhileWaitingForSfps() throws Exception {
136         setupWithSensor(true /* hasSfps */, false /* initialized */);
137 
138         for (int state : sAllStates) {
139             setBiometricState(state);
140             assertThat(mEventHandler.shouldConsumeSinglePress(400L)).isFalse();
141 
142             mLooper.dispatchAll();
143             verify(mDialog, never()).show();
144         }
145     }
146 
147     @Test
ignoresWhenIdleOrUnknown()148     public void ignoresWhenIdleOrUnknown() throws Exception {
149         setupWithSensor(true /* hasSidefps */, true /* initialized */);
150 
151         setBiometricState(BiometricStateListener.STATE_IDLE);
152         assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isFalse();
153 
154         setBiometricState(BiometricStateListener.STATE_AUTH_OTHER);
155         assertThat(mEventHandler.shouldConsumeSinglePress(90000L)).isFalse();
156 
157         mLooper.dispatchAll();
158         verify(mDialog, never()).show();
159     }
160 
161     @Test
ignoresOnKeyguard()162     public void ignoresOnKeyguard() throws Exception {
163         setupWithSensor(true /* hasSfps */, true /* initialized */);
164 
165         setBiometricState(BiometricStateListener.STATE_KEYGUARD_AUTH);
166         assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isFalse();
167 
168         mLooper.dispatchAll();
169         verify(mDialog, never()).show();
170     }
171 
172     @Test
doesNotpromptWhenBPisActive()173     public void doesNotpromptWhenBPisActive() throws Exception {
174         setupWithSensor(true /* hasSideFps */, true /* initialized */);
175 
176         setBiometricState(BiometricStateListener.STATE_BP_AUTH);
177         assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue();
178 
179         mLooper.dispatchAll();
180         verify(mDialog, never()).show();
181     }
182 
183     @Test
promptsWhenEnrolling()184     public void promptsWhenEnrolling() throws Exception {
185         setupWithSensor(true /* hasSfps */, true /* initialized */);
186 
187         setBiometricState(BiometricStateListener.STATE_ENROLLING);
188         assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue();
189 
190         mLooper.dispatchAll();
191         verify(mDialog).show();
192     }
193 
194     @Test
dialogDismissesAfterTime()195     public void dialogDismissesAfterTime() throws Exception {
196         setupWithSensor(true /* hasSfps */, true /* initialized */);
197 
198         setBiometricState(BiometricStateListener.STATE_ENROLLING);
199         when(mDialog.isShowing()).thenReturn(true);
200         assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue();
201 
202         mLooper.dispatchAll();
203         verify(mDialog).show();
204         mLooper.moveTimeForward(AUTO_DISMISS_DIALOG);
205         mLooper.dispatchAll();
206         verify(mDialog).dismiss();
207     }
208 
209     @Test
dialogDoesNotDismissOnSensorTouch()210     public void dialogDoesNotDismissOnSensorTouch() throws Exception {
211         setupWithSensor(true /* hasSfps */, true /* initialized */);
212 
213         setBiometricState(BiometricStateListener.STATE_ENROLLING);
214         when(mDialog.isShowing()).thenReturn(true);
215         assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue();
216 
217         mLooper.dispatchAll();
218         verify(mDialog).show();
219 
220         mBiometricStateListener.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH);
221         mLooper.moveTimeForward(AUTO_DISMISS_DIALOG - 1);
222         mLooper.dispatchAll();
223 
224         verify(mDialog, never()).dismiss();
225     }
226 
setBiometricState(@iometricStateListener.State int newState)227     private void setBiometricState(@BiometricStateListener.State int newState) {
228         if (mBiometricStateListener != null) {
229             mBiometricStateListener.onStateChanged(newState);
230             mLooper.dispatchAll();
231         }
232     }
233 
setupWithSensor(boolean hasSfps, boolean initialized)234     private void setupWithSensor(boolean hasSfps, boolean initialized) throws Exception {
235         when(mPackageManager.hasSystemFeature(eq(PackageManager.FEATURE_FINGERPRINT)))
236                 .thenReturn(true);
237         when(mFingerprintManager.isPowerbuttonFps()).thenReturn(hasSfps);
238         mEventHandler.onFingerprintSensorReady();
239 
240         ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> fpCallbackCaptor =
241                 ArgumentCaptor.forClass(IFingerprintAuthenticatorsRegisteredCallback.class);
242         verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(fpCallbackCaptor.capture());
243         if (initialized) {
244             fpCallbackCaptor
245                     .getValue()
246                     .onAllAuthenticatorsRegistered(
247                             List.of(mock(FingerprintSensorPropertiesInternal.class)));
248             if (hasSfps) {
249                 ArgumentCaptor<BiometricStateListener> captor =
250                         ArgumentCaptor.forClass(BiometricStateListener.class);
251                 verify(mFingerprintManager).registerBiometricStateListener(captor.capture());
252                 mBiometricStateListener = captor.getValue();
253             }
254         }
255     }
256 }
257