diff options
| author | 2024-01-12 19:44:19 +0800 | |
|---|---|---|
| committer | 2024-01-19 13:47:16 +0800 | |
| commit | 75435ea60adc4b5cb9e51e4865ab6362daf9cae6 (patch) | |
| tree | a7a9d88ef1b879d59bbc7fc0b21f45e49248a215 | |
| parent | f18b4fa982d16616f1e5e3e68d885641e44453a2 (diff) | |
Expose `FaceManager` APIs as `@SystemApi`.
Enable privileged apps with `USE_BACKGROUND_FACE_AUTHENTICATION` permission access to the face auth APIs for background face auth.
The first use case is Pixel Health (go/rppg-prd). Design doc: go/pixel-health-face-auth (b/312858933)
Android Feature Request Bug: b/305272520
Bug: 318309705
Test: Unit tests.
Change-Id: I037fbc15353cea6fd8f9a97e1e86e90305716f83
12 files changed, 245 insertions, 115 deletions
diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 2596f9c6c39c..3ae447f8c200 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -390,6 +390,7 @@ package android { field @Deprecated public static final String UPDATE_TIME_ZONE_RULES = "android.permission.UPDATE_TIME_ZONE_RULES"; field public static final String UPGRADE_RUNTIME_PERMISSIONS = "android.permission.UPGRADE_RUNTIME_PERMISSIONS"; field public static final String USER_ACTIVITY = "android.permission.USER_ACTIVITY"; + field @FlaggedApi("android.hardware.biometrics.face_background_authentication") public static final String USE_BACKGROUND_FACE_AUTHENTICATION = "android.permission.USE_BACKGROUND_FACE_AUTHENTICATION"; field public static final String USE_COLORIZED_NOTIFICATIONS = "android.permission.USE_COLORIZED_NOTIFICATIONS"; field public static final String USE_RESERVED_DISK = "android.permission.USE_RESERVED_DISK"; field public static final String UWB_PRIVILEGED = "android.permission.UWB_PRIVILEGED"; @@ -3546,6 +3547,7 @@ package android.content { field @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") public static final String ECM_ENHANCED_CONFIRMATION_SERVICE = "ecm_enhanced_confirmation"; field public static final String ETHERNET_SERVICE = "ethernet"; field public static final String EUICC_CARD_SERVICE = "euicc_card"; + field @FlaggedApi("android.hardware.biometrics.face_background_authentication") public static final String FACE_SERVICE = "face"; field public static final String FONT_SERVICE = "font"; field public static final String HDMI_CONTROL_SERVICE = "hdmi_control"; field public static final String MEDIA_TRANSCODING_SERVICE = "media_transcoding"; @@ -4726,6 +4728,15 @@ package android.hardware.display { } +package android.hardware.face { + + @FlaggedApi("android.hardware.biometrics.face_background_authentication") public class FaceManager { + method @FlaggedApi("android.hardware.biometrics.face_background_authentication") @RequiresPermission(android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION) public void authenticateInBackground(@Nullable java.util.concurrent.Executor, @Nullable android.hardware.biometrics.BiometricPrompt.CryptoObject, @Nullable android.os.CancellationSignal, @NonNull android.hardware.biometrics.BiometricPrompt.AuthenticationCallback); + method @FlaggedApi("android.hardware.biometrics.face_background_authentication") @RequiresPermission(anyOf={"android.permission.USE_BIOMETRIC_INTERNAL", android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION}) public boolean hasEnrolledTemplates(); + } + +} + package android.hardware.hdmi { public abstract class HdmiClient { diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 249c0e434e78..67a3627a399f 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -5079,6 +5079,8 @@ public abstract class Context { * @see #getSystemService * @see android.hardware.face.FaceManager */ + @FlaggedApi(android.hardware.biometrics.Flags.FLAG_FACE_BACKGROUND_AUTHENTICATION) + @SystemApi public static final String FACE_SERVICE = "face"; /** diff --git a/core/java/android/hardware/biometrics/flags.aconfig b/core/java/android/hardware/biometrics/flags.aconfig index 3ba8be4cc2ab..8165d44b251a 100644 --- a/core/java/android/hardware/biometrics/flags.aconfig +++ b/core/java/android/hardware/biometrics/flags.aconfig @@ -28,3 +28,10 @@ flag { bug: "302735104" } +flag { + name: "face_background_authentication" + namespace: "biometrics_framework" + description: "Feature flag for allowing face background authentication with USE_BACKGROUND_FACE_AUTHENTICATION." + bug: "318584190" +} + diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java index 02304b5ba4f3..bae5e7f83569 100644 --- a/core/java/android/hardware/face/FaceManager.java +++ b/core/java/android/hardware/face/FaceManager.java @@ -18,18 +18,23 @@ package android.hardware.face; import static android.Manifest.permission.INTERACT_ACROSS_USERS; import static android.Manifest.permission.MANAGE_BIOMETRIC; +import static android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION; import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL; import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_NONE; +import static android.hardware.biometrics.Flags.FLAG_FACE_BACKGROUND_AUTHENTICATION; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.SystemApi; import android.annotation.SystemService; import android.content.Context; import android.content.pm.PackageManager; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricFaceConstants; +import android.hardware.biometrics.BiometricPrompt; import android.hardware.biometrics.BiometricStateListener; import android.hardware.biometrics.CryptoObject; import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; @@ -37,9 +42,9 @@ import android.os.Binder; import android.os.CancellationSignal; import android.os.CancellationSignal.OnCancelListener; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.IBinder; import android.os.IRemoteCallback; -import android.os.Looper; import android.os.PowerManager; import android.os.RemoteException; import android.os.Trace; @@ -49,15 +54,21 @@ import android.util.Slog; import android.view.Surface; import com.android.internal.R; -import com.android.internal.os.SomeArgs; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.Executor; /** * A class that coordinates access to the face authentication hardware. + * + * <p>Please use {@link BiometricPrompt} for face authentication unless the experience must be + * customized for unique system-level utilities, like the lock screen or ambient background usage. + * * @hide */ +@FlaggedApi(FLAG_FACE_BACKGROUND_AUTHENTICATION) +@SystemApi @SystemService(Context.FACE_SERVICE) public class FaceManager implements BiometricAuthenticator, BiometricFaceConstants { @@ -88,81 +99,76 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan @Nullable private GenerateChallengeCallback mGenerateChallengeCallback; private CryptoObject mCryptoObject; private Face mRemovalFace; - private Handler mHandler; + private Executor mExecutor; private List<FaceSensorPropertiesInternal> mProps = new ArrayList<>(); private final IFaceServiceReceiver mServiceReceiver = new IFaceServiceReceiver.Stub() { @Override // binder call public void onEnrollResult(Face face, int remaining) { - mHandler.obtainMessage(MSG_ENROLL_RESULT, remaining, 0, face).sendToTarget(); + mExecutor.execute(() -> sendEnrollResult(face, remaining)); } @Override // binder call public void onAcquired(int acquireInfo, int vendorCode) { - mHandler.obtainMessage(MSG_ACQUIRED, acquireInfo, vendorCode).sendToTarget(); + mExecutor.execute(() -> sendAcquiredResult(acquireInfo, vendorCode)); } @Override // binder call public void onAuthenticationSucceeded(Face face, int userId, boolean isStrongBiometric) { - mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId, - isStrongBiometric ? 1 : 0, face).sendToTarget(); + mExecutor.execute(() -> sendAuthenticatedSucceeded(face, userId, isStrongBiometric)); } @Override // binder call public void onFaceDetected(int sensorId, int userId, boolean isStrongBiometric) { - mHandler.obtainMessage(MSG_FACE_DETECTED, sensorId, userId, isStrongBiometric) - .sendToTarget(); + mExecutor.execute(() -> sendFaceDetected(sensorId, userId, isStrongBiometric)); } @Override // binder call public void onAuthenticationFailed() { - mHandler.obtainMessage(MSG_AUTHENTICATION_FAILED).sendToTarget(); + mExecutor.execute(() -> sendAuthenticatedFailed()); } @Override // binder call public void onError(int error, int vendorCode) { - mHandler.obtainMessage(MSG_ERROR, error, vendorCode).sendToTarget(); + mExecutor.execute(() -> sendErrorResult(error, vendorCode)); } @Override // binder call public void onRemoved(Face face, int remaining) { - mHandler.obtainMessage(MSG_REMOVED, remaining, 0, face).sendToTarget(); - if (remaining == 0) { - Settings.Secure.putIntForUser(mContext.getContentResolver(), - Settings.Secure.FACE_UNLOCK_RE_ENROLL, 0, - UserHandle.USER_CURRENT); - } + mExecutor.execute(() -> { + sendRemovedResult(face, remaining); + if (remaining == 0) { + Settings.Secure.putIntForUser(mContext.getContentResolver(), + Settings.Secure.FACE_UNLOCK_RE_ENROLL, 0, + UserHandle.USER_CURRENT); + } + }); } @Override public void onFeatureSet(boolean success, int feature) { - mHandler.obtainMessage(MSG_SET_FEATURE_COMPLETED, feature, 0, success).sendToTarget(); + mExecutor.execute(() -> sendSetFeatureCompleted(success, feature)); } @Override public void onFeatureGet(boolean success, int[] features, boolean[] featureState) { - SomeArgs args = SomeArgs.obtain(); - args.arg1 = success; - args.arg2 = features; - args.arg3 = featureState; - mHandler.obtainMessage(MSG_GET_FEATURE_COMPLETED, args).sendToTarget(); + mExecutor.execute(() -> sendGetFeatureCompleted(success, features, featureState)); } @Override public void onChallengeGenerated(int sensorId, int userId, long challenge) { - mHandler.obtainMessage(MSG_CHALLENGE_GENERATED, sensorId, userId, challenge) - .sendToTarget(); + mExecutor.execute(() -> sendChallengeGenerated(sensorId, userId, challenge)); } @Override public void onAuthenticationFrame(FaceAuthenticationFrame frame) { - mHandler.obtainMessage(MSG_AUTHENTICATION_FRAME, frame).sendToTarget(); + mExecutor.execute(() -> sendAuthenticationFrame(frame)); } @Override public void onEnrollmentFrame(FaceEnrollFrame frame) { - mHandler.obtainMessage(MSG_ENROLLMENT_FRAME, frame).sendToTarget(); + mExecutor.execute(() -> sendEnrollmentFrame(frame)); } }; @@ -175,7 +181,7 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan if (mService == null) { Slog.v(TAG, "FaceAuthenticationManagerService was null"); } - mHandler = new MyHandler(context); + mExecutor = context.getMainExecutor(); if (context.checkCallingOrSelfPermission(USE_BIOMETRIC_INTERNAL) == PackageManager.PERMISSION_GRANTED) { addAuthenticatorsRegisteredCallback(new IFaceAuthenticatorsRegisteredCallback.Stub() { @@ -189,18 +195,16 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan } /** - * Use the provided handler thread for events. + * Returns an {@link Executor} for the given {@link Handler} or the main {@link Executor} if + * {@code handler} is {@code null}. */ - private void useHandler(Handler handler) { - if (handler != null) { - mHandler = new MyHandler(handler.getLooper()); - } else if (mHandler.getLooper() != mContext.getMainLooper()) { - mHandler = new MyHandler(mContext.getMainLooper()); - } + private @NonNull Executor createExecutorForHandlerIfNeeded(@Nullable Handler handler) { + return handler != null ? new HandlerExecutor(handler) : mContext.getMainExecutor(); } /** * @deprecated use {@link #authenticate(CryptoObject, CancellationSignal, AuthenticationCallback, Handler, FaceAuthenticateOptions)}. + * @hide */ @Deprecated @RequiresPermission(USE_BIOMETRIC_INTERNAL) @@ -212,17 +216,22 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan } /** - * Request authentication. This call operates the face recognition hardware and starts capturing images. + * Request authentication. + * + * <p>This call operates the face recognition hardware and starts capturing images. * It terminates when * {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)} or * {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)} is called, at * which point the object is no longer valid. The operation can be canceled by using the - * provided cancel object. + * provided {@code cancel} object. * - * @param crypto object associated with the call or null if none required - * @param cancel an object that can be used to cancel authentication + * @param crypto the cryptographic operations to use for authentication or {@code null} if + * none required + * @param cancel an object that can be used to cancel authentication or {@code null} if not + * needed * @param callback an object to receive authentication events - * @param handler an optional handler to handle callback events + * @param handler an optional handler to handle callback events or {@code null} to obtain main + * {@link Executor} from {@link Context} * @param options additional options to customize this request * @throws IllegalArgumentException if the crypto operation is not supported or is not backed * by @@ -235,6 +244,14 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel, @NonNull AuthenticationCallback callback, @Nullable Handler handler, @NonNull FaceAuthenticateOptions options) { + authenticate(crypto, cancel, callback, createExecutorForHandlerIfNeeded(handler), + options, false /* allowBackgroundAuthentication */); + } + + @RequiresPermission(anyOf = {USE_BIOMETRIC_INTERNAL, USE_BACKGROUND_FACE_AUTHENTICATION}) + private void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel, + @NonNull AuthenticationCallback callback, @NonNull Executor executor, + @NonNull FaceAuthenticateOptions options, boolean allowBackgroundAuthentication) { if (callback == null) { throw new IllegalArgumentException("Must supply an authentication callback"); } @@ -249,13 +266,15 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan if (mService != null) { try { - useHandler(handler); + mExecutor = executor; mAuthenticationCallback = callback; mCryptoObject = crypto; final long operationId = crypto != null ? crypto.getOpId() : 0; Trace.beginSection("FaceManager#authenticate"); - final long authId = mService.authenticate( - mToken, operationId, mServiceReceiver, options); + final long authId = allowBackgroundAuthentication + ? mService.authenticateInBackground( + mToken, operationId, mServiceReceiver, options) + : mService.authenticate(mToken, operationId, mServiceReceiver, options); if (cancel != null) { cancel.setOnCancelListener(new OnAuthenticationCancelListener(authId)); } @@ -273,6 +292,67 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan } /** + * Request background face authentication. + * + * <p>This call operates the face recognition hardware and starts capturing images. + * It terminates when + * {@link BiometricPrompt.AuthenticationCallback#onAuthenticationError(int, CharSequence)} or + * {@link BiometricPrompt.AuthenticationCallback#onAuthenticationSucceeded( + * BiometricPrompt.AuthenticationResult)} is called, at which point the object is no longer + * valid. The operation can be canceled by using the provided cancel object. + * + * <p>See {@link BiometricPrompt#authenticate} for more details. Please use + * {@link BiometricPrompt} for face authentication unless the experience must be customized for + * unique system-level utilities, like the lock screen or ambient background usage. + * + * @param executor the specified {@link Executor} to handle callback events; if {@code null}, + * the callback will be executed on the main {@link Executor}. + * @param crypto the cryptographic operations to use for authentication or {@code null} if + * none required. + * @param cancel an object that can be used to cancel authentication or {@code null} if not + * needed. + * @param callback an object to receive authentication events. + * @throws IllegalArgumentException if the crypto operation is not supported or is not backed + * by + * <a href="{@docRoot}training/articles/keystore.html">Android + * Keystore facility</a>. + * @hide + */ + @RequiresPermission(USE_BACKGROUND_FACE_AUTHENTICATION) + @FlaggedApi(FLAG_FACE_BACKGROUND_AUTHENTICATION) + @SystemApi + public void authenticateInBackground(@Nullable Executor executor, + @Nullable BiometricPrompt.CryptoObject crypto, @Nullable CancellationSignal cancel, + @NonNull BiometricPrompt.AuthenticationCallback callback) { + authenticate(crypto, cancel, new AuthenticationCallback() { + @Override + public void onAuthenticationError(int errorCode, CharSequence errString) { + callback.onAuthenticationError(errorCode, errString); + } + + @Override + public void onAuthenticationHelp(int helpCode, CharSequence helpString) { + callback.onAuthenticationHelp(helpCode, helpString); + } + + @Override + public void onAuthenticationSucceeded(AuthenticationResult result) { + callback.onAuthenticationSucceeded( + new BiometricPrompt.AuthenticationResult( + crypto, + BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC)); + } + + @Override + public void onAuthenticationFailed() { + callback.onAuthenticationFailed(); + } + }, executor == null ? mContext.getMainExecutor() : executor, + new FaceAuthenticateOptions.Builder().build(), + true /* allowBackgroundAuthentication */); + } + + /** * Uses the face hardware to detect for the presence of a face, without giving details about * accept/reject/lockout. * @hide @@ -628,12 +708,14 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan } /** - * Determine if there is a face enrolled. + * Determine if there are enrolled {@link Face} templates. * - * @return true if a face is enrolled, false otherwise + * @return {@code true} if there are enrolled {@link Face} templates, {@code false} otherwise * @hide */ - @RequiresPermission(USE_BIOMETRIC_INTERNAL) + @RequiresPermission(anyOf = {USE_BIOMETRIC_INTERNAL, USE_BACKGROUND_FACE_AUTHENTICATION}) + @FlaggedApi(FLAG_FACE_BACKGROUND_AUTHENTICATION) + @SystemApi public boolean hasEnrolledTemplates() { return hasEnrolledTemplates(UserHandle.myUserId()); } @@ -798,7 +880,7 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan PowerManager.PARTIAL_WAKE_LOCK, "faceLockoutResetCallback"); wakeLock.acquire(); - mHandler.post(() -> { + mExecutor.execute(() -> { try { callback.onLockoutReset(sensorId); } finally { @@ -1268,70 +1350,6 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan } } - private class MyHandler extends Handler { - private MyHandler(Context context) { - super(context.getMainLooper()); - } - - private MyHandler(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(android.os.Message msg) { - Trace.beginSection("FaceManager#handleMessage: " + Integer.toString(msg.what)); - switch (msg.what) { - case MSG_ENROLL_RESULT: - sendEnrollResult((Face) msg.obj, msg.arg1 /* remaining */); - break; - case MSG_ACQUIRED: - sendAcquiredResult(msg.arg1 /* acquire info */, msg.arg2 /* vendorCode */); - break; - case MSG_AUTHENTICATION_SUCCEEDED: - sendAuthenticatedSucceeded((Face) msg.obj, msg.arg1 /* userId */, - msg.arg2 == 1 /* isStrongBiometric */); - break; - case MSG_AUTHENTICATION_FAILED: - sendAuthenticatedFailed(); - break; - case MSG_ERROR: - sendErrorResult(msg.arg1 /* errMsgId */, msg.arg2 /* vendorCode */); - break; - case MSG_REMOVED: - sendRemovedResult((Face) msg.obj, msg.arg1 /* remaining */); - break; - case MSG_SET_FEATURE_COMPLETED: - sendSetFeatureCompleted((boolean) msg.obj /* success */, - msg.arg1 /* feature */); - break; - case MSG_GET_FEATURE_COMPLETED: - SomeArgs args = (SomeArgs) msg.obj; - sendGetFeatureCompleted((boolean) args.arg1 /* success */, - (int[]) args.arg2 /* features */, - (boolean[]) args.arg3 /* featureState */); - args.recycle(); - break; - case MSG_CHALLENGE_GENERATED: - sendChallengeGenerated(msg.arg1 /* sensorId */, msg.arg2 /* userId */, - (long) msg.obj /* challenge */); - break; - case MSG_FACE_DETECTED: - sendFaceDetected(msg.arg1 /* sensorId */, msg.arg2 /* userId */, - (boolean) msg.obj /* isStrongBiometric */); - break; - case MSG_AUTHENTICATION_FRAME: - sendAuthenticationFrame((FaceAuthenticationFrame) msg.obj /* frame */); - break; - case MSG_ENROLLMENT_FRAME: - sendEnrollmentFrame((FaceEnrollFrame) msg.obj /* frame */); - break; - default: - Slog.w(TAG, "Unknown message: " + msg.what); - } - Trace.endSection(); - } - } - private void sendSetFeatureCompleted(boolean success, int feature) { if (mSetFeatureCallback == null) { return; diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl index 0096877f548a..e267e6b22f9d 100644 --- a/core/java/android/hardware/face/IFaceService.aidl +++ b/core/java/android/hardware/face/IFaceService.aidl @@ -45,7 +45,7 @@ interface IFaceService { byte[] dumpSensorServiceStateProto(int sensorId, boolean clearSchedulerBuffer); // Retrieve static sensor properties for all face sensors - @EnforcePermission("USE_BIOMETRIC_INTERNAL") + @EnforcePermission(anyOf = {"USE_BIOMETRIC_INTERNAL", "USE_BACKGROUND_FACE_AUTHENTICATION"}) List<FaceSensorPropertiesInternal> getSensorPropertiesInternal(String opPackageName); // Retrieve static sensor properties for the specified sensor @@ -57,6 +57,11 @@ interface IFaceService { long authenticate(IBinder token, long operationId, IFaceServiceReceiver receiver, in FaceAuthenticateOptions options); + // Authenticate with a face. A requestId is returned that can be used to cancel this operation. + @EnforcePermission("USE_BACKGROUND_FACE_AUTHENTICATION") + long authenticateInBackground(IBinder token, long operationId, IFaceServiceReceiver receiver, + in FaceAuthenticateOptions options); + // Uses the face hardware to detect for the presence of a face, without giving details // about accept/reject/lockout. A requestId is returned that can be used to cancel this // operation. @@ -131,7 +136,7 @@ interface IFaceService { void revokeChallenge(IBinder token, int sensorId, int userId, String opPackageName, long challenge); // Determine if a user has at least one enrolled face - @EnforcePermission("USE_BIOMETRIC_INTERNAL") + @EnforcePermission(anyOf = {"USE_BIOMETRIC_INTERNAL", "USE_BACKGROUND_FACE_AUTHENTICATION"}) boolean hasEnrolledFaces(int sensorId, int userId, String opPackageName); // Return the LockoutTracker status for the specified user diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 4a82675715d8..35c0a3250ad1 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -6601,6 +6601,13 @@ <permission android:name="android.permission.USE_BIOMETRIC_INTERNAL" android:protectionLevel="signature" /> + <!-- Allows privileged apps to access the background face authentication. + @SystemApi + @FlaggedApi("android.hardware.biometrics.face_background_authentication") + @hide --> + <permission android:name="android.permission.USE_BACKGROUND_FACE_AUTHENTICATION" + android:protectionLevel="signature|privileged" /> + <!-- Allows the system to control the BiometricDialog (SystemUI). Reserved for the system. @hide --> <permission android:name="android.permission.MANAGE_BIOMETRIC_DIALOG" android:protectionLevel="signature" /> diff --git a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java index b843ad75ac0f..d816d0853ab6 100644 --- a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java +++ b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java @@ -18,6 +18,7 @@ package android.hardware.face; import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE; import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS; +import static android.hardware.biometrics.Flags.FLAG_FACE_BACKGROUND_AUTHENTICATION; import static com.google.common.truth.Truth.assertThat; @@ -35,12 +36,15 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.res.Resources; +import android.hardware.biometrics.BiometricPrompt; import android.os.CancellationSignal; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.IBinder; import android.os.RemoteException; import android.os.test.TestLooper; import android.platform.test.annotations.Presubmit; +import android.platform.test.annotations.RequiresFlagsEnabled; import com.android.internal.R; @@ -58,6 +62,7 @@ import org.mockito.junit.MockitoRule; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.concurrent.Executor; @Presubmit @RunWith(MockitoJUnitRunner.class) @@ -78,6 +83,8 @@ public class FaceManagerTest { @Mock private FaceManager.AuthenticationCallback mAuthCallback; @Mock + private BiometricPrompt.AuthenticationCallback mBioAuthCallback; + @Mock private FaceManager.EnrollmentCallback mEnrollmentCallback; @Mock private FaceManager.FaceDetectionCallback mFaceDetectionCallback; @@ -91,13 +98,16 @@ public class FaceManagerTest { private TestLooper mLooper; private Handler mHandler; private FaceManager mFaceManager; + private Executor mExecutor; @Before public void setUp() throws Exception { mLooper = new TestLooper(); mHandler = new Handler(mLooper.getLooper()); + mExecutor = new HandlerExecutor(mHandler); when(mContext.getMainLooper()).thenReturn(mLooper.getLooper()); + when(mContext.getMainExecutor()).thenReturn(mExecutor); when(mContext.getOpPackageName()).thenReturn(PACKAGE_NAME); when(mContext.getAttributionTag()).thenReturn(ATTRIBUTION_TAG); when(mContext.getApplicationInfo()).thenReturn(new ApplicationInfo()); @@ -159,6 +169,19 @@ public class FaceManagerTest { } @Test + @RequiresFlagsEnabled(FLAG_FACE_BACKGROUND_AUTHENTICATION) + public void authenticateInBackground_errorWhenUnavailable() throws Exception { + when(mService.authenticateInBackground(any(), anyLong(), any(), any())) + .thenThrow(new RemoteException()); + + mFaceManager.authenticateInBackground(mExecutor, null, new CancellationSignal(), + mBioAuthCallback); + mLooper.dispatchAll(); + + verify(mBioAuthCallback).onAuthenticationError(eq(FACE_ERROR_HW_UNAVAILABLE), any()); + } + + @Test public void enrollment_errorWhenFaceEnrollmentExists() throws RemoteException { when(mResources.getInteger(R.integer.config_faceMaxTemplatesPerUser)).thenReturn(1); when(mService.getEnrolledFaces(anyInt(), anyInt(), anyString())) diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 4be75f83422e..eac2147b02f8 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -427,6 +427,7 @@ applications that come with the platform <permission name="android.permission.USE_BIOMETRIC" /> <permission name="android.permission.TEST_BIOMETRIC" /> <permission name="android.permission.MANAGE_BIOMETRIC_DIALOG" /> + <permission name="android.permission.USE_BACKGROUND_FACE_AUTHENTICATION" /> <!-- Permissions required for CTS test - CtsContactsProviderTestCases --> <permission name="android.contacts.permission.MANAGE_SIM_ACCOUNTS" /> <!-- Permissions required for CTS test - CtsHdmiCecHostTestCases --> diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 3dfc4540d6e7..61e0ca8b589c 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -562,6 +562,9 @@ <!-- Permission required for CTS test - android.server.biometrics --> <uses-permission android:name="android.permission.MANAGE_BIOMETRIC_DIALOG" /> + <!-- Permission required for CTS test - android.server.biometrics --> + <uses-permission android:name="android.permission.USE_BACKGROUND_FACE_AUTHENTICATION" /> + <!-- Permissions required for CTS test - NotificationManagerTest --> <uses-permission android:name="android.permission.MANAGE_NOTIFICATION_LISTENERS" /> diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java index 0f964bb75944..73f3999f40ce 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java @@ -143,7 +143,11 @@ public class FaceService extends SystemService { return proto.getBytes(); } - @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) + @android.annotation.EnforcePermission( + anyOf = { + android.Manifest.permission.USE_BIOMETRIC_INTERNAL, + android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION + }) @Override // Binder call public List<FaceSensorPropertiesInternal> getSensorPropertiesInternal( String opPackageName) { @@ -285,6 +289,29 @@ public class FaceService extends SystemService { restricted, statsClient, isKeyguard); } + @android.annotation.EnforcePermission( + android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION) + @Override // Binder call + public long authenticateInBackground(final IBinder token, final long operationId, + final IFaceServiceReceiver receiver, final FaceAuthenticateOptions options) { + // TODO(b/152413782): If the sensor supports face detect and the device is encrypted or + // lockdown, something wrong happened. See similar path in FingerprintService. + + super.authenticateInBackground_enforcePermission(); + + final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider(); + if (provider == null) { + Slog.w(TAG, "Null provider for authenticate"); + return -1; + } + options.setSensorId(provider.first); + + return provider.second.scheduleAuthenticate(token, operationId, + 0 /* cookie */, new ClientMonitorCallbackConverter(receiver), options, + false /* restricted */, BiometricsProtoEnums.CLIENT_UNKNOWN /* statsClient */, + true /* allowBackgroundAuthentication */); + } + @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override // Binder call public long detectFace(final IBinder token, @@ -548,7 +575,11 @@ public class FaceService extends SystemService { return provider.getEnrolledFaces(sensorId, userId); } - @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) + @android.annotation.EnforcePermission( + anyOf = { + android.Manifest.permission.USE_BIOMETRIC_INTERNAL, + android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION + }) @Override // Binder call public boolean hasEnrolledFaces(int sensorId, int userId, String opPackageName) { super.hasEnrolledFaces_enforcePermission(); diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index 0089d4cafaad..5e5181bdfeeb 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -109,6 +109,7 @@ <uses-permission android:name="android.permission.UPDATE_LOCK_TASK_PACKAGES" /> <uses-permission android:name="android.permission.ACCESS_CONTEXT_HUB" /> <uses-permission android:name="android.permission.USE_BIOMETRIC_INTERNAL" /> + <uses-permission android:name="android.permission.USE_BACKGROUND_FACE_AUTHENTICATION" /> <uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION" /> <uses-permission android:name="android.permission.MANAGE_ROLE_HOLDERS" /> diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java index 3aaac2e9cf1b..c8a5583de0b2 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java @@ -16,6 +16,7 @@ package com.android.server.biometrics.sensors.face; +import static android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION; import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL; import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG; import static android.hardware.face.FaceSensorProperties.TYPE_UNKNOWN; @@ -234,6 +235,26 @@ public class FaceServiceTest { } @Test + public void testAuthenticateInBackground() throws Exception { + FaceAuthenticateOptions faceAuthenticateOptions = new FaceAuthenticateOptions.Builder() + .build(); + initService(); + mFaceService.mServiceWrapper.registerAuthenticators(List.of()); + waitForRegistration(); + + mContext.getTestablePermissions().setPermission( + USE_BIOMETRIC_INTERNAL, PackageManager.PERMISSION_DENIED); + mContext.getTestablePermissions().setPermission( + USE_BACKGROUND_FACE_AUTHENTICATION, PackageManager.PERMISSION_GRANTED); + + final long operationId = 5; + mFaceService.mServiceWrapper.authenticateInBackground(mToken, operationId, + mFaceServiceReceiver, faceAuthenticateOptions); + + assertThat(faceAuthenticateOptions.getSensorId()).isEqualTo(ID_DEFAULT); + } + + @Test public void testOptionsForDetect() throws Exception { FaceAuthenticateOptions faceAuthenticateOptions = new FaceAuthenticateOptions.Builder() .setOpPackageName(ComponentName.unflattenFromString(OP_PACKAGE_NAME) |