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
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 2596f9c..3ae447f 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -390,6 +390,7 @@
     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 @@
     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.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 249c0e43..67a3627 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -5079,6 +5079,8 @@
      * @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 3ba8be4..8165d44 100644
--- a/core/java/android/hardware/biometrics/flags.aconfig
+++ b/core/java/android/hardware/biometrics/flags.aconfig
@@ -28,3 +28,10 @@
   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 02304b5..bae5e7f 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -18,18 +18,23 @@
 
 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.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.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 @@
     @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 @@
         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 @@
     }
 
     /**
-     * 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 @@
     }
 
     /**
-     * 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 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 @@
 
         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 @@
     }
 
     /**
+     * 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 @@
     }
 
     /**
-     * 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 @@
                                             PowerManager.PARTIAL_WAKE_LOCK,
                                             "faceLockoutResetCallback");
                                     wakeLock.acquire();
-                                    mHandler.post(() -> {
+                                    mExecutor.execute(() -> {
                                         try {
                                             callback.onLockoutReset(sensorId);
                                         } finally {
@@ -1268,70 +1350,6 @@
         }
     }
 
-    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 0096877..e267e6b 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -45,7 +45,7 @@
     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 @@
     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 @@
     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 4a82675..35c0a32 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 b843ad7..d816d085 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 @@
 
 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 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 java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.concurrent.Executor;
 
 @Presubmit
 @RunWith(MockitoJUnitRunner.class)
@@ -78,6 +83,8 @@
     @Mock
     private FaceManager.AuthenticationCallback mAuthCallback;
     @Mock
+    private BiometricPrompt.AuthenticationCallback mBioAuthCallback;
+    @Mock
     private FaceManager.EnrollmentCallback mEnrollmentCallback;
     @Mock
     private FaceManager.FaceDetectionCallback mFaceDetectionCallback;
@@ -91,13 +98,16 @@
     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 @@
     }
 
     @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 4be75f8..eac2147 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -427,6 +427,7 @@
         <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 3dfc454..61e0ca8 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 0f964bb..73f3999 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 @@
             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 @@
                     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 @@
             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 0089d4c..5e5181b 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 3aaac2e..c8a5583d 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 @@
     }
 
     @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)