1 /*
2  * Copyright (C) 2018 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 android.hardware.biometrics.cts;
18 
19 import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE;
20 import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_STRONG;
21 import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_WEAK;
22 import static android.hardware.biometrics.BiometricManager.Authenticators.DEVICE_CREDENTIAL;
23 
24 import static org.junit.Assert.assertEquals;
25 import static org.junit.Assert.assertFalse;
26 import static org.junit.Assert.assertNotEquals;
27 import static org.junit.Assert.assertThrows;
28 import static org.junit.Assume.assumeFalse;
29 import static org.junit.Assume.assumeTrue;
30 
31 import android.app.KeyguardManager;
32 import android.content.Context;
33 import android.content.pm.PackageManager;
34 import android.hardware.biometrics.BiometricManager;
35 import android.hardware.biometrics.Flags;
36 import android.platform.test.annotations.Presubmit;
37 import android.platform.test.annotations.RequiresFlagsDisabled;
38 import android.platform.test.annotations.RequiresFlagsEnabled;
39 import android.platform.test.flag.junit.CheckFlagsRule;
40 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
41 import android.text.TextUtils;
42 
43 import androidx.test.ext.junit.runners.AndroidJUnit4;
44 import androidx.test.filters.SmallTest;
45 import androidx.test.platform.app.InstrumentationRegistry;
46 
47 import com.android.compatibility.common.util.ApiTest;
48 
49 import org.junit.Before;
50 import org.junit.Rule;
51 import org.junit.Test;
52 import org.junit.runner.RunWith;
53 
54 /**
55  * Basic test cases for BiometricManager. See the manual biometric tests in CtsVerifier for a more
56  * comprehensive test suite.
57  */
58 @RunWith(AndroidJUnit4.class)
59 @SmallTest
60 @Presubmit
61 public class BiometricManagerTest {
62     @Rule
63     public final CheckFlagsRule mCheckFlagsRule =
64             DeviceFlagsValueProvider.createCheckFlagsRule();
65     private Context mContext;
66     private BiometricManager mBiometricManager;
67 
68     @Before
setUp()69     public void setUp() {
70         mContext = InstrumentationRegistry.getInstrumentation().getContext();
71         mBiometricManager = mContext.getSystemService(BiometricManager.class);
72     }
73 
74     @Test
test_canAuthenticate()75     public void test_canAuthenticate() {
76 
77         assertNotEquals("Device should not have any biometrics enrolled",
78                 mBiometricManager.canAuthenticate(), BiometricManager.BIOMETRIC_SUCCESS);
79 
80         assertNotEquals("Device should not have any biometrics enrolled",
81                 mBiometricManager.canAuthenticate(DEVICE_CREDENTIAL | BIOMETRIC_WEAK),
82                 BiometricManager.BIOMETRIC_SUCCESS);
83     }
84 
85     @Test
test_getButtonLabel_isDifferentForDistinctAuthTypes()86     public void test_getButtonLabel_isDifferentForDistinctAuthTypes() {
87         // Ensure labels for biometrics and credential are different (if non-empty).
88         final CharSequence biometricLabel = mBiometricManager.getStrings(BIOMETRIC_WEAK)
89                 .getButtonLabel();
90         final CharSequence credentialLabel = mBiometricManager.getStrings(DEVICE_CREDENTIAL)
91                 .getButtonLabel();
92         if (!TextUtils.isEmpty(biometricLabel) || !TextUtils.isEmpty(credentialLabel)) {
93             assertFalse("Biometric and credential button labels should not match",
94                     TextUtils.equals(biometricLabel, credentialLabel));
95         }
96     }
97 
98     @Test
test_getButtonLabel_isNonEmptyIfPresentForSubAuthType()99     public void test_getButtonLabel_isNonEmptyIfPresentForSubAuthType() {
100         // Ensure label for biometrics|credential is non-empty if one for biometrics or credential
101         // (or both) is non-empty.
102         final CharSequence biometricOrCredentialLabel = mBiometricManager.getStrings(
103                 BIOMETRIC_WEAK | DEVICE_CREDENTIAL).getButtonLabel();
104         final CharSequence biometricLabel =
105                 mBiometricManager.getStrings(BIOMETRIC_WEAK).getButtonLabel();
106         final CharSequence credentialLabel = mBiometricManager.getStrings(
107                 DEVICE_CREDENTIAL).getButtonLabel();
108         final boolean isLabelPresentForSubAuthType =
109                 !TextUtils.isEmpty(biometricLabel) || !TextUtils.isEmpty(credentialLabel);
110         boolean isBiometricOrCredentialLabel;
111         if (hasOnlyConvenienceSensors()) {
112             isBiometricOrCredentialLabel = TextUtils.isEmpty(credentialLabel);
113         } else {
114             isBiometricOrCredentialLabel = TextUtils.isEmpty(biometricOrCredentialLabel);
115         }
116         assertFalse("Label should not be empty if one for an authenticator sub-type is non-empty",
117                 isBiometricOrCredentialLabel && isLabelPresentForSubAuthType);
118     }
119 
120     @Test
test_getPromptMessage_isDifferentForDistinctAuthTypes()121     public void test_getPromptMessage_isDifferentForDistinctAuthTypes() {
122         // Ensure messages for biometrics and credential are different (if non-empty).
123         final CharSequence biometricMessage = mBiometricManager.getStrings(BIOMETRIC_WEAK)
124                 .getPromptMessage();
125         final CharSequence credentialMessage = mBiometricManager.getStrings(DEVICE_CREDENTIAL)
126                 .getPromptMessage();
127         if (!TextUtils.isEmpty(biometricMessage) || !TextUtils.isEmpty(credentialMessage)) {
128             assertFalse("Biometric and credential prompt messages should not match",
129                     TextUtils.equals(biometricMessage, credentialMessage));
130         }
131     }
132 
133     @Test
test_getPromptMessage_isDifferentForBiometricsIfCredentialAllowed()134     public void test_getPromptMessage_isDifferentForBiometricsIfCredentialAllowed() {
135         // Ensure message for biometrics and biometrics|credential are different (if non-empty).
136         final CharSequence biometricMessage = mBiometricManager.getStrings(BIOMETRIC_WEAK)
137                 .getPromptMessage();
138         final CharSequence bioOrCredMessage = mBiometricManager.getStrings(
139                 BIOMETRIC_WEAK | DEVICE_CREDENTIAL).getPromptMessage();
140         if (!TextUtils.isEmpty(biometricMessage) || !TextUtils.isEmpty(bioOrCredMessage)) {
141             assertFalse("Biometric and biometric|credential prompt messages should not match",
142                     TextUtils.equals(biometricMessage, bioOrCredMessage));
143         }
144     }
145 
146     @Test
test_getSettingName_forBiometrics()147     public void test_getSettingName_forBiometrics() {
148         final PackageManager pm = mContext.getPackageManager();
149         final boolean hasFingerprint = pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT);
150         final boolean hasIris = pm.hasSystemFeature(PackageManager.FEATURE_IRIS);
151         final boolean hasFace = pm.hasSystemFeature(PackageManager.FEATURE_FACE);
152         assumeTrue("Test requires biometric hardware", hasFingerprint || hasIris || hasFace);
153         assumeFalse("Test requires biometric hardware above weak level",
154                 hasOnlyConvenienceSensors());
155 
156         // Ensure biometric setting name is non-empty if device supports biometrics.
157         assertFalse("Name should be non-empty if device supports biometric authentication",
158                 TextUtils.isEmpty(mBiometricManager.getStrings(BIOMETRIC_WEAK).getSettingName()));
159         assertFalse("Name should be non-empty if device supports biometric authentication",
160                 TextUtils.isEmpty(mBiometricManager.getStrings(BIOMETRIC_WEAK | DEVICE_CREDENTIAL)
161                         .getSettingName()));
162     }
163 
hasOnlyConvenienceSensors()164     private boolean hasOnlyConvenienceSensors() {
165         return mBiometricManager.canAuthenticate(BIOMETRIC_WEAK)
166                 == BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE;
167     }
168 
169     @Test
test_getSettingName_forCredential()170     public void test_getSettingName_forCredential() {
171         final KeyguardManager km = mContext.getSystemService(KeyguardManager.class);
172         assumeTrue("Test requires KeyguardManager", km != null);
173 
174         // Ensure credential setting name is non-empty if device supports PIN/pattern/password.
175         assertFalse("Name should be non-empty if device supports PIN/pattern/password",
176                 TextUtils.isEmpty(mBiometricManager.getStrings(DEVICE_CREDENTIAL)
177                         .getSettingName()));
178         assertFalse("Name should be non-empty if device supports PIN/pattern/password",
179                 TextUtils.isEmpty(mBiometricManager.getStrings(BIOMETRIC_WEAK | DEVICE_CREDENTIAL)
180                         .getSettingName()));
181     }
182 
183     @Test
184     @ApiTest(apis = {"android.hardware.biometrics.BiometricManager#getLastAuthenticationTime"})
185     @RequiresFlagsDisabled(Flags.FLAG_LAST_AUTHENTICATION_TIME)
testGetLastAuthenticationTime_flagOff_throwsUnsupportedOperation()186     public void testGetLastAuthenticationTime_flagOff_throwsUnsupportedOperation() {
187         assertThrows(
188                 "Should throw UnsupportedOperationException when flag is disabled",
189                 UnsupportedOperationException.class,
190                 () -> mBiometricManager.getLastAuthenticationTime(BIOMETRIC_STRONG));
191     }
192 
193     @Test
194     @ApiTest(apis = {"android.hardware.biometrics.BiometricManager#getLastAuthenticationTime"})
195     @RequiresFlagsEnabled(Flags.FLAG_LAST_AUTHENTICATION_TIME)
testGetLastAuthenticationTime_allowsStrongAuthenticator()196     public void testGetLastAuthenticationTime_allowsStrongAuthenticator() {
197         assertEquals("BIOMETRIC_STRONG should have no auth time",
198                 BiometricManager.BIOMETRIC_NO_AUTHENTICATION,
199                 mBiometricManager.getLastAuthenticationTime(BIOMETRIC_STRONG));
200     }
201 
202     @Test
203     @ApiTest(apis = {"android.hardware.biometrics.BiometricManager#getLastAuthenticationTime"})
204     @RequiresFlagsEnabled(Flags.FLAG_LAST_AUTHENTICATION_TIME)
testGetLastAuthenticationTime_allowsDeviceCredentialAuthenticator()205     public void testGetLastAuthenticationTime_allowsDeviceCredentialAuthenticator() {
206         assertEquals("DEVICE_CREDENTIAL should have no auth time",
207                 BiometricManager.BIOMETRIC_NO_AUTHENTICATION,
208                 mBiometricManager.getLastAuthenticationTime(DEVICE_CREDENTIAL));
209     }
210 
211     @Test
212     @ApiTest(apis = {"android.hardware.biometrics.BiometricManager#getLastAuthenticationTime"})
213     @RequiresFlagsEnabled(Flags.FLAG_LAST_AUTHENTICATION_TIME)
testGetLastAuthenticationTime_allowsDeviceCredentialAndStrongAuthenticator()214     public void testGetLastAuthenticationTime_allowsDeviceCredentialAndStrongAuthenticator() {
215         assertEquals("DEVICE_CREDENTIAL and BIOMETRIC_STRONG should have no auth time",
216                 BiometricManager.BIOMETRIC_NO_AUTHENTICATION,
217                 mBiometricManager.getLastAuthenticationTime(DEVICE_CREDENTIAL | BIOMETRIC_STRONG));
218     }
219 
220     @Test
221     @ApiTest(apis = {"android.hardware.biometrics.BiometricManager#getLastAuthenticationTime"})
testGetLastAuthenticationTime_throwsNoAuthenticator()222     public void testGetLastAuthenticationTime_throwsNoAuthenticator() {
223         assertThrows("0 should not be allowed",
224                 IllegalArgumentException.class,
225                 () -> mBiometricManager.getLastAuthenticationTime(0));
226     }
227 
228     @Test
229     @ApiTest(apis = {"android.hardware.biometrics.BiometricManager#getLastAuthenticationTime"})
testGetLastAuthenticationTime_throwsBogusAuthenticator()230     public void testGetLastAuthenticationTime_throwsBogusAuthenticator() {
231         assertThrows("42 should not be allowed",
232                 IllegalArgumentException.class,
233                 () -> mBiometricManager.getLastAuthenticationTime(42));
234     }
235 
236     @Test
237     @ApiTest(apis = {"android.hardware.biometrics.BiometricManager#getLastAuthenticationTime"})
testGetLastAuthenticationTime_throwsWeakAuthenticator()238     public void testGetLastAuthenticationTime_throwsWeakAuthenticator() {
239         assertThrows("BIOMETRIC_WEAK should not be allowed",
240                 IllegalArgumentException.class,
241                 () -> mBiometricManager.getLastAuthenticationTime(BIOMETRIC_WEAK));
242     }
243 
244     @Test
245     @ApiTest(apis = {"android.hardware.biometrics.BiometricManager#getLastAuthenticationTime"})
testGetLastAuthenticationTime_throwsConvenienceAuthenticator()246     public void testGetLastAuthenticationTime_throwsConvenienceAuthenticator() {
247         assertThrows("BIOMETRIC_CONVENIENCE should not be allowed",
248                 IllegalArgumentException.class,
249                 () -> mBiometricManager.getLastAuthenticationTime(BIOMETRIC_CONVENIENCE));
250     }
251 }
252