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