Add support for fingerprint@2.2 and face@1.1

This CL adds the necessary plumbing, but not implementation, to do the
following:

biometrics.fingerprint@2.2:
- enroll_2_2(..., handle windowId)
- authenticate_2_2(..., handle windowId)

biometrics.face@1.1:
- enroll_1_1(..., handle windowId)
- enrollRemotely(...)

Bug: 148493694
Bug: 145562442
Test: enrolled and authenticated with Keyguard and BiometricPrompt
Test: compiled face@1.0 HAL as face@1.1 and made sure it works
Change-Id: I22e25df4d880d16d21a940e7dff6d233b64743d1
diff --git a/core/java/android/hardware/biometrics/BiometricNativeHandleUtils.java b/core/java/android/hardware/biometrics/BiometricNativeHandleUtils.java
new file mode 100644
index 0000000..5544eae
--- /dev/null
+++ b/core/java/android/hardware/biometrics/BiometricNativeHandleUtils.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.biometrics;
+
+import android.os.NativeHandle;
+import android.os.ParcelFileDescriptor;
+
+import java.io.IOException;
+
+/**
+ * A class that contains utilities for IBiometricNativeHandle.
+ *
+ * @hide
+ */
+public final class BiometricNativeHandleUtils {
+
+    private BiometricNativeHandleUtils() {
+    }
+
+    /**
+     * Converts a {@link NativeHandle} into an {@link IBiometricNativeHandle} by duplicating the
+     * underlying file descriptors.
+     *
+     * Both the original and new handle must be closed after use.
+     *
+     * @param h {@link NativeHandle}. Usually used to identify a WindowManager window. Can be null.
+     * @return A {@link IBiometricNativeHandle} representation of {@code h}. Will be null if
+     * {@code h} or its raw file descriptors are null.
+     */
+    public static IBiometricNativeHandle dup(NativeHandle h) {
+        IBiometricNativeHandle handle = null;
+        if (h != null && h.getFileDescriptors() != null && h.getInts() != null) {
+            handle = new IBiometricNativeHandle();
+            handle.ints = h.getInts().clone();
+            handle.fds = new ParcelFileDescriptor[h.getFileDescriptors().length];
+            for (int i = 0; i < h.getFileDescriptors().length; ++i) {
+                try {
+                    handle.fds[i] = ParcelFileDescriptor.dup(h.getFileDescriptors()[i]);
+                } catch (IOException e) {
+                    return null;
+                }
+            }
+        }
+        return handle;
+    }
+
+    /**
+     * Closes the handle's file descriptors.
+     *
+     * @param h {@link IBiometricNativeHandle} handle.
+     */
+    public static void close(IBiometricNativeHandle h) {
+        if (h != null) {
+            for (ParcelFileDescriptor fd : h.fds) {
+                if (fd != null) {
+                    try {
+                        fd.close();
+                    } catch (IOException e) {
+                        // do nothing.
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/core/java/android/hardware/biometrics/IBiometricNativeHandle.aidl b/core/java/android/hardware/biometrics/IBiometricNativeHandle.aidl
new file mode 100644
index 0000000..6dcdc1b
--- /dev/null
+++ b/core/java/android/hardware/biometrics/IBiometricNativeHandle.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.hardware.biometrics;
+
+/**
+ * Representation of a native handle.
+ * Copied from /common/aidl/android/hardware/common/NativeHandle.aidl
+ * @hide
+ */
+parcelable IBiometricNativeHandle {
+    ParcelFileDescriptor[] fds;
+    int[] ints;
+}
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 55ebe28..6bda46b 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -29,7 +29,9 @@
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricFaceConstants;
+import android.hardware.biometrics.BiometricNativeHandleUtils;
 import android.hardware.biometrics.CryptoObject;
+import android.hardware.biometrics.IBiometricNativeHandle;
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
 import android.os.Binder;
 import android.os.CancellationSignal;
@@ -38,6 +40,7 @@
 import android.os.IBinder;
 import android.os.IRemoteCallback;
 import android.os.Looper;
+import android.os.NativeHandle;
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.Trace;
@@ -245,6 +248,19 @@
     }
 
     /**
+     * Defaults to {@link FaceManager#enroll(int, byte[], CancellationSignal, EnrollmentCallback,
+     * int[], NativeHandle)} with {@code windowId} set to null.
+     *
+     * @see FaceManager#enroll(int, byte[], CancellationSignal, EnrollmentCallback, int[],
+     * NativeHandle)
+     */
+    @RequiresPermission(MANAGE_BIOMETRIC)
+    public void enroll(int userId, byte[] token, CancellationSignal cancel,
+            EnrollmentCallback callback, int[] disabledFeatures) {
+        enroll(userId, token, cancel, callback, disabledFeatures, null /* windowId */);
+    }
+
+    /**
      * Request face authentication enrollment. This call operates the face authentication hardware
      * and starts capturing images. Progress will be indicated by callbacks to the
      * {@link EnrollmentCallback} object. It terminates when
@@ -259,11 +275,13 @@
      * @param flags    optional flags
      * @param userId   the user to whom this face will belong to
      * @param callback an object to receive enrollment events
+     * @param windowId optional ID of a camera preview window for a single-camera device. Must be
+     *                 null if not used.
      * @hide
      */
     @RequiresPermission(MANAGE_BIOMETRIC)
     public void enroll(int userId, byte[] token, CancellationSignal cancel,
-            EnrollmentCallback callback, int[] disabledFeatures) {
+            EnrollmentCallback callback, int[] disabledFeatures, @Nullable NativeHandle windowId) {
         if (callback == null) {
             throw new IllegalArgumentException("Must supply an enrollment callback");
         }
@@ -278,20 +296,72 @@
         }
 
         if (mService != null) {
+            IBiometricNativeHandle handle = BiometricNativeHandleUtils.dup(windowId);
             try {
                 mEnrollmentCallback = callback;
                 Trace.beginSection("FaceManager#enroll");
                 mService.enroll(userId, mToken, token, mServiceReceiver,
-                        mContext.getOpPackageName(), disabledFeatures);
+                        mContext.getOpPackageName(), disabledFeatures, handle);
             } catch (RemoteException e) {
                 Log.w(TAG, "Remote exception in enroll: ", e);
-                if (callback != null) {
-                    // Though this may not be a hardware issue, it will cause apps to give up or
-                    // try again later.
-                    callback.onEnrollmentError(FACE_ERROR_HW_UNAVAILABLE,
-                            getErrorString(mContext, FACE_ERROR_HW_UNAVAILABLE,
+                // Though this may not be a hardware issue, it will cause apps to give up or
+                // try again later.
+                callback.onEnrollmentError(FACE_ERROR_HW_UNAVAILABLE,
+                        getErrorString(mContext, FACE_ERROR_HW_UNAVAILABLE,
                                 0 /* vendorCode */));
-                }
+            } finally {
+                Trace.endSection();
+                BiometricNativeHandleUtils.close(handle);
+            }
+        }
+    }
+
+    /**
+     * Request face authentication enrollment for a remote client, for example Android Auto.
+     * This call operates the face authentication hardware and starts capturing images.
+     * Progress will be indicated by callbacks to the
+     * {@link EnrollmentCallback} object. It terminates when
+     * {@link EnrollmentCallback#onEnrollmentError(int, CharSequence)} or
+     * {@link EnrollmentCallback#onEnrollmentProgress(int) is called with remaining == 0, at
+     * which point the object is no longer valid. The operation can be canceled by using the
+     * provided cancel object.
+     *
+     * @param token    a unique token provided by a recent creation or verification of device
+     *                 credentials (e.g. pin, pattern or password).
+     * @param cancel   an object that can be used to cancel enrollment
+     * @param userId   the user to whom this face will belong to
+     * @param callback an object to receive enrollment events
+     * @hide
+     */
+    @RequiresPermission(MANAGE_BIOMETRIC)
+    public void enrollRemotely(int userId, byte[] token, CancellationSignal cancel,
+            EnrollmentCallback callback, int[] disabledFeatures) {
+        if (callback == null) {
+            throw new IllegalArgumentException("Must supply an enrollment callback");
+        }
+
+        if (cancel != null) {
+            if (cancel.isCanceled()) {
+                Log.w(TAG, "enrollRemotely is already canceled.");
+                return;
+            } else {
+                cancel.setOnCancelListener(new OnEnrollCancelListener());
+            }
+        }
+
+        if (mService != null) {
+            try {
+                mEnrollmentCallback = callback;
+                Trace.beginSection("FaceManager#enrollRemotely");
+                mService.enrollRemotely(userId, mToken, token, mServiceReceiver,
+                        mContext.getOpPackageName(), disabledFeatures);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Remote exception in enrollRemotely: ", e);
+                // Though this may not be a hardware issue, it will cause apps to give up or
+                // try again later.
+                callback.onEnrollmentError(FACE_ERROR_HW_UNAVAILABLE,
+                        getErrorString(mContext, FACE_ERROR_HW_UNAVAILABLE,
+                                0 /* vendorCode */));
             } finally {
                 Trace.endSection();
             }
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index 68a4aef..8ba2473 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -15,6 +15,7 @@
  */
 package android.hardware.face;
 
+import android.hardware.biometrics.IBiometricNativeHandle;
 import android.hardware.biometrics.IBiometricServiceReceiverInternal;
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
 import android.hardware.face.IFaceServiceReceiver;
@@ -51,6 +52,10 @@
 
     // Start face enrollment
     void enroll(int userId, IBinder token, in byte [] cryptoToken, IFaceServiceReceiver receiver,
+            String opPackageName, in int [] disabledFeatures, in IBiometricNativeHandle windowId);
+
+    // Start remote face enrollment
+    void enrollRemotely(int userId, IBinder token, in byte [] cryptoToken, IFaceServiceReceiver receiver,
             String opPackageName, in int [] disabledFeatures);
 
     // Cancel enrollment in progress
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index ff9d145..f6717c7 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -32,7 +32,9 @@
 import android.content.pm.PackageManager;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricFingerprintConstants;
+import android.hardware.biometrics.BiometricNativeHandleUtils;
 import android.hardware.biometrics.BiometricPrompt;
+import android.hardware.biometrics.IBiometricNativeHandle;
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
 import android.os.Binder;
 import android.os.CancellationSignal;
@@ -41,6 +43,7 @@
 import android.os.IBinder;
 import android.os.IRemoteCallback;
 import android.os.Looper;
+import android.os.NativeHandle;
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -403,15 +406,33 @@
     }
 
     /**
-     * Per-user version, see {@link FingerprintManager#authenticate(CryptoObject,
-     * CancellationSignal, int, AuthenticationCallback, Handler)}. This version does not
-     * display the BiometricPrompt.
-     * @param userId the user ID that the fingerprint hardware will authenticate for.
+     * Defaults to {@link FingerprintManager#authenticate(CryptoObject, CancellationSignal, int,
+     * AuthenticationCallback, Handler, int, NativeHandle)} with {@code windowId} set to null.
+     *
+     * @see FingerprintManager#authenticate(CryptoObject, CancellationSignal, int,
+     * AuthenticationCallback, Handler, int, NativeHandle)
+     *
      * @hide
      */
     @RequiresPermission(anyOf = {USE_BIOMETRIC, USE_FINGERPRINT})
     public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel,
             int flags, @NonNull AuthenticationCallback callback, Handler handler, int userId) {
+        authenticate(crypto, cancel, flags, callback, handler, userId, null /* windowId */);
+    }
+
+    /**
+     * Per-user version, see {@link FingerprintManager#authenticate(CryptoObject,
+     * CancellationSignal, int, AuthenticationCallback, Handler)}. This version does not
+     * display the BiometricPrompt.
+     * @param userId the user ID that the fingerprint hardware will authenticate for.
+     * @param windowId for optical fingerprint sensors that require active illumination by the OLED
+     *        display. Should be null for devices that don't require illumination.
+     * @hide
+     */
+    @RequiresPermission(anyOf = {USE_BIOMETRIC, USE_FINGERPRINT})
+    public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel,
+            int flags, @NonNull AuthenticationCallback callback, Handler handler, int userId,
+            @Nullable NativeHandle windowId) {
         if (callback == null) {
             throw new IllegalArgumentException("Must supply an authentication callback");
         }
@@ -425,26 +446,44 @@
             }
         }
 
-        if (mService != null) try {
-            useHandler(handler);
-            mAuthenticationCallback = callback;
-            mCryptoObject = crypto;
-            long sessionId = crypto != null ? crypto.getOpId() : 0;
-            mService.authenticate(mToken, sessionId, userId, mServiceReceiver, flags,
-                    mContext.getOpPackageName());
-        } catch (RemoteException e) {
-            Slog.w(TAG, "Remote exception while authenticating: ", e);
-            if (callback != null) {
+        if (mService != null) {
+            IBiometricNativeHandle handle = BiometricNativeHandleUtils.dup(windowId);
+            try {
+                useHandler(handler);
+                mAuthenticationCallback = callback;
+                mCryptoObject = crypto;
+                long sessionId = crypto != null ? crypto.getOpId() : 0;
+                mService.authenticate(mToken, sessionId, userId, mServiceReceiver, flags,
+                        mContext.getOpPackageName(), handle);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Remote exception while authenticating: ", e);
                 // Though this may not be a hardware issue, it will cause apps to give up or try
                 // again later.
                 callback.onAuthenticationError(FINGERPRINT_ERROR_HW_UNAVAILABLE,
                         getErrorString(mContext, FINGERPRINT_ERROR_HW_UNAVAILABLE,
-                            0 /* vendorCode */));
+                                0 /* vendorCode */));
+            } finally {
+                BiometricNativeHandleUtils.close(handle);
             }
         }
     }
 
     /**
+     * Defaults to {@link FingerprintManager#enroll(byte[], CancellationSignal, int, int,
+     * EnrollmentCallback, NativeHandle)} with {@code windowId} set to null.
+     *
+     * @see FingerprintManager#enroll(byte[], CancellationSignal, int, int, EnrollmentCallback,
+     * NativeHandle)
+     *
+     * @hide
+     */
+    @RequiresPermission(MANAGE_FINGERPRINT)
+    public void enroll(byte [] token, CancellationSignal cancel, int flags,
+            int userId, EnrollmentCallback callback) {
+        enroll(token, cancel, flags, userId, callback, null /* windowId */);
+    }
+
+    /**
      * Request fingerprint enrollment. This call warms up the fingerprint hardware
      * and starts scanning for fingerprints. Progress will be indicated by callbacks to the
      * {@link EnrollmentCallback} object. It terminates when
@@ -462,7 +501,7 @@
      */
     @RequiresPermission(MANAGE_FINGERPRINT)
     public void enroll(byte [] token, CancellationSignal cancel, int flags,
-            int userId, EnrollmentCallback callback) {
+            int userId, EnrollmentCallback callback, @Nullable NativeHandle windowId) {
         if (userId == UserHandle.USER_CURRENT) {
             userId = getCurrentUserId();
         }
@@ -479,18 +518,21 @@
             }
         }
 
-        if (mService != null) try {
-            mEnrollmentCallback = callback;
-            mService.enroll(mToken, token, userId, mServiceReceiver, flags,
-                    mContext.getOpPackageName());
-        } catch (RemoteException e) {
-            Slog.w(TAG, "Remote exception in enroll: ", e);
-            if (callback != null) {
+        if (mService != null) {
+            IBiometricNativeHandle handle = BiometricNativeHandleUtils.dup(windowId);
+            try {
+                mEnrollmentCallback = callback;
+                mService.enroll(mToken, token, userId, mServiceReceiver, flags,
+                        mContext.getOpPackageName(), handle);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Remote exception in enroll: ", e);
                 // Though this may not be a hardware issue, it will cause apps to give up or try
                 // again later.
                 callback.onEnrollmentError(FINGERPRINT_ERROR_HW_UNAVAILABLE,
                         getErrorString(mContext, FINGERPRINT_ERROR_HW_UNAVAILABLE,
-                            0 /* vendorCode */));
+                                0 /* vendorCode */));
+            } finally {
+                BiometricNativeHandleUtils.close(handle);
             }
         }
     }
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index 1a7e128..f2ffd08d 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -15,6 +15,7 @@
  */
 package android.hardware.fingerprint;
 
+import android.hardware.biometrics.IBiometricNativeHandle;
 import android.hardware.biometrics.IBiometricServiceReceiverInternal;
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
 import android.hardware.fingerprint.IFingerprintClientActiveCallback;
@@ -31,7 +32,8 @@
     // USE_FINGERPRINT/USE_BIOMETRIC permission. This is effectively deprecated, since it only comes
     // through FingerprintManager now.
     void authenticate(IBinder token, long sessionId, int userId,
-            IFingerprintServiceReceiver receiver, int flags, String opPackageName);
+            IFingerprintServiceReceiver receiver, int flags, String opPackageName,
+            in IBiometricNativeHandle windowId);
 
     // This method prepares the service to start authenticating, but doesn't start authentication.
     // This is protected by the MANAGE_BIOMETRIC signatuer permission. This method should only be
@@ -40,7 +42,7 @@
     // startPreparedClient().
     void prepareForAuthentication(IBinder token, long sessionId, int userId,
             IBiometricServiceReceiverInternal wrapperReceiver, String opPackageName, int cookie,
-            int callingUid, int callingPid, int callingUserId);
+            int callingUid, int callingPid, int callingUserId, in IBiometricNativeHandle windowId);
 
     // Starts authentication with the previously prepared client.
     void startPreparedClient(int cookie);
@@ -55,7 +57,7 @@
 
     // Start fingerprint enrollment
     void enroll(IBinder token, in byte [] cryptoToken, int groupId, IFingerprintServiceReceiver receiver,
-            int flags, String opPackageName);
+            int flags, String opPackageName, in IBiometricNativeHandle windowId);
 
     // Cancel enrollment in progress
     void cancelEnrollment(IBinder token);
diff --git a/services/core/Android.bp b/services/core/Android.bp
index a603fa9..f33237f 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -115,8 +115,8 @@
         "android.hardware.health-V2.0-java",
         "android.hardware.light-java",
         "android.hardware.weaver-V1.0-java",
-        "android.hardware.biometrics.face-V1.0-java",
-        "android.hardware.biometrics.fingerprint-V2.1-java",
+        "android.hardware.biometrics.face-V1.1-java",
+        "android.hardware.biometrics.fingerprint-V2.2-java",
         "android.hardware.oemlock-V1.0-java",
         "android.hardware.configstore-V1.0-java",
         "android.hardware.contexthub-V1.0-java",
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/AuthenticationClient.java
index 766e5c4..7bbda9f 100644
--- a/services/core/java/com/android/server/biometrics/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/AuthenticationClient.java
@@ -20,11 +20,14 @@
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.IBiometricNativeHandle;
 import android.os.IBinder;
+import android.os.NativeHandle;
 import android.os.RemoteException;
 import android.security.KeyStore;
 import android.util.Slog;
 
+import java.io.IOException;
 import java.util.ArrayList;
 
 /**
@@ -41,6 +44,7 @@
     public static final int LOCKOUT_PERMANENT = 2;
 
     private final boolean mRequireConfirmation;
+    private final NativeHandle mWindowId;
 
     // We need to track this state since it's possible for applications to request for
     // authentication while the device is already locked out. In that case, the client is created
@@ -69,11 +73,25 @@
     public AuthenticationClient(Context context, Constants constants,
             BiometricServiceBase.DaemonWrapper daemon, long halDeviceId, IBinder token,
             BiometricServiceBase.ServiceListener listener, int targetUserId, int groupId, long opId,
-            boolean restricted, String owner, int cookie, boolean requireConfirmation) {
+            boolean restricted, String owner, int cookie, boolean requireConfirmation,
+            IBiometricNativeHandle windowId) {
         super(context, constants, daemon, halDeviceId, token, listener, targetUserId, groupId,
                 restricted, owner, cookie);
         mOpId = opId;
         mRequireConfirmation = requireConfirmation;
+        mWindowId = Utils.dupNativeHandle(windowId);
+    }
+
+    @Override
+    public void destroy() {
+        if (mWindowId != null && mWindowId.getFileDescriptors() != null) {
+            try {
+                mWindowId.close();
+            } catch (IOException e) {
+                Slog.e(getLogTag(), "Failed to close windowId NativeHandle: ", e);
+            }
+        }
+        super.destroy();
     }
 
     protected long getStartTimeMs() {
@@ -233,7 +251,7 @@
         onStart();
         try {
             mStartTimeMs = System.currentTimeMillis();
-            final int result = getDaemonWrapper().authenticate(mOpId, getGroupId());
+            final int result = getDaemonWrapper().authenticate(mOpId, getGroupId(), mWindowId);
             if (result != 0) {
                 Slog.w(getLogTag(), "startAuthentication failed, result=" + result);
                 mMetricsLogger.histogram(mConstants.tagAuthStartError(), result);
diff --git a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
index 687d935..0e70994 100644
--- a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
+++ b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
@@ -32,6 +32,7 @@
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.IBiometricNativeHandle;
 import android.hardware.biometrics.IBiometricService;
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
 import android.hardware.biometrics.IBiometricServiceReceiverInternal;
@@ -43,6 +44,7 @@
 import android.os.IBinder;
 import android.os.IHwBinder;
 import android.os.IRemoteCallback;
+import android.os.NativeHandle;
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
@@ -220,9 +222,10 @@
 
         public AuthenticationClientImpl(Context context, DaemonWrapper daemon, long halDeviceId,
                 IBinder token, ServiceListener listener, int targetUserId, int groupId, long opId,
-                boolean restricted, String owner, int cookie, boolean requireConfirmation) {
+                boolean restricted, String owner, int cookie, boolean requireConfirmation,
+                IBiometricNativeHandle windowId) {
             super(context, getConstants(), daemon, halDeviceId, token, listener, targetUserId,
-                    groupId, opId, restricted, owner, cookie, requireConfirmation);
+                    groupId, opId, restricted, owner, cookie, requireConfirmation, windowId);
         }
 
         @Override
@@ -283,10 +286,10 @@
         public EnrollClientImpl(Context context, DaemonWrapper daemon, long halDeviceId,
                 IBinder token, ServiceListener listener, int userId, int groupId,
                 byte[] cryptoToken, boolean restricted, String owner,
-                final int[] disabledFeatures, int timeoutSec) {
+                final int[] disabledFeatures, int timeoutSec, IBiometricNativeHandle windowId) {
             super(context, getConstants(), daemon, halDeviceId, token, listener,
                     userId, groupId, cryptoToken, restricted, owner, getBiometricUtils(),
-                    disabledFeatures, timeoutSec);
+                    disabledFeatures, timeoutSec, windowId);
         }
 
         @Override
@@ -472,12 +475,13 @@
      */
     protected interface DaemonWrapper {
         int ERROR_ESRCH = 3; // Likely HAL is dead. see errno.h.
-        int authenticate(long operationId, int groupId) throws RemoteException;
+        int authenticate(long operationId, int groupId, NativeHandle windowId)
+                throws RemoteException;
         int cancel() throws RemoteException;
         int remove(int groupId, int biometricId) throws RemoteException;
         int enumerate() throws RemoteException;
         int enroll(byte[] token, int groupId, int timeout,
-                ArrayList<Integer> disabledFeatures) throws RemoteException;
+                ArrayList<Integer> disabledFeatures, NativeHandle windowId) throws RemoteException;
         void resetLockout(byte[] token) throws RemoteException;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/EnrollClient.java b/services/core/java/com/android/server/biometrics/EnrollClient.java
index 7ebb7c0..684795e 100644
--- a/services/core/java/com/android/server/biometrics/EnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/EnrollClient.java
@@ -20,10 +20,13 @@
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.IBiometricNativeHandle;
 import android.os.IBinder;
+import android.os.NativeHandle;
 import android.os.RemoteException;
 import android.util.Slog;
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 
@@ -35,6 +38,7 @@
     private final BiometricUtils mBiometricUtils;
     private final int[] mDisabledFeatures;
     private final int mTimeoutSec;
+    private final NativeHandle mWindowId;
 
     private long mEnrollmentStartTimeMs;
 
@@ -44,13 +48,26 @@
             BiometricServiceBase.DaemonWrapper daemon, long halDeviceId, IBinder token,
             BiometricServiceBase.ServiceListener listener, int userId, int groupId,
             byte[] cryptoToken, boolean restricted, String owner, BiometricUtils utils,
-            final int[] disabledFeatures, int timeoutSec) {
+            final int[] disabledFeatures, int timeoutSec, IBiometricNativeHandle windowId) {
         super(context, constants, daemon, halDeviceId, token, listener, userId, groupId, restricted,
                 owner, 0 /* cookie */);
         mBiometricUtils = utils;
         mCryptoToken = Arrays.copyOf(cryptoToken, cryptoToken.length);
         mDisabledFeatures = Arrays.copyOf(disabledFeatures, disabledFeatures.length);
         mTimeoutSec = timeoutSec;
+        mWindowId = Utils.dupNativeHandle(windowId);
+    }
+
+    @Override
+    public void destroy() {
+        if (mWindowId != null && mWindowId.getFileDescriptors() != null) {
+            try {
+                mWindowId.close();
+            } catch (IOException e) {
+                Slog.e(getLogTag(), "Failed to close windowId NativeHandle: ", e);
+            }
+        }
+        super.destroy();
     }
 
     @Override
@@ -102,7 +119,7 @@
             }
 
             final int result = getDaemonWrapper().enroll(mCryptoToken, getGroupId(), mTimeoutSec,
-                    disabledFeatures);
+                    disabledFeatures, mWindowId);
             if (result != 0) {
                 Slog.w(getLogTag(), "startEnroll failed, result=" + result);
                 mMetricsLogger.histogram(mConstants.tagEnrollStartError(), result);
diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java
index 389763b..2d4ab63 100644
--- a/services/core/java/com/android/server/biometrics/Utils.java
+++ b/services/core/java/com/android/server/biometrics/Utils.java
@@ -23,12 +23,17 @@
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.BiometricPrompt;
 import android.hardware.biometrics.BiometricPrompt.AuthenticationResultType;
+import android.hardware.biometrics.IBiometricNativeHandle;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.NativeHandle;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.Slog;
 
+import java.io.FileDescriptor;
+import java.io.IOException;
+
 public class Utils {
     public static boolean isDebugEnabled(Context context, int targetUserId) {
         if (targetUserId == UserHandle.USER_NULL) {
@@ -237,4 +242,31 @@
                 throw new IllegalArgumentException("Unsupported dismissal reason: " + reason);
         }
     }
+
+    /**
+     * Converts an {@link IBiometricNativeHandle} to a {@link NativeHandle} by duplicating the
+     * the underlying file descriptors.
+     *
+     * Both the original and new handle must be closed after use.
+     *
+     * @param h {@link IBiometricNativeHandle} received as a binder call argument. Usually used to
+     *          identify a WindowManager window. Can be null.
+     * @return A {@link NativeHandle} representation of {@code h}. Will be null if either {@code h}
+     *          or its contents are null.
+     */
+    public static NativeHandle dupNativeHandle(IBiometricNativeHandle h) {
+        NativeHandle handle = null;
+        if (h != null && h.fds != null && h.ints != null) {
+            FileDescriptor[] fds = new FileDescriptor[h.fds.length];
+            for (int i = 0; i < h.fds.length; ++i) {
+                try {
+                    fds[i] = h.fds[i].dup().getFileDescriptor();
+                } catch (IOException e) {
+                    return null;
+                }
+            }
+            handle = new NativeHandle(fds, h.ints, true /* own */);
+        }
+        return handle;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java
index b512475..31c3d4d 100644
--- a/services/core/java/com/android/server/biometrics/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/face/FaceService.java
@@ -34,6 +34,7 @@
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.IBiometricNativeHandle;
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
 import android.hardware.biometrics.IBiometricServiceReceiverInternal;
 import android.hardware.biometrics.face.V1_0.IBiometricsFace;
@@ -214,9 +215,10 @@
         public FaceAuthClient(Context context,
                 DaemonWrapper daemon, long halDeviceId, IBinder token,
                 ServiceListener listener, int targetUserId, int groupId, long opId,
-                boolean restricted, String owner, int cookie, boolean requireConfirmation) {
+                boolean restricted, String owner, int cookie, boolean requireConfirmation,
+                IBiometricNativeHandle windowId) {
             super(context, daemon, halDeviceId, token, listener, targetUserId, groupId, opId,
-                    restricted, owner, cookie, requireConfirmation);
+                    restricted, owner, cookie, requireConfirmation, windowId);
         }
 
         @Override
@@ -373,7 +375,7 @@
         @Override // Binder call
         public void enroll(int userId, final IBinder token, final byte[] cryptoToken,
                 final IFaceServiceReceiver receiver, final String opPackageName,
-                final int[] disabledFeatures) {
+                final int[] disabledFeatures, IBiometricNativeHandle windowId) {
             checkPermission(MANAGE_BIOMETRIC);
             updateActiveGroup(userId, opPackageName);
 
@@ -384,7 +386,7 @@
             final EnrollClientImpl client = new EnrollClientImpl(getContext(), mDaemonWrapper,
                     mHalDeviceId, token, new ServiceListenerImpl(receiver), mCurrentUserId,
                     0 /* groupId */, cryptoToken, restricted, opPackageName, disabledFeatures,
-                    ENROLL_TIMEOUT_SEC) {
+                    ENROLL_TIMEOUT_SEC, windowId) {
 
                 @Override
                 public int[] getAcquireIgnorelist() {
@@ -411,6 +413,14 @@
         }
 
         @Override // Binder call
+        public void enrollRemotely(int userId, final IBinder token, final byte[] cryptoToken,
+                final IFaceServiceReceiver receiver, final String opPackageName,
+                final int[] disabledFeatures) {
+            checkPermission(MANAGE_BIOMETRIC);
+            // TODO(b/145027036): Implement this.
+        }
+
+        @Override // Binder call
         public void cancelEnrollment(final IBinder token) {
             checkPermission(MANAGE_BIOMETRIC);
             cancelEnrollmentInternal(token);
@@ -426,7 +436,7 @@
             final AuthenticationClientImpl client = new FaceAuthClient(getContext(),
                     mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver),
                     mCurrentUserId, 0 /* groupId */, opId, restricted, opPackageName,
-                    0 /* cookie */, false /* requireConfirmation */);
+                    0 /* cookie */, false /* requireConfirmation */, null /* windowId */);
             authenticateInternal(client, opId, opPackageName);
         }
 
@@ -442,7 +452,7 @@
                     mDaemonWrapper, mHalDeviceId, token,
                     new BiometricPromptServiceListenerImpl(wrapperReceiver),
                     mCurrentUserId, 0 /* groupId */, opId, restricted, opPackageName, cookie,
-                    requireConfirmation);
+                    requireConfirmation, null /* windowId */);
             authenticateInternal(client, opId, opPackageName, callingUid, callingPid,
                     callingUserId);
         }
@@ -985,7 +995,8 @@
      */
     private final DaemonWrapper mDaemonWrapper = new DaemonWrapper() {
         @Override
-        public int authenticate(long operationId, int groupId) throws RemoteException {
+        public int authenticate(long operationId, int groupId, NativeHandle windowId)
+                throws RemoteException {
             IBiometricsFace daemon = getFaceDaemon();
             if (daemon == null) {
                 Slog.w(TAG, "authenticate(): no face HAL!");
@@ -1026,7 +1037,7 @@
 
         @Override
         public int enroll(byte[] cryptoToken, int groupId, int timeout,
-                ArrayList<Integer> disabledFeatures) throws RemoteException {
+                ArrayList<Integer> disabledFeatures, NativeHandle windowId) throws RemoteException {
             IBiometricsFace daemon = getFaceDaemon();
             if (daemon == null) {
                 Slog.w(TAG, "enroll(): no face HAL!");
@@ -1036,7 +1047,17 @@
             for (int i = 0; i < cryptoToken.length; i++) {
                 token.add(cryptoToken[i]);
             }
-            return daemon.enroll(token, timeout, disabledFeatures);
+            android.hardware.biometrics.face.V1_1.IBiometricsFace daemon11 =
+                    android.hardware.biometrics.face.V1_1.IBiometricsFace.castFrom(
+                            daemon);
+            if (daemon11 != null) {
+                return daemon11.enroll_1_1(token, timeout, disabledFeatures, windowId);
+            } else if (windowId == null) {
+                return daemon.enroll(token, timeout, disabledFeatures);
+            } else {
+                Slog.e(TAG, "enroll(): windowId is only supported in @1.1 HAL");
+                return ERROR_ESRCH;
+            }
         }
 
         @Override
diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintAuthenticator.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintAuthenticator.java
index 6150de1..7a4e62e 100644
--- a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintAuthenticator.java
@@ -38,7 +38,7 @@
             String opPackageName, int cookie, int callingUid, int callingPid, int callingUserId)
             throws RemoteException {
         mFingerprintService.prepareForAuthentication(token, sessionId, userId, wrapperReceiver,
-                opPackageName, cookie, callingUid, callingPid, callingUserId);
+                opPackageName, cookie, callingUid, callingPid, callingUserId, null /* windowId */);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
index 44797ad..57d1867 100644
--- a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
@@ -37,6 +37,7 @@
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.IBiometricNativeHandle;
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
 import android.hardware.biometrics.IBiometricServiceReceiverInternal;
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
@@ -50,6 +51,7 @@
 import android.os.Build;
 import android.os.Environment;
 import android.os.IBinder;
+import android.os.NativeHandle;
 import android.os.RemoteException;
 import android.os.SELinux;
 import android.os.SystemClock;
@@ -132,9 +134,9 @@
                 DaemonWrapper daemon, long halDeviceId, IBinder token,
                 ServiceListener listener, int targetUserId, int groupId, long opId,
                 boolean restricted, String owner, int cookie,
-                boolean requireConfirmation) {
+                boolean requireConfirmation, IBiometricNativeHandle windowId) {
             super(context, daemon, halDeviceId, token, listener, targetUserId, groupId, opId,
-                    restricted, owner, cookie, requireConfirmation);
+                    restricted, owner, cookie, requireConfirmation, windowId);
         }
 
         @Override
@@ -198,7 +200,7 @@
         @Override // Binder call
         public void enroll(final IBinder token, final byte[] cryptoToken, final int userId,
                 final IFingerprintServiceReceiver receiver, final int flags,
-                final String opPackageName) {
+                final String opPackageName, IBiometricNativeHandle windowId) {
             checkPermission(MANAGE_FINGERPRINT);
 
             final boolean restricted = isRestricted();
@@ -206,7 +208,7 @@
             final EnrollClientImpl client = new EnrollClientImpl(getContext(), mDaemonWrapper,
                     mHalDeviceId, token, new ServiceListenerImpl(receiver), mCurrentUserId, groupId,
                     cryptoToken, restricted, opPackageName, new int[0] /* disabledFeatures */,
-                    ENROLL_TIMEOUT_SEC) {
+                    ENROLL_TIMEOUT_SEC, windowId) {
                 @Override
                 public boolean shouldVibrate() {
                     return true;
@@ -230,20 +232,22 @@
         @Override // Binder call
         public void authenticate(final IBinder token, final long opId, final int groupId,
                 final IFingerprintServiceReceiver receiver, final int flags,
-                final String opPackageName) {
+                final String opPackageName, IBiometricNativeHandle windowId) {
             updateActiveGroup(groupId, opPackageName);
             final boolean restricted = isRestricted();
             final AuthenticationClientImpl client = new FingerprintAuthClient(getContext(),
                     mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver),
                     mCurrentUserId, groupId, opId, restricted, opPackageName,
-                    0 /* cookie */, false /* requireConfirmation */);
+                    0 /* cookie */, false /* requireConfirmation */,
+                    windowId);
             authenticateInternal(client, opId, opPackageName);
         }
 
         @Override // Binder call
         public void prepareForAuthentication(IBinder token, long opId, int groupId,
                 IBiometricServiceReceiverInternal wrapperReceiver, String opPackageName,
-                int cookie, int callingUid, int callingPid, int callingUserId) {
+                int cookie, int callingUid, int callingPid, int callingUserId,
+                IBiometricNativeHandle windowId) {
             checkPermission(MANAGE_BIOMETRIC);
             updateActiveGroup(groupId, opPackageName);
             final boolean restricted = true; // BiometricPrompt is always restricted
@@ -251,7 +255,8 @@
                     mDaemonWrapper, mHalDeviceId, token,
                     new BiometricPromptServiceListenerImpl(wrapperReceiver),
                     mCurrentUserId, groupId, opId, restricted, opPackageName, cookie,
-                    false /* requireConfirmation */);
+                    false /* requireConfirmation */,
+                    windowId);
             authenticateInternal(client, opId, opPackageName, callingUid, callingPid,
                     callingUserId);
         }
@@ -654,13 +659,24 @@
      */
     private final DaemonWrapper mDaemonWrapper = new DaemonWrapper() {
         @Override
-        public int authenticate(long operationId, int groupId) throws RemoteException {
+        public int authenticate(long operationId, int groupId, NativeHandle windowId)
+                throws RemoteException {
             IBiometricsFingerprint daemon = getFingerprintDaemon();
             if (daemon == null) {
                 Slog.w(TAG, "authenticate(): no fingerprint HAL!");
                 return ERROR_ESRCH;
             }
-            return daemon.authenticate(operationId, groupId);
+            android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprint daemon22 =
+                    android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprint.castFrom(
+                            daemon);
+            if (daemon22 != null) {
+                return daemon22.authenticate_2_2(operationId, groupId, windowId);
+            } else if (windowId == null) {
+                return daemon.authenticate(operationId, groupId);
+            } else {
+                Slog.e(TAG, "authenticate(): windowId is only supported in @2.2 HAL");
+                return ERROR_ESRCH;
+            }
         }
 
         @Override
@@ -695,13 +711,27 @@
 
         @Override
         public int enroll(byte[] cryptoToken, int groupId, int timeout,
-                ArrayList<Integer> disabledFeatures) throws RemoteException {
+                ArrayList<Integer> disabledFeatures, NativeHandle windowId) throws RemoteException {
             IBiometricsFingerprint daemon = getFingerprintDaemon();
             if (daemon == null) {
                 Slog.w(TAG, "enroll(): no fingerprint HAL!");
                 return ERROR_ESRCH;
             }
-            return daemon.enroll(cryptoToken, groupId, timeout);
+            android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprint daemon22 =
+                    android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprint.castFrom(
+                            daemon);
+            if (daemon22 != null) {
+                ArrayList<Byte> cryptoTokenAsList = new ArrayList<>(cryptoToken.length);
+                for (byte b : cryptoToken) {
+                    cryptoTokenAsList.add(b);
+                }
+                return daemon22.enroll_2_2(cryptoTokenAsList, groupId, timeout, windowId);
+            } else if (windowId == null) {
+                return daemon.enroll(cryptoToken, groupId, timeout);
+            } else {
+                Slog.e(TAG, "enroll(): windowId is only supported in @2.2 HAL");
+                return ERROR_ESRCH;
+            }
         }
 
         @Override