diff options
13 files changed, 339 insertions, 3 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 7cf7e19f865c..70cf99432869 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -18291,11 +18291,13 @@ package android.hardware.biometrics { public class BiometricManager { method @Deprecated @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public int canAuthenticate(); method @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public int canAuthenticate(int); + method @FlaggedApi("android.hardware.biometrics.last_authentication_time") @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public long getLastAuthenticationTime(int); method @NonNull @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public android.hardware.biometrics.BiometricManager.Strings getStrings(int); field public static final int BIOMETRIC_ERROR_HW_UNAVAILABLE = 1; // 0x1 field public static final int BIOMETRIC_ERROR_NONE_ENROLLED = 11; // 0xb field public static final int BIOMETRIC_ERROR_NO_HARDWARE = 12; // 0xc field public static final int BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED = 15; // 0xf + field @FlaggedApi("android.hardware.biometrics.last_authentication_time") public static final long BIOMETRIC_NO_AUTHENTICATION = -1L; // 0xffffffffffffffffL field public static final int BIOMETRIC_SUCCESS = 0; // 0x0 } @@ -18341,6 +18343,7 @@ package android.hardware.biometrics { field public static final int BIOMETRIC_ERROR_UNABLE_TO_PROCESS = 2; // 0x2 field public static final int BIOMETRIC_ERROR_USER_CANCELED = 10; // 0xa field public static final int BIOMETRIC_ERROR_VENDOR = 8; // 0x8 + field @FlaggedApi("android.hardware.biometrics.last_authentication_time") public static final long BIOMETRIC_NO_AUTHENTICATION = -1L; // 0xffffffffffffffffL } public abstract static class BiometricPrompt.AuthenticationCallback { diff --git a/core/java/android/hardware/biometrics/BiometricConstants.java b/core/java/android/hardware/biometrics/BiometricConstants.java index 943eee461809..61d87026b6e9 100644 --- a/core/java/android/hardware/biometrics/BiometricConstants.java +++ b/core/java/android/hardware/biometrics/BiometricConstants.java @@ -18,6 +18,7 @@ package android.hardware.biometrics; import static android.hardware.biometrics.BiometricManager.Authenticators; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; @@ -296,4 +297,15 @@ public interface BiometricConstants { @Retention(RetentionPolicy.SOURCE) @IntDef({BIOMETRIC_LOCKOUT_NONE, BIOMETRIC_LOCKOUT_TIMED, BIOMETRIC_LOCKOUT_PERMANENT}) @interface LockoutMode {} + + // + // Other miscellaneous constants + // + + /** + * Returned from {@link BiometricManager#getLastAuthenticationTime(int)} when there has + * been no successful authentication for the given authenticator since boot. + */ + @FlaggedApi(Flags.FLAG_LAST_AUTHENTICATION_TIME) + long BIOMETRIC_NO_AUTHENTICATION = -1; } diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java index 82694ee3463b..90bbca8336e1 100644 --- a/core/java/android/hardware/biometrics/BiometricManager.java +++ b/core/java/android/hardware/biometrics/BiometricManager.java @@ -23,6 +23,8 @@ import static android.Manifest.permission.WRITE_DEVICE_CONFIG; import static com.android.internal.util.FrameworkStatsLog.AUTH_DEPRECATED_APIUSED__DEPRECATED_API__API_BIOMETRIC_MANAGER_CAN_AUTHENTICATE; +import android.annotation.ElapsedRealtimeLong; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -30,6 +32,7 @@ import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; +import android.app.KeyguardManager; import android.content.Context; import android.os.IBinder; import android.os.RemoteException; @@ -86,6 +89,17 @@ public class BiometricManager { BiometricConstants.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED; /** + * Returned from {@link BiometricManager#getLastAuthenticationTime(int)} when no matching + * successful authentication has been performed since boot. + */ + @FlaggedApi(Flags.FLAG_LAST_AUTHENTICATION_TIME) + public static final long BIOMETRIC_NO_AUTHENTICATION = + BiometricConstants.BIOMETRIC_NO_AUTHENTICATION; + + private static final int GET_LAST_AUTH_TIME_ALLOWED_AUTHENTICATORS = + Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_STRONG; + + /** * @hide */ @IntDef({BIOMETRIC_SUCCESS, @@ -637,5 +651,58 @@ public class BiometricManager { } } + + /** + * Gets the last time the user successfully authenticated using one of the given authenticators. + * The returned value is time in + * {@link android.os.SystemClock#elapsedRealtime SystemClock.elapsedRealtime()} (time since + * boot, including sleep). + * <p> + * {@link BiometricManager#BIOMETRIC_NO_AUTHENTICATION} is returned in the case where there + * has been no successful authentication using any of the given authenticators since boot. + * <p> + * Currently, only {@link Authenticators#DEVICE_CREDENTIAL} and + * {@link Authenticators#BIOMETRIC_STRONG} are accepted. {@link IllegalArgumentException} will + * be thrown if {@code authenticators} contains other authenticator types. + * <p> + * Note that this may return successful authentication times even if the device is currently + * locked. You may use {@link KeyguardManager#isDeviceLocked()} to determine if the device + * is unlocked or not. Additionally, this method may return valid times for an authentication + * method that is no longer available. For instance, if the user unlocked the device with a + * {@link Authenticators#BIOMETRIC_STRONG} authenticator but then deleted that authenticator + * (e.g., fingerprint data), this method will still return the time of that unlock for + * {@link Authenticators#BIOMETRIC_STRONG} if it is the most recent successful event. The caveat + * is that {@link BiometricManager#BIOMETRIC_NO_AUTHENTICATION} will be returned if the device + * no longer has a secure lock screen at all, even if there were successful authentications + * performed before the lock screen was made insecure. + * + * @param authenticators bit field consisting of constants defined in {@link Authenticators}. + * @return the time of last authentication or + * {@link BiometricManager#BIOMETRIC_NO_AUTHENTICATION} + * @throws IllegalArgumentException if {@code authenticators} contains values other than + * {@link Authenticators#DEVICE_CREDENTIAL} and {@link Authenticators#BIOMETRIC_STRONG} or is + * 0. + */ + @RequiresPermission(USE_BIOMETRIC) + @ElapsedRealtimeLong + @FlaggedApi(Flags.FLAG_LAST_AUTHENTICATION_TIME) + public long getLastAuthenticationTime( + @BiometricManager.Authenticators.Types int authenticators) { + if (authenticators == 0 + || (GET_LAST_AUTH_TIME_ALLOWED_AUTHENTICATORS & authenticators) != authenticators) { + throw new IllegalArgumentException( + "Only BIOMETRIC_STRONG and DEVICE_CREDENTIAL authenticators may be used."); + } + + if (mService != null) { + try { + return mService.getLastAuthenticationTime(UserHandle.myUserId(), authenticators); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } else { + return BIOMETRIC_NO_AUTHENTICATION; + } + } } diff --git a/core/java/android/hardware/biometrics/IAuthService.aidl b/core/java/android/hardware/biometrics/IAuthService.aidl index c2e5c0b6d519..5bdbe2b573b2 100644 --- a/core/java/android/hardware/biometrics/IAuthService.aidl +++ b/core/java/android/hardware/biometrics/IAuthService.aidl @@ -57,6 +57,9 @@ interface IAuthService { // Checks if biometrics can be used. int canAuthenticate(String opPackageName, int userId, int authenticators); + // Gets the time of last authentication for the given user and authenticators. + long getLastAuthenticationTime(int userId, int authenticators); + // Checks if any biometrics are enrolled. boolean hasEnrolledBiometrics(int userId, String opPackageName); diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl index 18c8d1bd3a1e..058f302af62b 100644 --- a/core/java/android/hardware/biometrics/IBiometricService.aidl +++ b/core/java/android/hardware/biometrics/IBiometricService.aidl @@ -53,6 +53,10 @@ interface IBiometricService { @EnforcePermission("USE_BIOMETRIC_INTERNAL") int canAuthenticate(String opPackageName, int userId, int callingUserId, int authenticators); + // Gets the time of last authentication for the given user and authenticators. + @EnforcePermission("USE_BIOMETRIC_INTERNAL") + long getLastAuthenticationTime(int userId, int authenticators); + // Checks if any biometrics are enrolled. @EnforcePermission("USE_BIOMETRIC_INTERNAL") boolean hasEnrolledBiometrics(int userId, String opPackageName); diff --git a/core/java/android/hardware/biometrics/flags.aconfig b/core/java/android/hardware/biometrics/flags.aconfig index 0924e0d12bd3..c370ad658c3b 100644 --- a/core/java/android/hardware/biometrics/flags.aconfig +++ b/core/java/android/hardware/biometrics/flags.aconfig @@ -1,6 +1,13 @@ package: "android.hardware.biometrics" flag { + name: "last_authentication_time" + namespace: "wallet_integration" + description: "Feature flag for adding getLastAuthenticationTime API to BiometricManager" + bug: "301979982" +} + +flag { name: "add_key_agreement_crypto_object" namespace: "biometrics" description: "Feature flag for adding KeyAgreement api to CryptoObject." diff --git a/keystore/java/android/security/Authorization.java b/keystore/java/android/security/Authorization.java index 2d2dd24763c4..b4b3e9275035 100644 --- a/keystore/java/android/security/Authorization.java +++ b/keystore/java/android/security/Authorization.java @@ -18,7 +18,9 @@ package android.security; import android.annotation.NonNull; import android.annotation.Nullable; +import android.hardware.biometrics.BiometricConstants; import android.hardware.security.keymint.HardwareAuthToken; +import android.hardware.security.keymint.HardwareAuthenticatorType; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceSpecificException; @@ -37,7 +39,10 @@ public class Authorization { public static final int SYSTEM_ERROR = ResponseCode.SYSTEM_ERROR; - private static IKeystoreAuthorization getService() { + /** + * @return an instance of IKeystoreAuthorization + */ + public static IKeystoreAuthorization getService() { return IKeystoreAuthorization.Stub.asInterface( ServiceManager.checkService("android.security.authorization")); } @@ -100,4 +105,24 @@ public class Authorization { } } + /** + * Gets the last authentication time of the given user and authenticators. + * + * @param userId user id + * @param authenticatorTypes an array of {@link HardwareAuthenticatorType}. + * @return the last authentication time or + * {@link BiometricConstants#BIOMETRIC_NO_AUTHENTICATION}. + */ + public static long getLastAuthenticationTime( + long userId, @HardwareAuthenticatorType int[] authenticatorTypes) { + try { + return getService().getLastAuthTime(userId, authenticatorTypes); + } catch (RemoteException | NullPointerException e) { + Log.w(TAG, "Can not connect to keystore", e); + return BiometricConstants.BIOMETRIC_NO_AUTHENTICATION; + } catch (ServiceSpecificException e) { + return BiometricConstants.BIOMETRIC_NO_AUTHENTICATION; + } + } + } diff --git a/keystore/java/android/security/GateKeeper.java b/keystore/java/android/security/GateKeeper.java index af188a95c929..464714fe2895 100644 --- a/keystore/java/android/security/GateKeeper.java +++ b/keystore/java/android/security/GateKeeper.java @@ -45,8 +45,19 @@ public abstract class GateKeeper { @UnsupportedAppUsage public static long getSecureUserId() throws IllegalStateException { + return getSecureUserId(UserHandle.myUserId()); + } + + /** + * Return the secure user id for a given user id + * @param userId the user id, e.g. 0 + * @return the secure user id or {@link GateKeeper#INVALID_SECURE_USER_ID} if no such mapping + * for the given user id is found. + * @throws IllegalStateException if there is an error retrieving the secure user id + */ + public static long getSecureUserId(int userId) throws IllegalStateException { try { - return getService().getSecureUserId(UserHandle.myUserId()); + return getService().getSecureUserId(userId); } catch (RemoteException e) { throw new IllegalStateException( "Failed to obtain secure user ID from gatekeeper", e); diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java index 4538cad513d6..0629e6373b6e 100644 --- a/services/core/java/com/android/server/biometrics/AuthService.java +++ b/services/core/java/com/android/server/biometrics/AuthService.java @@ -39,6 +39,7 @@ import android.content.pm.PackageManager; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.ComponentInfoInternal; +import android.hardware.biometrics.Flags; import android.hardware.biometrics.IAuthService; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; import android.hardware.biometrics.IBiometricService; @@ -333,6 +334,33 @@ public class AuthService extends SystemService { } @Override + public long getLastAuthenticationTime(int userId, + @Authenticators.Types int authenticators) throws RemoteException { + // Only allow internal clients to call getLastAuthenticationTime with a different + // userId. + final int callingUserId = UserHandle.getCallingUserId(); + + if (userId != callingUserId) { + checkInternalPermission(); + } else { + checkPermission(); + } + + final long identity = Binder.clearCallingIdentity(); + try { + // We can't do this above because we need the READ_DEVICE_CONFIG permission, which + // the calling user may not possess. + if (!Flags.lastAuthenticationTime()) { + throw new UnsupportedOperationException(); + } + + return mBiometricService.getLastAuthenticationTime(userId, authenticators); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override public boolean hasEnrolledBiometrics(int userId, String opPackageName) throws RemoteException { checkInternalPermission(); diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index 1898b8015462..91a68ea67b3b 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -20,6 +20,7 @@ import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; import static android.hardware.biometrics.BiometricManager.Authenticators; +import static android.hardware.biometrics.BiometricManager.BIOMETRIC_NO_AUTHENTICATION; import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_IDLE; @@ -39,6 +40,7 @@ import android.hardware.SensorPrivacyManager; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricPrompt; +import android.hardware.biometrics.Flags; import android.hardware.biometrics.IBiometricAuthenticator; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; import android.hardware.biometrics.IBiometricSensorReceiver; @@ -53,6 +55,7 @@ import android.hardware.biometrics.SensorPropertiesInternal; import android.hardware.camera2.CameraManager; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; +import android.hardware.security.keymint.HardwareAuthenticatorType; import android.net.Uri; import android.os.Binder; import android.os.Build; @@ -62,10 +65,16 @@ import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.ServiceSpecificException; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; +import android.security.Authorization; +import android.security.GateKeeper; import android.security.KeyStore; +import android.security.authorization.IKeystoreAuthorization; +import android.security.authorization.ResponseCode; +import android.service.gatekeeper.IGateKeeperService; import android.text.TextUtils; import android.util.ArraySet; import android.util.Pair; @@ -79,6 +88,7 @@ import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.DumpUtils; import com.android.server.SystemService; import com.android.server.biometrics.log.BiometricContext; +import com.android.server.utils.Slogf; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -116,6 +126,10 @@ public class BiometricService extends SystemService { KeyStore mKeyStore; @VisibleForTesting ITrustManager mTrustManager; + @VisibleForTesting + IKeystoreAuthorization mKeystoreAuthorization; + @VisibleForTesting + IGateKeeperService mGateKeeper; // Get and cache the available biometric authenticators and their associated info. final ArrayList<BiometricSensor> mSensors = new ArrayList<>(); @@ -616,6 +630,64 @@ public class BiometricService extends SystemService { } @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) + @Override // Binder call + public long getLastAuthenticationTime( + int userId, @Authenticators.Types int authenticators) { + super.getLastAuthenticationTime_enforcePermission(); + + if (!Flags.lastAuthenticationTime()) { + throw new UnsupportedOperationException(); + } + + Slogf.d(TAG, "getLastAuthenticationTime(userId=%d, authenticators=0x%x)", + userId, authenticators); + + final long secureUserId; + try { + secureUserId = mGateKeeper.getSecureUserId(userId); + } catch (RemoteException e) { + Slogf.w(TAG, "Failed to get secure user id for " + userId, e); + return BIOMETRIC_NO_AUTHENTICATION; + } + + if (secureUserId == GateKeeper.INVALID_SECURE_USER_ID) { + Slogf.w(TAG, "No secure user id for " + userId); + return BIOMETRIC_NO_AUTHENTICATION; + } + + ArrayList<Integer> hardwareAuthenticators = new ArrayList<>(2); + + if ((authenticators & Authenticators.DEVICE_CREDENTIAL) != 0) { + hardwareAuthenticators.add(HardwareAuthenticatorType.PASSWORD); + } + + if ((authenticators & Authenticators.BIOMETRIC_STRONG) != 0) { + hardwareAuthenticators.add(HardwareAuthenticatorType.FINGERPRINT); + } + + if (hardwareAuthenticators.isEmpty()) { + throw new IllegalArgumentException("authenticators must not be empty"); + } + + int[] authTypesArray = hardwareAuthenticators.stream() + .mapToInt(Integer::intValue) + .toArray(); + try { + return mKeystoreAuthorization.getLastAuthTime(secureUserId, authTypesArray); + } catch (RemoteException e) { + Slog.w(TAG, "Error getting last auth time: " + e); + return BiometricConstants.BIOMETRIC_NO_AUTHENTICATION; + } catch (ServiceSpecificException e) { + // This is returned when the feature flag test fails in keystore2 + if (e.errorCode == ResponseCode.PERMISSION_DENIED) { + throw new UnsupportedOperationException(); + } + + return BiometricConstants.BIOMETRIC_NO_AUTHENTICATION; + } + } + + @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override public boolean hasEnrolledBiometrics(int userId, String opPackageName) { @@ -937,6 +1009,14 @@ public class BiometricService extends SystemService { return ActivityManager.getService(); } + public IKeystoreAuthorization getKeystoreAuthorizationService() { + return Authorization.getService(); + } + + public IGateKeeperService getGateKeeperService() { + return GateKeeper.getService(); + } + public ITrustManager getTrustManager() { return ITrustManager.Stub.asInterface(ServiceManager.getService(Context.TRUST_SERVICE)); } @@ -1050,6 +1130,8 @@ public class BiometricService extends SystemService { mBiometricContext = injector.getBiometricContext(context); mUserManager = injector.getUserManager(context); mBiometricCameraManager = injector.getBiometricCameraManager(context); + mKeystoreAuthorization = injector.getKeystoreAuthorizationService(); + mGateKeeper = injector.getGateKeeperService(); try { injector.getActivityManagerService().registerUserSwitchObserver( diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index 152fef663c56..27fd8a66fff1 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -67,6 +67,7 @@ android_test { "ActivityContext", "coretests-aidl", "securebox", + "flag-junit", ], libs: [ diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java index f88afe7839c3..a78f2dcf2ab2 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java @@ -41,6 +41,8 @@ import android.app.AppOpsManager; import android.content.Context; import android.content.pm.PackageManager; import android.content.res.Resources; +import android.hardware.biometrics.BiometricManager; +import android.hardware.biometrics.Flags; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; import android.hardware.biometrics.IBiometricService; import android.hardware.biometrics.IBiometricServiceReceiver; @@ -53,6 +55,7 @@ import android.hardware.iris.IIrisService; import android.os.Binder; import android.os.UserHandle; import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.SetFlagsRule; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; @@ -84,6 +87,8 @@ public class AuthServiceTest { @Rule public MockitoRule mockitorule = MockitoJUnit.rule(); + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Mock private Context mContext; @Mock @@ -418,6 +423,37 @@ public class AuthServiceTest { eq(callback)); } + @Test(expected = UnsupportedOperationException.class) + public void testGetLastAuthenticationTime_flaggedOff_throwsUnsupportedOperationException() + throws Exception { + mSetFlagsRule.disableFlags(Flags.FLAG_LAST_AUTHENTICATION_TIME); + setInternalAndTestBiometricPermissions(mContext, true /* hasPermission */); + + mAuthService = new AuthService(mContext, mInjector); + mAuthService.onStart(); + + mAuthService.mImpl.getLastAuthenticationTime(0, + BiometricManager.Authenticators.BIOMETRIC_STRONG); + } + + @Test + public void testGetLastAuthenticationTime_flaggedOn_callsBiometricService() + throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_LAST_AUTHENTICATION_TIME); + setInternalAndTestBiometricPermissions(mContext, true /* hasPermission */); + + mAuthService = new AuthService(mContext, mInjector); + mAuthService.onStart(); + + final int userId = 0; + final int authenticators = BiometricManager.Authenticators.BIOMETRIC_STRONG; + + mAuthService.mImpl.getLastAuthenticationTime(userId, authenticators); + + waitForIdle(); + verify(mBiometricService).getLastAuthenticationTime(eq(userId), eq(authenticators)); + } + private static void setInternalAndTestBiometricPermissions( Context context, boolean hasPermission) { for (String p : List.of(TEST_BIOMETRIC, MANAGE_BIOMETRIC, USE_BIOMETRIC_INTERNAL)) { diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java index 0230d77e8e14..408442bcceed 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java @@ -62,6 +62,7 @@ import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricPrompt; +import android.hardware.biometrics.Flags; import android.hardware.biometrics.IBiometricAuthenticator; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; import android.hardware.biometrics.IBiometricSensorReceiver; @@ -71,12 +72,17 @@ import android.hardware.biometrics.IBiometricSysuiReceiver; import android.hardware.biometrics.PromptInfo; import android.hardware.display.DisplayManagerGlobal; import android.hardware.fingerprint.FingerprintManager; +import android.hardware.keymaster.HardwareAuthenticatorType; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; import android.os.UserManager; import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.SetFlagsRule; +import android.security.GateKeeper; import android.security.KeyStore; +import android.security.authorization.IKeystoreAuthorization; +import android.service.gatekeeper.IGateKeeperService; import android.view.Display; import android.view.DisplayInfo; import android.view.WindowManager; @@ -92,6 +98,7 @@ import com.android.server.biometrics.sensors.AuthSessionCoordinator; import com.android.server.biometrics.sensors.LockoutTracker; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.mockito.AdditionalMatchers; import org.mockito.ArgumentCaptor; @@ -105,6 +112,9 @@ import java.util.Random; @SmallTest public class BiometricServiceTest { + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + private static final String TEST_PACKAGE_NAME = "test_package"; private static final long TEST_REQUEST_ID = 44; @@ -162,10 +172,16 @@ public class BiometricServiceTest { @Mock private BiometricCameraManager mBiometricCameraManager; + @Mock + private IKeystoreAuthorization mKeystoreAuthService; + + @Mock + private IGateKeeperService mGateKeeperService; + BiometricContextProvider mBiometricContextProvider; @Before - public void setUp() { + public void setUp() throws RemoteException { MockitoAnnotations.initMocks(this); resetReceivers(); @@ -215,6 +231,9 @@ public class BiometricServiceTest { mStatusBarService, null /* handler */, mAuthSessionCoordinator); when(mInjector.getBiometricContext(any())).thenReturn(mBiometricContextProvider); + when(mInjector.getKeystoreAuthorizationService()).thenReturn(mKeystoreAuthService); + when(mInjector.getGateKeeperService()).thenReturn(mGateKeeperService); + when(mGateKeeperService.getSecureUserId(anyInt())).thenReturn(42L); final String[] config = { "0:2:15", // ID0:Fingerprint:Strong @@ -1751,6 +1770,44 @@ public class BiometricServiceTest { verifyNoMoreInteractions(callback); } + @Test(expected = UnsupportedOperationException.class) + public void testGetLastAuthenticationTime_flagOff_throwsUnsupportedOperationException() + throws RemoteException { + mSetFlagsRule.disableFlags(Flags.FLAG_LAST_AUTHENTICATION_TIME); + + mBiometricService = new BiometricService(mContext, mInjector); + mBiometricService.mImpl.getLastAuthenticationTime(0, Authenticators.BIOMETRIC_STRONG); + } + + @Test + public void testGetLastAuthenticationTime_flagOn_callsKeystoreAuthorization() + throws RemoteException { + mSetFlagsRule.enableFlags(Flags.FLAG_LAST_AUTHENTICATION_TIME); + + final int[] hardwareAuthenticators = new int[] { + HardwareAuthenticatorType.PASSWORD, + HardwareAuthenticatorType.FINGERPRINT + }; + + final int userId = 0; + final long secureUserId = mGateKeeperService.getSecureUserId(userId); + + assertNotEquals(GateKeeper.INVALID_SECURE_USER_ID, secureUserId); + + final long expectedResult = 31337L; + + when(mKeystoreAuthService.getLastAuthTime(eq(secureUserId), eq(hardwareAuthenticators))) + .thenReturn(expectedResult); + + mBiometricService = new BiometricService(mContext, mInjector); + + final long result = mBiometricService.mImpl.getLastAuthenticationTime(userId, + Authenticators.BIOMETRIC_STRONG | Authenticators.DEVICE_CREDENTIAL); + + assertEquals(expectedResult, result); + verify(mKeystoreAuthService).getLastAuthTime(eq(secureUserId), eq(hardwareAuthenticators)); + } + // Helper methods private int invokeCanAuthenticate(BiometricService service, int authenticators) |