Merge "Nit: PackageManagerService"
diff --git a/Android.bp b/Android.bp
index 4d2e5e1..dd5ef92 100644
--- a/Android.bp
+++ b/Android.bp
@@ -152,6 +152,9 @@
         "core/java/android/hardware/display/IDisplayManagerCallback.aidl",
         "core/java/android/hardware/display/IVirtualDisplayCallback.aidl",
         "core/java/android/hardware/fingerprint/IFingerprintClientActiveCallback.aidl",
+        "core/java/android/hardware/face/IFaceService.aidl",
+        "core/java/android/hardware/face/IFaceServiceLockoutResetCallback.aidl",
+        "core/java/android/hardware/face/IFaceServiceReceiver.aidl",
         "core/java/android/hardware/fingerprint/IFingerprintService.aidl",
         "core/java/android/hardware/fingerprint/IFingerprintServiceLockoutResetCallback.aidl",
         "core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl",
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 8639849..090e584 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -350,8 +350,10 @@
     public static final int OP_START_FOREGROUND = 76;
     /** @hide */
     public static final int OP_BLUETOOTH_SCAN = 77;
+    /** @hide Use the face authentication API. */
+    public static final int OP_USE_FACE = 78;
     /** @hide */
-    public static final int _NUM_OP = 78;
+    public static final int _NUM_OP = 79;
 
     /** Access to coarse location information. */
     public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -596,6 +598,11 @@
     /** @hide */
     public static final String OPSTR_BLUETOOTH_SCAN = "android:bluetooth_scan";
 
+    /** @hide Use the face authentication API. */
+    public static final String OPSTR_USE_FACE = "android:use_FACE";
+
+
+
     // Warning: If an permission is added here it also has to be added to
     // com.android.packageinstaller.permission.utils.EventLogger
     private static final int[] RUNTIME_AND_APPOP_PERMISSIONS_OPS = {
@@ -733,6 +740,7 @@
             OP_MANAGE_IPSEC_TUNNELS,            // MANAGE_IPSEC_HANDOVERS
             OP_START_FOREGROUND,                // START_FOREGROUND
             OP_COARSE_LOCATION,                 // BLUETOOTH_SCAN
+            OP_USE_FACE,                        // FACE
     };
 
     /**
@@ -817,6 +825,7 @@
             OPSTR_MANAGE_IPSEC_TUNNELS,
             OPSTR_START_FOREGROUND,
             OPSTR_BLUETOOTH_SCAN,
+            OPSTR_USE_FACE,
     };
 
     /**
@@ -902,6 +911,7 @@
             "MANAGE_IPSEC_TUNNELS",
             "START_FOREGROUND",
             "BLUETOOTH_SCAN",
+            "USE_FACE",
     };
 
     /**
@@ -987,6 +997,7 @@
             null, // no permission for OP_MANAGE_IPSEC_TUNNELS
             Manifest.permission.FOREGROUND_SERVICE,
             null, // no permission for OP_BLUETOOTH_SCAN
+            Manifest.permission.USE_BIOMETRIC,
     };
 
     /**
@@ -1073,6 +1084,7 @@
             null, // MANAGE_IPSEC_TUNNELS
             null, // START_FOREGROUND
             null, // maybe should be UserManager.DISALLOW_SHARE_LOCATION, //BLUETOOTH_SCAN
+            null, // USE_FACE
     };
 
     /**
@@ -1158,6 +1170,7 @@
             false, // MANAGE_IPSEC_HANDOVERS
             false, // START_FOREGROUND
             true, // BLUETOOTH_SCAN
+            false, // USE_FACE
     };
 
     /**
@@ -1242,6 +1255,7 @@
             AppOpsManager.MODE_ERRORED,  // MANAGE_IPSEC_TUNNELS
             AppOpsManager.MODE_ALLOWED,  // OP_START_FOREGROUND
             AppOpsManager.MODE_ALLOWED,  // OP_BLUETOOTH_SCAN
+            AppOpsManager.MODE_ALLOWED,  // USE_FACE
     };
 
     /**
@@ -1330,6 +1344,7 @@
             false, // MANAGE_IPSEC_TUNNELS
             false, // START_FOREGROUND
             false, // BLUETOOTH_SCAN
+            false, // USE_FACE
     };
 
     /**
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index ba355f9..1ad3054 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -644,7 +644,7 @@
 
     @Nullable
     private Uri restoreSoundUri(Context context, @Nullable Uri uri) {
-        if (uri == null) {
+        if (uri == null || Uri.EMPTY.equals(uri)) {
             return null;
         }
         ContentResolver contentResolver = context.getContentResolver();
@@ -680,7 +680,7 @@
 
     private Uri getSoundForBackup(Context context) {
         Uri sound = getSound();
-        if (sound == null) {
+        if (sound == null || Uri.EMPTY.equals(sound)) {
             return null;
         }
         Uri canonicalSound = context.getContentResolver().canonicalize(sound);
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index e5f143c..98dc237 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -54,6 +54,8 @@
 import android.hardware.SystemSensorManager;
 import android.hardware.camera2.CameraManager;
 import android.hardware.display.DisplayManager;
+import android.hardware.face.FaceManager;
+import android.hardware.face.IFaceService;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.IFingerprintService;
 import android.hardware.hdmi.HdmiControlManager;
@@ -791,6 +793,22 @@
                 return new FingerprintManager(ctx.getOuterContext(), service);
             }});
 
+        registerService(Context.FACE_SERVICE, FaceManager.class,
+                new CachedServiceFetcher<FaceManager>() {
+                    @Override
+                    public FaceManager createService(ContextImpl ctx)
+                            throws ServiceNotFoundException {
+                        final IBinder binder;
+                        if (ctx.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.O) {
+                            binder = ServiceManager.getServiceOrThrow(Context.FACE_SERVICE);
+                        } else {
+                            binder = ServiceManager.getService(Context.FACE_SERVICE);
+                        }
+                        IFaceService service = IFaceService.Stub.asInterface(binder);
+                        return new FaceManager(ctx.getOuterContext(), service);
+                    }
+                });
+
         registerService(Context.TV_INPUT_SERVICE, TvInputManager.class,
                 new CachedServiceFetcher<TvInputManager>() {
             @Override
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index fde756c..6ad6c25 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -996,17 +996,29 @@
     }
 
     /**
-     * If the current wallpaper is a live wallpaper component, return the
-     * information about that wallpaper.  Otherwise, if it is a static image,
-     * simply return null.
+     * Returns the information about the wallpaper if the current wallpaper is
+     * a live wallpaper component. Otherwise, if the wallpaper is a static image,
+     * this returns null.
      */
     public WallpaperInfo getWallpaperInfo() {
+        return getWallpaperInfo(mContext.getUserId());
+    }
+
+    /**
+     * Returns the information about the wallpaper if the current wallpaper is
+     * a live wallpaper component. Otherwise, if the wallpaper is a static image,
+     * this returns null.
+     *
+     * @param userId Owner of the wallpaper.
+     * @hide
+     */
+    public WallpaperInfo getWallpaperInfo(int userId) {
         try {
             if (sGlobals.mService == null) {
                 Log.w(TAG, "WallpaperService not running");
                 throw new RuntimeException(new DeadSystemException());
             } else {
-                return sGlobals.mService.getWallpaperInfo(mContext.getUserId());
+                return sGlobals.mService.getWallpaperInfo(userId);
             }
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 7ba4447..5e7f1e4 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -3237,8 +3237,8 @@
 
     /**
      * Called by a device/profile owner to set the timeout after which unlocking with secondary, non
-     * strong auth (e.g. fingerprint, trust agents) times out, i.e. the user has to use a strong
-     * authentication method like password, pin or pattern.
+     * strong auth (e.g. fingerprint, face, trust agents) times out, i.e. the user has to use a
+     * strong authentication method like password, pin or pattern.
      *
      * <p>This timeout is used internally to reset the timer to require strong auth again after
      * specified timeout each time it has been successfully used.
@@ -3710,7 +3710,6 @@
             | DevicePolicyManager.KEYGUARD_DISABLE_IRIS
             | DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT;
 
-
     /**
      * Disable all current and future keyguard customizations.
      */
@@ -4898,10 +4897,10 @@
     /**
      * @hide
      */
-    public void reportFailedFingerprintAttempt(int userHandle) {
+    public void reportFailedBiometricAttempt(int userHandle) {
         if (mService != null) {
             try {
-                mService.reportFailedFingerprintAttempt(userHandle);
+                mService.reportFailedBiometricAttempt(userHandle);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -4911,10 +4910,10 @@
     /**
      * @hide
      */
-    public void reportSuccessfulFingerprintAttempt(int userHandle) {
+    public void reportSuccessfulBiometricAttempt(int userHandle) {
         if (mService != null) {
             try {
-                mService.reportSuccessfulFingerprintAttempt(userHandle);
+                mService.reportSuccessfulBiometricAttempt(userHandle);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index e3b160f..c95bc5b 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -132,8 +132,8 @@
     void reportPasswordChanged(int userId);
     void reportFailedPasswordAttempt(int userHandle);
     void reportSuccessfulPasswordAttempt(int userHandle);
-    void reportFailedFingerprintAttempt(int userHandle);
-    void reportSuccessfulFingerprintAttempt(int userHandle);
+    void reportFailedBiometricAttempt(int userHandle);
+    void reportSuccessfulBiometricAttempt(int userHandle);
     void reportKeyguardDismissed(int userHandle);
     void reportKeyguardSecured(int userHandle);
 
diff --git a/core/java/android/app/trust/ITrustManager.aidl b/core/java/android/app/trust/ITrustManager.aidl
index 6d65e3e..9985cc0 100644
--- a/core/java/android/app/trust/ITrustManager.aidl
+++ b/core/java/android/app/trust/ITrustManager.aidl
@@ -17,6 +17,7 @@
 package android.app.trust;
 
 import android.app.trust.ITrustListener;
+import android.hardware.biometrics.BiometricSourceType;
 
 /**
  * System private API to comunicate with trust service.
@@ -34,6 +35,6 @@
     boolean isDeviceLocked(int userId);
     boolean isDeviceSecure(int userId);
     boolean isTrustUsuallyManaged(int userId);
-    void unlockedByFingerprintForUser(int userId);
-    void clearAllFingerprints();
+    void unlockedByBiometricForUser(int userId, in BiometricSourceType source);
+    void clearAllBiometricRecognized(in BiometricSourceType target);
 }
diff --git a/core/java/android/app/trust/TrustManager.java b/core/java/android/app/trust/TrustManager.java
index 8ab0b70..fb27bed 100644
--- a/core/java/android/app/trust/TrustManager.java
+++ b/core/java/android/app/trust/TrustManager.java
@@ -20,6 +20,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemService;
 import android.content.Context;
+import android.hardware.biometrics.BiometricSourceType;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -195,26 +196,28 @@
     }
 
     /**
-     * Updates the trust state for the user due to the user unlocking via fingerprint.
-     * Should only be called if user authenticated via fingerprint and bouncer can be skipped.
+     * Updates the trust state for the user due to the user unlocking via a biometric sensor.
+     * Should only be called if user authenticated via fingerprint, face, or iris and bouncer
+     * can be skipped.
+     *
      * @param userId
      */
     @RequiresPermission(Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE)
-    public void unlockedByFingerprintForUser(int userId) {
+    public void unlockedByBiometricForUser(int userId, BiometricSourceType source) {
         try {
-            mService.unlockedByFingerprintForUser(userId);
+            mService.unlockedByBiometricForUser(userId, source);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
 
     /**
-     * Clears authenticated fingerprints for all users.
+     * Clears authentication by the specified biometric type for all users.
      */
     @RequiresPermission(Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE)
-    public void clearAllFingerprints() {
+    public void clearAllBiometricRecognized(BiometricSourceType source) {
         try {
-            mService.clearAllFingerprints();
+            mService.clearAllBiometricRecognized(source);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index c0cfb90..5e96b92 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3005,6 +3005,7 @@
             NSD_SERVICE,
             AUDIO_SERVICE,
             FINGERPRINT_SERVICE,
+            //@hide: FACE_SERVICE,
             MEDIA_ROUTER_SERVICE,
             TELEPHONY_SERVICE,
             TELEPHONY_SUBSCRIPTION_SERVICE,
@@ -3652,6 +3653,18 @@
 
     /**
      * Use with {@link #getSystemService(String)} to retrieve a
+     * Use with {@link #getSystemService} to retrieve a
+     * {@link android.hardware.face.FaceManager} for handling management
+     * of face authentication.
+     *
+     * @hide
+     * @see #getSystemService
+     * @see android.hardware.face.FaceManager
+     */
+    public static final String FACE_SERVICE = "face";
+
+    /**
+     * Use with {@link #getSystemService} to retrieve a
      * {@link android.media.MediaRouter} for controlling and managing
      * routing of media.
      *
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 43b6984..a76bc3a 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2245,12 +2245,20 @@
     /**
      * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature}: The device has biometric hardware to detect a fingerprint.
-      */
+     */
     @SdkConstant(SdkConstantType.FEATURE)
     public static final String FEATURE_FINGERPRINT = "android.hardware.fingerprint";
 
     /**
      * Feature for {@link #getSystemAvailableFeatures} and
+     * {@link #hasSystemFeature}: The device has biometric hardware to perform face authentication.
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_FACE = "android.hardware.face";
+
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature}: The device supports portrait orientation
      * screens.  For backwards compatibility, you can assume that if neither
      * this nor {@link #FEATURE_SCREEN_LANDSCAPE} is set then the device supports
diff --git a/core/java/android/hardware/biometrics/BiometricSourceType.aidl b/core/java/android/hardware/biometrics/BiometricSourceType.aidl
new file mode 100644
index 0000000..15440d8
--- /dev/null
+++ b/core/java/android/hardware/biometrics/BiometricSourceType.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2018 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;
+
+/**
+ * @hide
+ */
+parcelable BiometricSourceType;
\ No newline at end of file
diff --git a/core/java/android/hardware/biometrics/BiometricSourceType.java b/core/java/android/hardware/biometrics/BiometricSourceType.java
new file mode 100644
index 0000000..4a08cf2
--- /dev/null
+++ b/core/java/android/hardware/biometrics/BiometricSourceType.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2018 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.Parcel;
+import android.os.Parcelable;
+
+/**
+ * @hide
+ */
+public enum BiometricSourceType implements Parcelable {
+    FINGERPRINT,
+    FACE,
+    IRIS;
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(name());
+    }
+
+    public static final Creator<BiometricSourceType> CREATOR = new Creator<BiometricSourceType>() {
+        @Override
+        public BiometricSourceType createFromParcel(final Parcel source) {
+            return BiometricSourceType.valueOf(source.readString());
+        }
+
+        @Override
+        public BiometricSourceType[] newArray(final int size) {
+            return new BiometricSourceType[size];
+        }
+    };
+}
diff --git a/core/java/android/hardware/face/Face.aidl b/core/java/android/hardware/face/Face.aidl
new file mode 100644
index 0000000..a7c9141
--- /dev/null
+++ b/core/java/android/hardware/face/Face.aidl
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2018 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.face;
+
+/**
+ * @hide
+ */
+parcelable Face;
diff --git a/core/java/android/hardware/face/Face.java b/core/java/android/hardware/face/Face.java
new file mode 100644
index 0000000..c07351d
--- /dev/null
+++ b/core/java/android/hardware/face/Face.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2018 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.face;
+
+import android.hardware.biometrics.BiometricAuthenticator;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Container for face metadata.
+ *
+ * @hide
+ */
+public final class Face extends BiometricAuthenticator.BiometricIdentifier {
+    private CharSequence mName;
+    private int mFaceId;
+    private long mDeviceId; // physical device this face is associated with
+
+    public Face(CharSequence name, int faceId, long deviceId) {
+        mName = name;
+        mFaceId = faceId;
+        mDeviceId = deviceId;
+    }
+
+    private Face(Parcel in) {
+        mName = in.readString();
+        mFaceId = in.readInt();
+        mDeviceId = in.readLong();
+    }
+
+    /**
+     * Gets the human-readable name for the given fingerprint.
+     * @return name given to finger
+     */
+    public CharSequence getName() {
+        return mName;
+    }
+
+    /**
+     * Gets the device-specific finger id.  Used by Settings to map a name to a specific
+     * fingerprint template.
+     * @return device-specific id for this finger
+     * @hide
+     */
+    public int getFaceId() {
+        return mFaceId;
+    }
+
+    /**
+     * Device this face belongs to.
+     *
+     * @hide
+     */
+    public long getDeviceId() {
+        return mDeviceId;
+    }
+
+    /**
+     * Describes the contents.
+     * @return
+     */
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Writes to a parcel.
+     * @param out
+     * @param flags Additional flags about how the object should be written.
+     */
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeString(mName.toString());
+        out.writeInt(mFaceId);
+        out.writeLong(mDeviceId);
+    }
+
+    public static final Parcelable.Creator<Face> CREATOR = new Parcelable.Creator<Face>() {
+            public Face createFromParcel(Parcel in) {
+                return new Face(in);
+            }
+
+            public Face[] newArray(int size) {
+                return new Face[size];
+            }
+    };
+}
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
new file mode 100644
index 0000000..92f1990
--- /dev/null
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -0,0 +1,1149 @@
+/**
+ * Copyright (C) 2018 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.face;
+
+import static android.Manifest.permission.INTERACT_ACROSS_USERS;
+import static android.Manifest.permission.MANAGE_FACE;
+import static android.Manifest.permission.USE_BIOMETRIC;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemService;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.os.Binder;
+import android.os.CancellationSignal;
+import android.os.CancellationSignal.OnCancelListener;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IRemoteCallback;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.security.keystore.AndroidKeyStoreProvider;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.R;
+
+import java.security.Signature;
+
+import javax.crypto.Cipher;
+import javax.crypto.Mac;
+
+/**
+ * A class that coordinates access to the face authentication hardware.
+ * @hide
+ */
+@SystemService(Context.FACE_SERVICE)
+public class FaceManager {
+    /**
+     * The hardware is unavailable. Try again later.
+     */
+    public static final int FACE_ERROR_HW_UNAVAILABLE = 1;
+    /**
+     * Error state returned when the sensor was unable to process the current image.
+     */
+    public static final int FACE_ERROR_UNABLE_TO_PROCESS = 2;
+    /**
+     * Error state returned when the current request has been running too long. This is intended to
+     * prevent programs from waiting for the face authentication sensor indefinitely. The timeout is
+     * platform and sensor-specific, but is generally on the order of 30 seconds.
+     */
+    public static final int FACE_ERROR_TIMEOUT = 3;
+    /**
+     * Error state returned for operations like enrollment; the operation cannot be completed
+     * because there's not enough storage remaining to complete the operation.
+     */
+    public static final int FACE_ERROR_NO_SPACE = 4;
+    /**
+     * The operation was canceled because the face authentication sensor is unavailable. For
+     * example, this may happen when the user is switched, the device is locked or another pending
+     * operation prevents or disables it.
+     */
+    public static final int FACE_ERROR_CANCELED = 5;
+    /**
+     * The {@link FaceManager#remove} call failed. Typically this will happen when the
+     * provided face id was incorrect.
+     *
+     * @hide
+     */
+    public static final int FACE_ERROR_UNABLE_TO_REMOVE = 6;
+    /**
+     * The operation was canceled because the API is locked out due to too many attempts.
+     * This occurs after 5 failed attempts, and lasts for 30 seconds.
+     */
+    public static final int FACE_ERROR_LOCKOUT = 7;
+    /**
+     * Hardware vendors may extend this list if there are conditions that do not fall under one of
+     * the above categories. Vendors are responsible for providing error strings for these errors.
+     * These messages are typically reserved for internal operations such as enrollment, but may be
+     * used to express vendor errors not covered by the ones in HAL h file. Applications are
+     * expected to show the error message string if they happen, but are advised not to rely on the
+     * message id since they will be device and vendor-specific
+     */
+    public static final int FACE_ERROR_VENDOR = 8;
+    //
+    // Error messages from face authentication hardware during initialization, enrollment,
+    // authentication or removal. Must agree with the list in HAL h file
+    //
+    /**
+     * The operation was canceled because FACE_ERROR_LOCKOUT occurred too many times.
+     * Face authentication is disabled until the user unlocks with strong authentication
+     * (PIN/Pattern/Password)
+     */
+    public static final int FACE_ERROR_LOCKOUT_PERMANENT = 9;
+    /**
+     * The user canceled the operation. Upon receiving this, applications should use alternate
+     * authentication (e.g. a password). The application should also provide the means to return
+     * to face authentication, such as a "use face authentication" button.
+     */
+    public static final int FACE_ERROR_USER_CANCELED = 10;
+    /**
+     * The user does not have a face enrolled.
+     */
+    public static final int FACE_ERROR_NOT_ENROLLED = 11;
+    /**
+     * The device does not have a face sensor. This message will propagate if the calling app
+     * ignores the result from PackageManager.hasFeature(FEATURE_FACE) and calls
+     * this API anyway. Apps should always check for the feature before calling this API.
+     */
+    public static final int FACE_ERROR_HW_NOT_PRESENT = 12;
+    /**
+     * @hide
+     */
+    public static final int FACE_ERROR_VENDOR_BASE = 1000;
+    /**
+     * The image acquired was good.
+     */
+    public static final int FACE_ACQUIRED_GOOD = 0;
+    /**
+     * The face image was not good enough to process due to a detected condition.
+     * (See {@link #FACE_ACQUIRED_TOO_BRIGHT or @link #FACE_ACQUIRED_TOO_DARK}).
+     */
+    public static final int FACE_ACQUIRED_INSUFFICIENT = 1;
+    /**
+     * The face image was too bright due to too much ambient light.
+     * For example, it's reasonable to return this after multiple
+     * {@link #FACE_ACQUIRED_INSUFFICIENT}
+     * The user is expected to take action to retry in better lighting conditions
+     * when this is returned.
+     */
+    public static final int FACE_ACQUIRED_TOO_BRIGHT = 2;
+    /**
+     * The face image was too dark due to illumination light obscured.
+     * For example, it's reasonable to return this after multiple
+     * {@link #FACE_ACQUIRED_INSUFFICIENT}
+     * The user is expected to take action to retry in better lighting conditions
+     * when this is returned.
+     */
+    public static final int FACE_ACQUIRED_TOO_DARK = 3;
+    /**
+     * The detected face is too close to the sensor, and the image can't be processed.
+     * The user should be informed to move farther from the sensor when this is returned.
+     */
+    public static final int FACE_ACQUIRED_TOO_CLOSE = 4;
+    /**
+     * The detected face is too small, as the user might be too far from the sensor.
+     * The user should be informed to move closer to the sensor when this is returned.
+     */
+    public static final int FACE_ACQUIRED_TOO_FAR = 5;
+    /**
+     * Only the upper part of the face was detected. The sensor field of view is too high.
+     * The user should be informed to move up with respect to the sensor when this is returned.
+     */
+    public static final int FACE_ACQUIRED_TOO_HIGH = 6;
+    /**
+     * Only the lower part of the face was detected. The sensor field of view is too low.
+     * The user should be informed to move down with respect to the sensor when this is returned.
+     */
+    public static final int FACE_ACQUIRED_TOO_LOW = 7;
+
+    //
+    // Image acquisition messages. Must agree with those in HAL h file
+    //
+    /**
+     * Only the right part of the face was detected. The sensor field of view is too far right.
+     * The user should be informed to move to the right with respect to the sensor
+     * when this is returned.
+     */
+    public static final int FACE_ACQUIRED_TOO_RIGHT = 8;
+    /**
+     * Only the left part of the face was detected. The sensor field of view is too far left.
+     * The user should be informed to move to the left with respect to the sensor
+     * when this is returned.
+     */
+    public static final int FACE_ACQUIRED_TOO_LEFT = 9;
+    /**
+     * User's gaze strayed too far from the sensor causing significant parts of the user's face
+     * to be hidden.
+     * The user should be informed to turn the face front to the sensor.
+     */
+    public static final int FACE_ACQUIRED_POOR_GAZE = 10;
+    /**
+     * No face was detected in front of the sensor.
+     * The user should be informed to point the sensor to a face when this is returned.
+     */
+    public static final int FACE_ACQUIRED_NOT_DETECTED = 11;
+    /**
+     * Hardware vendors may extend this list if there are conditions that do not fall under one of
+     * the above categories. Vendors are responsible for providing error strings for these errors.
+     *
+     * @hide
+     */
+    public static final int FACE_ACQUIRED_VENDOR = 12;
+    /**
+     * @hide
+     */
+    public static final int FACE_ACQUIRED_VENDOR_BASE = 1000;
+    private static final String TAG = "FaceManager";
+    private static final boolean DEBUG = true;
+    private static final int MSG_ENROLL_RESULT = 100;
+    private static final int MSG_ACQUIRED = 101;
+    private static final int MSG_AUTHENTICATION_SUCCEEDED = 102;
+    private static final int MSG_AUTHENTICATION_FAILED = 103;
+    private static final int MSG_ERROR = 104;
+    private static final int MSG_REMOVED = 105;
+    private final Context mContext;
+    private IFaceService mService;
+    private IBinder mToken = new Binder();
+    private AuthenticationCallback mAuthenticationCallback;
+    private EnrollmentCallback mEnrollmentCallback;
+    private RemovalCallback mRemovalCallback;
+    private CryptoObject mCryptoObject;
+    private Face mRemovalFace;
+    private Handler mHandler;
+    private IFaceServiceReceiver mServiceReceiver = new IFaceServiceReceiver.Stub() {
+
+        @Override // binder call
+        public void onEnrollResult(long deviceId, int faceId, int remaining) {
+            mHandler.obtainMessage(MSG_ENROLL_RESULT, remaining, 0,
+                    new Face(null, faceId, deviceId)).sendToTarget();
+        }
+
+        @Override // binder call
+        public void onAcquired(long deviceId, int acquireInfo, int vendorCode) {
+            mHandler.obtainMessage(MSG_ACQUIRED, acquireInfo, vendorCode, deviceId).sendToTarget();
+        }
+
+        @Override // binder call
+        public void onAuthenticationSucceeded(long deviceId, Face face) {
+            mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, face).sendToTarget();
+        }
+
+        @Override // binder call
+        public void onAuthenticationFailed(long deviceId) {
+            mHandler.obtainMessage(MSG_AUTHENTICATION_FAILED).sendToTarget();
+        }
+
+        @Override // binder call
+        public void onError(long deviceId, int error, int vendorCode) {
+            mHandler.obtainMessage(MSG_ERROR, error, vendorCode, deviceId).sendToTarget();
+        }
+
+        @Override // binder call
+        public void onRemoved(long deviceId, int faceId, int remaining) {
+            mHandler.obtainMessage(MSG_REMOVED, remaining, 0,
+                    new Face(null, faceId, deviceId)).sendToTarget();
+        }
+    };
+
+    /**
+     * @hide
+     */
+    public FaceManager(Context context, IFaceService service) {
+        mContext = context;
+        mService = service;
+        if (mService == null) {
+            Slog.v(TAG, "FaceAuthenticationManagerService was null");
+        }
+        mHandler = new MyHandler(context);
+    }
+
+    /**
+     * Request authentication of a crypto object. 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.
+     *
+     * @param crypto   object associated with the call or null if none required.
+     * @param cancel   an object that can be used to cancel authentication
+     * @param flags    optional flags; should be 0
+     * @param callback an object to receive authentication events
+     * @param handler  an optional handler to handle callback 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>.
+     * @throws IllegalStateException    if the crypto primitive is not initialized.
+     */
+    @RequiresPermission(USE_BIOMETRIC)
+    public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel,
+            int flags, @NonNull AuthenticationCallback callback, @Nullable Handler handler) {
+        authenticate(crypto, cancel, flags, callback, handler, UserHandle.myUserId());
+    }
+
+    /**
+     * Use the provided handler thread for events.
+     */
+    private void useHandler(Handler handler) {
+        if (handler != null) {
+            mHandler = new MyHandler(handler.getLooper());
+        } else if (mHandler.getLooper() != mContext.getMainLooper()) {
+            mHandler = new MyHandler(mContext.getMainLooper());
+        }
+    }
+
+    /**
+     * Per-user version
+     *
+     * @hide
+     */
+    @RequiresPermission(USE_BIOMETRIC)
+    public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel,
+            int flags, @NonNull AuthenticationCallback callback, Handler handler, int userId) {
+        if (callback == null) {
+            throw new IllegalArgumentException("Must supply an authentication callback");
+        }
+
+        if (cancel != null) {
+            if (cancel.isCanceled()) {
+                Log.w(TAG, "authentication already canceled");
+                return;
+            } else {
+                cancel.setOnCancelListener(new OnAuthenticationCancelListener(crypto));
+            }
+        }
+
+        if (mService != null) {
+            try {
+                useHandler(handler);
+                mAuthenticationCallback = callback;
+                mCryptoObject = crypto;
+                long sessionId = crypto != null ? crypto.getOpId() : 0;
+                mService.authenticate(mToken, sessionId, mServiceReceiver, flags,
+                        mContext.getOpPackageName());
+            } catch (RemoteException e) {
+                Log.w(TAG, "Remote exception while authenticating: ", e);
+                if (callback != null) {
+                    // Though this may not be a hardware issue, it will cause apps to give up or try
+                    // again later.
+                    callback.onAuthenticationError(FACE_ERROR_HW_UNAVAILABLE,
+                            getErrorString(FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */));
+                }
+            }
+        }
+    }
+
+    /**
+     * 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
+     * {@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 flags    optional flags
+     * @param userId   the user to whom this face will belong to
+     * @param callback an object to receive enrollment events
+     * @hide
+     */
+    @RequiresPermission(MANAGE_FACE)
+    public void enroll(byte[] token, CancellationSignal cancel, int flags,
+            int userId, EnrollmentCallback callback) {
+        if (userId == UserHandle.USER_CURRENT) {
+            userId = getCurrentUserId();
+        }
+        if (callback == null) {
+            throw new IllegalArgumentException("Must supply an enrollment callback");
+        }
+
+        if (cancel != null) {
+            if (cancel.isCanceled()) {
+                Log.w(TAG, "enrollment already canceled");
+                return;
+            } else {
+                cancel.setOnCancelListener(new OnEnrollCancelListener());
+            }
+        }
+
+        if (mService != null) {
+            try {
+                mEnrollmentCallback = callback;
+                mService.enroll(mToken, token, userId, mServiceReceiver, flags,
+                        mContext.getOpPackageName());
+            } 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(FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */));
+                }
+            }
+        }
+    }
+
+    /**
+     * Requests a pre-enrollment auth token to tie enrollment to the confirmation of
+     * existing device credentials (e.g. pin/pattern/password).
+     *
+     * @hide
+     */
+    @RequiresPermission(MANAGE_FACE)
+    public long preEnroll() {
+        long result = 0;
+        if (mService != null) {
+            try {
+                result = mService.preEnroll(mToken);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Finishes enrollment and cancels the current auth token.
+     *
+     * @hide
+     */
+    @RequiresPermission(MANAGE_FACE)
+    public int postEnroll() {
+        int result = 0;
+        if (mService != null) {
+            try {
+                result = mService.postEnroll(mToken);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Sets the active user. This is meant to be used to select the current profile for enrollment
+     * to allow separate enrolled faces for a work profile
+     *
+     * @hide
+     */
+    @RequiresPermission(MANAGE_FACE)
+    public void setActiveUser(int userId) {
+        if (mService != null) {
+            try {
+                mService.setActiveUser(userId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Remove given face template from face hardware and/or protected storage.
+     *
+     * @param face     the face item to remove
+     * @param userId   the user who this face belongs to
+     * @param callback an optional callback to verify that face templates have been
+     *                 successfully removed. May be null if no callback is required.
+     * @hide
+     */
+    @RequiresPermission(MANAGE_FACE)
+    public void remove(Face face, int userId, RemovalCallback callback) {
+        if (mService != null) {
+            try {
+                mRemovalCallback = callback;
+                mRemovalFace = face;
+                mService.remove(mToken, userId, mServiceReceiver);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Remote exception in remove: ", e);
+                if (callback != null) {
+                    callback.onRemovalError(face, FACE_ERROR_HW_UNAVAILABLE,
+                            getErrorString(FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */));
+                }
+            }
+        }
+    }
+
+    /**
+     * Obtain the enrolled face template.
+     *
+     * @return the current face item
+     * @hide
+     */
+    @RequiresPermission(USE_BIOMETRIC)
+    public Face getEnrolledFace(int userId) {
+        if (mService != null) {
+            try {
+                return mService.getEnrolledFace(userId, mContext.getOpPackageName());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Obtain the enrolled face template.
+     *
+     * @return the current face item
+     * @hide
+     */
+    @RequiresPermission(USE_BIOMETRIC)
+    public Face getEnrolledFace() {
+        return getEnrolledFace(UserHandle.myUserId());
+    }
+
+    /**
+     * Determine if there is a face enrolled.
+     *
+     * @return true if a face is enrolled, false otherwise
+     */
+    @RequiresPermission(USE_BIOMETRIC)
+    public boolean hasEnrolledFace() {
+        if (mService != null) {
+            try {
+                return mService.hasEnrolledFace(
+                        UserHandle.myUserId(), mContext.getOpPackageName());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @hide
+     */
+    @RequiresPermission(allOf = {
+            USE_BIOMETRIC,
+            INTERACT_ACROSS_USERS})
+    public boolean hasEnrolledFace(int userId) {
+        if (mService != null) {
+            try {
+                return mService.hasEnrolledFace(userId, mContext.getOpPackageName());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Determine if face authentication sensor hardware is present and functional.
+     *
+     * @return true if hardware is present and functional, false otherwise.
+     */
+    @RequiresPermission(USE_BIOMETRIC)
+    public boolean isHardwareDetected() {
+        if (mService != null) {
+            try {
+                long deviceId = 0; /* TODO: plumb hardware id to FPMS */
+                return mService.isHardwareDetected(deviceId, mContext.getOpPackageName());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        } else {
+            Log.w(TAG, "isFaceHardwareDetected(): Service not connected!");
+        }
+        return false;
+    }
+
+    /**
+     * Retrieves the authenticator token for binding keys to the lifecycle
+     * of the calling user's face. Used only by internal clients.
+     *
+     * @hide
+     */
+    public long getAuthenticatorId() {
+        if (mService != null) {
+            try {
+                return mService.getAuthenticatorId(mContext.getOpPackageName());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        } else {
+            Log.w(TAG, "getAuthenticatorId(): Service not connected!");
+        }
+        return 0;
+    }
+
+    /**
+     * Reset the lockout timer when asked to do so by keyguard.
+     *
+     * @param token an opaque token returned by password confirmation.
+     * @hide
+     */
+    public void resetTimeout(byte[] token) {
+        if (mService != null) {
+            try {
+                mService.resetTimeout(token);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        } else {
+            Log.w(TAG, "resetTimeout(): Service not connected!");
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public void addLockoutResetCallback(final LockoutResetCallback callback) {
+        if (mService != null) {
+            try {
+                final PowerManager powerManager = mContext.getSystemService(PowerManager.class);
+                mService.addLockoutResetCallback(
+                        new IFaceServiceLockoutResetCallback.Stub() {
+
+                            @Override
+                            public void onLockoutReset(long deviceId,
+                                    IRemoteCallback serverCallback)
+                                    throws RemoteException {
+                                try {
+                                    final PowerManager.WakeLock wakeLock = powerManager.newWakeLock(
+                                            PowerManager.PARTIAL_WAKE_LOCK,
+                                            "faceLockoutResetCallback");
+                                    wakeLock.acquire();
+                                    mHandler.post(() -> {
+                                        try {
+                                            callback.onLockoutReset();
+                                        } finally {
+                                            wakeLock.release();
+                                        }
+                                    });
+                                } finally {
+                                    serverCallback.sendResult(null /* data */);
+                                }
+                            }
+                        });
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        } else {
+            Log.w(TAG, "addLockoutResetCallback(): Service not connected!");
+        }
+    }
+
+    private int getCurrentUserId() {
+        try {
+            return ActivityManager.getService().getCurrentUser().id;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    private void cancelEnrollment() {
+        if (mService != null) {
+            try {
+                mService.cancelEnrollment(mToken);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    private void cancelAuthentication(CryptoObject cryptoObject) {
+        if (mService != null) {
+            try {
+                mService.cancelAuthentication(mToken, mContext.getOpPackageName());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    private String getErrorString(int errMsg, int vendorCode) {
+        switch (errMsg) {
+            case FACE_ERROR_HW_UNAVAILABLE:
+                return mContext.getString(
+                        com.android.internal.R.string.face_error_hw_not_available);
+            case FACE_ERROR_UNABLE_TO_PROCESS:
+                return mContext.getString(
+                        com.android.internal.R.string.face_error_unable_to_process);
+            case FACE_ERROR_TIMEOUT:
+                return mContext.getString(com.android.internal.R.string.face_error_timeout);
+            case FACE_ERROR_NO_SPACE:
+                return mContext.getString(com.android.internal.R.string.face_error_no_space);
+            case FACE_ERROR_CANCELED:
+                return mContext.getString(com.android.internal.R.string.face_error_canceled);
+            case FACE_ERROR_LOCKOUT:
+                return mContext.getString(com.android.internal.R.string.face_error_lockout);
+            case FACE_ERROR_LOCKOUT_PERMANENT:
+                return mContext.getString(
+                        com.android.internal.R.string.face_error_lockout_permanent);
+            case FACE_ERROR_NOT_ENROLLED:
+                return mContext.getString(com.android.internal.R.string.face_error_not_enrolled);
+            case FACE_ERROR_HW_NOT_PRESENT:
+                return mContext.getString(com.android.internal.R.string.face_error_hw_not_present);
+            case FACE_ERROR_VENDOR: {
+                String[] msgArray = mContext.getResources().getStringArray(
+                        com.android.internal.R.array.face_error_vendor);
+                if (vendorCode < msgArray.length) {
+                    return msgArray[vendorCode];
+                }
+            }
+        }
+        Slog.w(TAG, "Invalid error message: " + errMsg + ", " + vendorCode);
+        return null;
+    }
+
+    private String getAcquiredString(int acquireInfo, int vendorCode) {
+        switch (acquireInfo) {
+            case FACE_ACQUIRED_GOOD:
+                return null;
+            case FACE_ACQUIRED_INSUFFICIENT:
+                return mContext.getString(R.string.face_acquired_insufficient);
+            case FACE_ACQUIRED_TOO_BRIGHT:
+                return mContext.getString(R.string.face_acquired_too_bright);
+            case FACE_ACQUIRED_TOO_DARK:
+                return mContext.getString(R.string.face_acquired_too_dark);
+            case FACE_ACQUIRED_TOO_CLOSE:
+                return mContext.getString(R.string.face_acquired_too_close);
+            case FACE_ACQUIRED_TOO_FAR:
+                return mContext.getString(R.string.face_acquired_too_far);
+            case FACE_ACQUIRED_TOO_HIGH:
+                return mContext.getString(R.string.face_acquired_too_high);
+            case FACE_ACQUIRED_TOO_LOW:
+                return mContext.getString(R.string.face_acquired_too_low);
+            case FACE_ACQUIRED_TOO_RIGHT:
+                return mContext.getString(R.string.face_acquired_too_right);
+            case FACE_ACQUIRED_TOO_LEFT:
+                return mContext.getString(R.string.face_acquired_too_left);
+            case FACE_ACQUIRED_POOR_GAZE:
+                return mContext.getString(R.string.face_acquired_poor_gaze);
+            case FACE_ACQUIRED_NOT_DETECTED:
+                return mContext.getString(R.string.face_acquired_not_detected);
+            case FACE_ACQUIRED_VENDOR: {
+                String[] msgArray = mContext.getResources().getStringArray(
+                        R.array.face_acquired_vendor);
+                if (vendorCode < msgArray.length) {
+                    return msgArray[vendorCode];
+                }
+            }
+        }
+        Slog.w(TAG, "Invalid acquired message: " + acquireInfo + ", " + vendorCode);
+        return null;
+    }
+
+    /**
+     * A wrapper class for the crypto objects supported by FaceAuthenticationManager.
+     */
+    public static final class CryptoObject {
+
+        private final Object mCrypto;
+
+        public CryptoObject(@NonNull Signature signature) {
+            mCrypto = signature;
+        }
+
+        public CryptoObject(@NonNull Cipher cipher) {
+            mCrypto = cipher;
+        }
+
+        public CryptoObject(@NonNull Mac mac) {
+            mCrypto = mac;
+        }
+
+        /**
+         * Get {@link Signature} object.
+         *
+         * @return {@link Signature} object or null if this doesn't contain one.
+         */
+        public Signature getSignature() {
+            return mCrypto instanceof Signature ? (Signature) mCrypto : null;
+        }
+
+        /**
+         * Get {@link Cipher} object.
+         *
+         * @return {@link Cipher} object or null if this doesn't contain one.
+         */
+        public Cipher getCipher() {
+            return mCrypto instanceof Cipher ? (Cipher) mCrypto : null;
+        }
+
+        /**
+         * Get {@link Mac} object.
+         *
+         * @return {@link Mac} object or null if this doesn't contain one.
+         */
+        public Mac getMac() {
+            return mCrypto instanceof Mac ? (Mac) mCrypto : null;
+        }
+
+        /**
+         * @return the opId associated with this object or 0 if none
+         * @hide
+         */
+        public long getOpId() {
+            return mCrypto != null
+                    ? AndroidKeyStoreProvider.getKeyStoreOperationHandle(mCrypto) : 0;
+        }
+    }
+
+    /**
+     * Container for callback data from {@link FaceManager#authenticate(CryptoObject,
+     * CancellationSignal, int, AuthenticationCallback, Handler)}.
+     */
+    public static class AuthenticationResult {
+        private Face mFace;
+        private CryptoObject mCryptoObject;
+        private int mUserId;
+
+        /**
+         * Authentication result
+         *
+         * @param crypto the crypto object
+         * @param face   the recognized face data, if allowed.
+         * @hide
+         */
+        public AuthenticationResult(CryptoObject crypto, Face face, int userId) {
+            mCryptoObject = crypto;
+            mFace = face;
+            mUserId = userId;
+        }
+
+        /**
+         * Obtain the crypto object associated with this transaction
+         *
+         * @return crypto object provided to {@link FaceManager#authenticate
+         * (CryptoObject,
+         * CancellationSignal, int, AuthenticationCallback, Handler)}.
+         */
+        public CryptoObject getCryptoObject() {
+            return mCryptoObject;
+        }
+
+        /**
+         * Obtain the Face associated with this operation. Applications are strongly
+         * discouraged from associating specific faces with specific applications or operations.
+         *
+         * @hide
+         */
+        public Face getFace() {
+            return mFace;
+        }
+
+        /**
+         * Obtain the userId for which this face was authenticated.
+         *
+         * @hide
+         */
+        public int getUserId() {
+            return mUserId;
+        }
+    }
+
+    /**
+     * Callback structure provided to {@link FaceManager#authenticate(CryptoObject,
+     * CancellationSignal, int, AuthenticationCallback, Handler)}. Users of {@link
+     * FaceManager#authenticate(CryptoObject, CancellationSignal,
+     * int, AuthenticationCallback, Handler) } must provide an implementation of this for listening
+     * to face events.
+     */
+    public abstract static class AuthenticationCallback {
+
+        /**
+         * Called when an unrecoverable error has been encountered and the operation is complete.
+         * No further callbacks will be made on this object.
+         *
+         * @param errorCode An integer identifying the error message
+         * @param errString A human-readable error string that can be shown in UI
+         */
+        public void onAuthenticationError(int errorCode, CharSequence errString) {
+        }
+
+        /**
+         * Called when a recoverable error has been encountered during authentication. The help
+         * string is provided to give the user guidance for what went wrong, such as
+         * "Sensor dirty, please clean it."
+         *
+         * @param helpCode   An integer identifying the error message
+         * @param helpString A human-readable string that can be shown in UI
+         */
+        public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
+        }
+
+        /**
+         * Called when a face is recognized.
+         *
+         * @param result An object containing authentication-related data
+         */
+        public void onAuthenticationSucceeded(AuthenticationResult result) {
+        }
+
+        /**
+         * Called when a face is detected but not recognized.
+         */
+        public void onAuthenticationFailed() {
+        }
+
+        /**
+         * Called when a face image has been acquired, but wasn't processed yet.
+         *
+         * @param acquireInfo one of FACE_ACQUIRED_* constants
+         * @hide
+         */
+        public void onAuthenticationAcquired(int acquireInfo) {
+        }
+    }
+
+    /**
+     * Callback structure provided to {@link FaceManager#enroll(long,
+     * EnrollmentCallback, CancellationSignal, int). Users of {@link #FaceAuthenticationManager()}
+     * must provide an implementation of this to {@link FaceManager#enroll(long,
+     * CancellationSignal, int, EnrollmentCallback) for listening to face enrollment events.
+     *
+     * @hide
+     */
+    public abstract static class EnrollmentCallback {
+
+        /**
+         * Called when an unrecoverable error has been encountered and the operation is complete.
+         * No further callbacks will be made on this object.
+         *
+         * @param errMsgId  An integer identifying the error message
+         * @param errString A human-readable error string that can be shown in UI
+         */
+        public void onEnrollmentError(int errMsgId, CharSequence errString) {
+        }
+
+        /**
+         * Called when a recoverable error has been encountered during enrollment. The help
+         * string is provided to give the user guidance for what went wrong, such as
+         * "Image too dark, uncover light source" or what they need to do next, such as
+         * "Rotate face up / down."
+         *
+         * @param helpMsgId  An integer identifying the error message
+         * @param helpString A human-readable string that can be shown in UI
+         */
+        public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) {
+        }
+
+        /**
+         * Called as each enrollment step progresses. Enrollment is considered complete when
+         * remaining reaches 0. This function will not be called if enrollment fails. See
+         * {@link EnrollmentCallback#onEnrollmentError(int, CharSequence)}
+         *
+         * @param remaining The number of remaining steps
+         * @param vendorMsg Vendor feedback about the current enroll attempt. Use it to customize
+         *                  the GUI according to vendor's requirements.
+         */
+        public void onEnrollmentProgress(int remaining, long vendorMsg) {
+        }
+    }
+
+    /**
+     * Callback structure provided to {@link #remove}. Users of {@link FaceManager}
+     * may
+     * optionally provide an implementation of this to
+     * {@link #remove(Face, int, RemovalCallback)} for listening to face template
+     * removal events.
+     *
+     * @hide
+     */
+    public abstract static class RemovalCallback {
+
+        /**
+         * Called when the given face can't be removed.
+         *
+         * @param face      The face that the call attempted to remove
+         * @param errMsgId  An associated error message id
+         * @param errString An error message indicating why the face id can't be removed
+         */
+        public void onRemovalError(Face face, int errMsgId, CharSequence errString) {
+        }
+
+        /**
+         * Called when a given face is successfully removed.
+         *
+         * @param face The face template that was removed.
+         */
+        public void onRemovalSucceeded(Face face) {
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public abstract static class LockoutResetCallback {
+
+        /**
+         * Called when lockout period expired and clients are allowed to listen for face
+         * authentication
+         * again.
+         */
+        public void onLockoutReset() {
+        }
+    }
+
+    private class OnEnrollCancelListener implements OnCancelListener {
+        @Override
+        public void onCancel() {
+            cancelEnrollment();
+        }
+    }
+
+    private class OnAuthenticationCancelListener implements OnCancelListener {
+        private CryptoObject mCrypto;
+
+        OnAuthenticationCancelListener(CryptoObject crypto) {
+            mCrypto = crypto;
+        }
+
+        @Override
+        public void onCancel() {
+            cancelAuthentication(mCrypto);
+        }
+    }
+
+    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) {
+            switch (msg.what) {
+                case MSG_ENROLL_RESULT:
+                    sendEnrollResult((EnrollResultMsg) msg.obj);
+                    break;
+                case MSG_ACQUIRED:
+                    sendAcquiredResult((Long) msg.obj /* deviceId */, msg.arg1 /* acquire info */,
+                            msg.arg2 /* vendorCode */);
+                    break;
+                case MSG_AUTHENTICATION_SUCCEEDED:
+                    sendAuthenticatedSucceeded((Face) msg.obj, msg.arg1 /* userId */);
+                    break;
+                case MSG_AUTHENTICATION_FAILED:
+                    sendAuthenticatedFailed();
+                    break;
+                case MSG_ERROR:
+                    sendErrorResult((Long) msg.obj /* deviceId */, msg.arg1 /* errMsgId */,
+                            msg.arg2 /* vendorCode */);
+                    break;
+                case MSG_REMOVED:
+                    sendRemovedResult((Face) msg.obj);
+                    break;
+            }
+        }
+
+        private void sendRemovedResult(Face face) {
+            if (mRemovalCallback == null) {
+                return;
+            }
+            if (face == null) {
+                Log.e(TAG, "Received MSG_REMOVED, but face is null");
+                return;
+            }
+
+
+            mRemovalCallback.onRemovalSucceeded(face);
+        }
+
+        private void sendErrorResult(long deviceId, int errMsgId, int vendorCode) {
+            // emulate HAL 2.1 behavior and send real errMsgId
+            final int clientErrMsgId = errMsgId == FACE_ERROR_VENDOR
+                    ? (vendorCode + FACE_ERROR_VENDOR_BASE) : errMsgId;
+            if (mEnrollmentCallback != null) {
+                mEnrollmentCallback.onEnrollmentError(clientErrMsgId,
+                        getErrorString(errMsgId, vendorCode));
+            } else if (mAuthenticationCallback != null) {
+                mAuthenticationCallback.onAuthenticationError(clientErrMsgId,
+                        getErrorString(errMsgId, vendorCode));
+            } else if (mRemovalCallback != null) {
+                mRemovalCallback.onRemovalError(mRemovalFace, clientErrMsgId,
+                        getErrorString(errMsgId, vendorCode));
+            }
+        }
+
+        private void sendEnrollResult(EnrollResultMsg faceWrapper) {
+            if (mEnrollmentCallback != null) {
+                int remaining = faceWrapper.getRemaining();
+                long vendorMsg = faceWrapper.getVendorMsg();
+                mEnrollmentCallback.onEnrollmentProgress(remaining, vendorMsg);
+            }
+        }
+
+        private void sendAuthenticatedSucceeded(Face face, int userId) {
+            if (mAuthenticationCallback != null) {
+                final AuthenticationResult result =
+                        new AuthenticationResult(mCryptoObject, face, userId);
+                mAuthenticationCallback.onAuthenticationSucceeded(result);
+            }
+        }
+
+        private void sendAuthenticatedFailed() {
+            if (mAuthenticationCallback != null) {
+                mAuthenticationCallback.onAuthenticationFailed();
+            }
+        }
+
+        private void sendAcquiredResult(long deviceId, int acquireInfo, int vendorCode) {
+            if (mAuthenticationCallback != null) {
+                mAuthenticationCallback.onAuthenticationAcquired(acquireInfo);
+            }
+            final String msg = getAcquiredString(acquireInfo, vendorCode);
+            if (msg == null) {
+                return;
+            }
+            final int clientInfo = acquireInfo == FACE_ACQUIRED_VENDOR
+                    ? (vendorCode + FACE_ACQUIRED_VENDOR_BASE) : acquireInfo;
+            if (mEnrollmentCallback != null) {
+                mEnrollmentCallback.onEnrollmentHelp(clientInfo, msg);
+            } else if (mAuthenticationCallback != null) {
+                mAuthenticationCallback.onAuthenticationHelp(clientInfo, msg);
+            }
+        }
+    }
+
+    private class EnrollResultMsg {
+        private final Face mFace;
+        private final int mRemaining;
+        private final long mVendorMsg;
+
+        EnrollResultMsg(Face face, int remaining, long vendorMsg) {
+            mFace = face;
+            mRemaining = remaining;
+            mVendorMsg = vendorMsg;
+        }
+
+        Face getFace() {
+            return mFace;
+        }
+
+        long getVendorMsg() {
+            return mVendorMsg;
+        }
+
+        int getRemaining() {
+            return mRemaining;
+        }
+    }
+}
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
new file mode 100644
index 0000000..856a313
--- /dev/null
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2018 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.face;
+
+import android.os.Bundle;
+import android.hardware.face.IFaceServiceReceiver;
+import android.hardware.face.IFaceServiceLockoutResetCallback;
+import android.hardware.face.Face;
+
+/**
+ * Communication channel from client to the face service.
+ * @hide
+ */
+interface IFaceService {
+    // Authenticate the given sessionId with a face
+    void authenticate(IBinder token, long sessionId,
+            IFaceServiceReceiver receiver, int flags, String opPackageName);
+
+    // Cancel authentication for the given sessionId
+    void cancelAuthentication(IBinder token, String opPackageName);
+
+    // Start face enrollment
+    void enroll(IBinder token, in byte [] cryptoToken, int userId, IFaceServiceReceiver receiver,
+                int flags, String opPackageName);
+
+    // Cancel enrollment in progress
+    void cancelEnrollment(IBinder token);
+
+    // Any errors resulting from this call will be returned to the listener
+    void remove(IBinder token, int userId, IFaceServiceReceiver receiver);
+
+    // Get the enrolled face for user.
+    Face getEnrolledFace(int userId, String opPackageName);
+
+    // Determine if HAL is loaded and ready
+    boolean isHardwareDetected(long deviceId, String opPackageName);
+
+    // Get a pre-enrollment authentication token
+    long preEnroll(IBinder token);
+
+    // Finish an enrollment sequence and invalidate the authentication token
+    int postEnroll(IBinder token);
+
+    // Determine if a user has enrolled a face
+    boolean hasEnrolledFace(int userId, String opPackageName);
+
+    // Gets the number of hardware devices
+    // int getHardwareDeviceCount();
+
+    // Gets the unique device id for hardware enumerated at i
+    // long getHardwareDevice(int i);
+
+    // Gets the authenticator ID for face
+    long getAuthenticatorId(String opPackageName);
+
+    // Reset the timeout when user authenticates with strong auth (e.g. PIN, pattern or password)
+    void resetTimeout(in byte [] cryptoToken);
+
+    // Add a callback which gets notified when the face lockout period expired.
+    void addLockoutResetCallback(IFaceServiceLockoutResetCallback callback);
+
+    // Explicitly set the active user (for enrolling work profile)
+    void setActiveUser(int uid);
+}
diff --git a/core/java/android/hardware/face/IFaceServiceLockoutResetCallback.aidl b/core/java/android/hardware/face/IFaceServiceLockoutResetCallback.aidl
new file mode 100644
index 0000000..b62fde3
--- /dev/null
+++ b/core/java/android/hardware/face/IFaceServiceLockoutResetCallback.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2018 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.face;
+
+import android.hardware.face.Face;
+import android.os.Bundle;
+import android.os.IRemoteCallback;
+import android.os.UserHandle;
+
+/**
+ * Callback when lockout period expired and clients are allowed to authenticate again.
+ * @hide
+ */
+oneway interface IFaceServiceLockoutResetCallback {
+
+    /**
+     * A wakelock will be held until the reciever calls back into {@param callback}
+     */
+    void onLockoutReset(long deviceId, IRemoteCallback callback);
+}
diff --git a/core/java/android/hardware/face/IFaceServiceReceiver.aidl b/core/java/android/hardware/face/IFaceServiceReceiver.aidl
new file mode 100644
index 0000000..16fb690
--- /dev/null
+++ b/core/java/android/hardware/face/IFaceServiceReceiver.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2018 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.face;
+
+import android.hardware.face.Face;
+import android.os.Bundle;
+import android.os.UserHandle;
+
+/**
+ * Communication channel from the FaceService back to FaceAuthenticationManager.
+ * @hide
+ */
+oneway interface IFaceServiceReceiver {
+    void onEnrollResult(long deviceId, int faceId, int remaining);
+    void onAcquired(long deviceId, int acquiredInfo, int vendorCode);
+    void onAuthenticationSucceeded(long deviceId, in Face face);
+    void onAuthenticationFailed(long deviceId);
+    void onError(long deviceId, int error, int vendorCode);
+    void onRemoved(long deviceId, int faceId, int remaining);
+}
diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index 6c3a58c..989c58b 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -79,6 +79,11 @@
      */
     public static final int ACTION_ROTATE_SCREEN = 6;
 
+    /*
+     * Time between we get a face acquired signal until we start with the unlock animation
+     */
+    public static final int ACTION_FACE_WAKE_AND_UNLOCK = 6;
+
     private static final String[] NAMES = new String[] {
             "expand panel",
             "toggle recents",
@@ -86,7 +91,8 @@
             "check credential",
             "check credential unlocked",
             "turn on screen",
-            "rotate the screen"};
+            "rotate the screen",
+            "face wake-and-unlock" };
 
     private static LatencyTracker sLatencyTracker;
 
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index d294933..80d8063 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -1528,7 +1528,7 @@
      * @see StrongAuthTracker#isFingerprintAllowedForUser
      */
     public boolean isFingerprintAllowedForUser(int userId) {
-        return (getStrongAuthForUser(userId) & ~StrongAuthTracker.ALLOWING_FINGERPRINT) == 0;
+        return (getStrongAuthForUser(userId) & ~StrongAuthTracker.ALLOWING_BIOMETRIC) == 0;
     }
 
     public boolean isUserInLockdown(int userId) {
@@ -1733,11 +1733,10 @@
         public static final int STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN = 0x20;
 
         /**
-         * Strong auth flags that do not prevent fingerprint from being accepted as auth.
-         *
-         * If any other flags are set, fingerprint is disabled.
+         * Strong auth flags that do not prevent biometric methods from being accepted as auth.
+         * If any other flags are set, biometric authentication is disabled.
          */
-        private static final int ALLOWING_FINGERPRINT = STRONG_AUTH_NOT_REQUIRED
+        private static final int ALLOWING_BIOMETRIC = STRONG_AUTH_NOT_REQUIRED
                 | SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
 
         private final SparseIntArray mStrongAuthRequiredForUser = new SparseIntArray();
@@ -1784,11 +1783,11 @@
         }
 
         /**
-         * @return true if unlocking with fingerprint alone is allowed for {@param userId} by the
-         * current strong authentication requirements.
+         * @return true if unlocking with a biometric method alone is allowed for {@param userId}
+         * by the current strong authentication requirements.
          */
-        public boolean isFingerprintAllowedForUser(int userId) {
-            return (getStrongAuthForUser(userId) & ~ALLOWING_FINGERPRINT) == 0;
+        public boolean isBiometricAllowedForUser(int userId) {
+            return (getStrongAuthForUser(userId) & ~ALLOWING_BIOMETRIC) == 0;
         }
 
         /**
diff --git a/core/proto/android/service/face.proto b/core/proto/android/service/face.proto
new file mode 100644
index 0000000..4188a34
--- /dev/null
+++ b/core/proto/android/service/face.proto
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+syntax = "proto3";
+
+package android.service.face;
+
+option java_multiple_files = true;
+option java_outer_classname = "FaceServiceProto";
+
+message FaceServiceDumpProto {
+    // Each log may include multiple user_id for different users.
+    repeated FaceUserStatsProto users = 1;
+}
+
+message FaceUserStatsProto {
+    // Refer to the UserHandle documentation.
+    int32 user_id = 1;
+
+    // Normal face authentications stats (e.g. lockscreen).
+    FaceActionStatsProto normal = 2;
+
+    // Crypto authentications (e.g. to unlock password storage, make secure
+    // purchases, etc).
+    FaceActionStatsProto crypto = 3;
+}
+
+message FaceActionStatsProto {
+    // Number of accepted faces.
+    int32 accept = 1;
+
+    // Number of rejected faces.
+    int32 reject = 2;
+
+    // Total number of acquisitions. Should be >= accept+reject due to poor
+    // image acquisition in some cases (too high, too low, poor gaze, etc.)
+    int32 acquire = 3;
+
+    // Total number of lockouts.
+    int32 lockout = 4;
+
+    // Total number of permanent lockouts.
+    int32 lockout_permanent = 5;
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c601215..f4b7ec8 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -600,6 +600,7 @@
     <protected-broadcast android:name="android.app.action.DATA_SHARING_RESTRICTION_CHANGED" />
     <protected-broadcast android:name="android.app.action.STATSD_STARTED" />
     <protected-broadcast android:name="com.android.server.biometrics.fingerprint.ACTION_LOCKOUT_RESET" />
+    <protected-broadcast android:name="com.android.server.biometrics.face.ACTION_LOCKOUT_RESET" />
 
     <!-- For IdleController -->
     <protected-broadcast android:name="android.intent.action.DOCK_IDLE" />
@@ -3637,6 +3638,14 @@
     <permission android:name="android.permission.RESET_FINGERPRINT_LOCKOUT"
         android:protectionLevel="signature" />
 
+    <!-- Allows managing (adding, removing) facial templates. Reserved for the system. @hide -->
+    <permission android:name="android.permission.MANAGE_FACE"
+        android:protectionLevel="signature|privileged" />
+
+    <!-- Allows an app to reset face authentication attempt counter. Reserved for the system. @hide -->
+    <permission android:name="android.permission.RESET_FACE_LOCKOUT"
+        android:protectionLevel="signature" />
+
     <!-- Allows an application to control keyguard.  Only allowed for system processes.
         @hide -->
     <permission android:name="android.permission.CONTROL_KEYGUARD"
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index cf68934..4e96671 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1408,6 +1408,74 @@
     <!-- Content description which should be used for the fingerprint icon. -->
     <string name="fingerprint_icon_content_description">Fingerprint icon</string>
 
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=50] -->
+    <string name="permlab_manageFace">manage face authentication hardware</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=90] -->
+    <string name="permdesc_manageFace">Allows the app to invoke methods to add and delete facial templates for use.</string>
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=50] -->
+    <string name="permlab_useFaceAuthentication">use face authentication hardware</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=90] -->
+    <string name="permdesc_useFaceAuthentication">Allows the app to use face authentication hardware for authentication</string>
+
+    <!-- Message shown during face acquisition when the face cannot be recognized [CHAR LIMIT=50] -->
+    <string name="face_acquired_insufficient">Couldn\u2019t process face. Please try again.</string>
+    <!-- Message shown during face acquisition when the image is too bright [CHAR LIMIT=50] -->
+    <string name="face_acquired_too_bright">Face is too bright. Please try in lower light.</string>
+    <!-- Message shown during face acquisition when the image is too dark [CHAR LIMIT=50] -->
+    <string name="face_acquired_too_dark">Face is too dark. Please uncover light source.</string>
+    <!-- Message shown during face acquisition when the user is too close to sensor [CHAR LIMIT=50] -->
+    <string name="face_acquired_too_close">Please move sensor farther from face.</string>
+    <!-- Message shown during face acquisition when the user is too far from sensor [CHAR LIMIT=50] -->
+    <string name="face_acquired_too_far">Please bring sensor closer to face.</string>
+    <!-- Message shown during face acquisition when the user is too high relatively to sensor [CHAR LIMIT=50] -->
+    <string name="face_acquired_too_high">Please move sensor higher.</string>
+    <!-- Message shown during face acquisition when the user is too low relatively to sensor [CHAR LIMIT=50] -->
+    <string name="face_acquired_too_low">Please move sensor lower.</string>
+    <!-- Message shown during face acquisition when the user is too right relatively to sensor [CHAR LIMIT=50] -->
+    <string name="face_acquired_too_right">Please move sensor to the right.</string>
+    <!-- Message shown during face acquisition when the user is too left relatively to sensor [CHAR LIMIT=50] -->
+    <string name="face_acquired_too_left">Please move sensor to the left.</string>
+    <!-- Message shown during face acquisition when the user is not front facing the sensor [CHAR LIMIT=50] -->
+    <string name="face_acquired_poor_gaze">Please look at the sensor.</string>
+    <!-- Message shown during face acquisition when the user is not detected [CHAR LIMIT=50] -->
+    <string name="face_acquired_not_detected">No face detected.</string>
+
+
+    <!-- Message shown during face acquisition when the face is not kept steady infront of device [CHAR LIMIT=50] -->
+    <string name="face_acquired_not_steady">Keep face steady infront of device.</string>
+    <!-- Array containing custom messages shown during face acquisition from vendor.  Vendor is expected to add and translate these strings -->
+    <string-array name="face_acquired_vendor">
+    </string-array>
+
+    <!-- Error message shown when the face hardware can't be accessed. [CHAR LIMIT=50] -->
+    <string name="face_error_hw_not_available">Face hardware not available.</string>
+    <!-- Error message shown when the face hardware timer has expired and the user needs to restart the operation. [CHAR LIMIT=50] -->
+    <string name="face_error_timeout">Face time out reached. Try again.</string>
+    <!-- Error message shown when the face hardware has run out of room for storing faces. [CHAR LIMIT=50] -->
+    <string name="face_error_no_space">Face can\u2019t be stored.</string>
+    <!-- Generic error message shown when the face operation (e.g. enrollment or authentication) is canceled. Generally not shown to the user. [CHAR LIMIT=50] -->
+    <string name="face_error_canceled">Face operation canceled.</string>
+    <!-- Generic error message shown when the face operation fails because too many attempts have been made. [CHAR LIMIT=50] -->
+    <string name="face_error_lockout">Too many attempts. Try again later.</string>
+    <!-- Generic error message shown when the face operation fails because strong authentication is required. [CHAR LIMIT=50] -->
+    <string name="face_error_lockout_permanent">Too many attempts. Facial authentication disabled.</string>
+    <!-- Generic error message shown when the face hardware can't recognize the face. [CHAR LIMIT=50] -->
+    <string name="face_error_unable_to_process">Try again.</string>
+    <!-- Generic error message shown when the user has no enrolled face. [CHAR LIMIT=50] -->
+    <string name="face_error_not_enrolled">No face enrolled.</string>
+    <!-- Generic error message shown when the app requests face authentication on a device without a sensor. [CHAR LIMIT=60] -->
+    <string name="face_error_hw_not_present">This device does not have a face authentication sensor</string>
+
+    <!-- Template to be used to name enrolled faces by default. [CHAR LIMIT=10] -->
+    <string name="face_name_template">Face <xliff:g id="faceId" example="1">%d</xliff:g></string>
+
+    <!-- Array containing custom error messages from vendor.  Vendor is expected to add and translate these strings -->
+    <string-array name="face_error_vendor">
+    </string-array>
+
+    <!-- Content description which should be used for the face icon. [CHAR LIMIT=10] -->
+    <string name="face_icon_content_description">Face icon</string>
+
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_readSyncSettings">read sync settings</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
@@ -3180,6 +3248,21 @@
     <!-- A notification is shown when the user connects to a Wi-Fi network and the system detects that that network has no Internet access. This is the notification's message. -->
     <string name="wifi_no_internet_detailed">Tap for options</string>
 
+    <!-- A notification is shown when the user's softap config has been changed due to underlying
+         hardware restrictions. This is the notifications's title.
+         [CHAR_LIMIT=NONE] -->
+    <string name="wifi_softap_config_change">Changes to your hotspot settings</string>
+
+    <!-- A notification is shown when the user's softap config has been changed due to underlying
+         hardware restrictions. This is the notification's summary message.
+         [CHAR_LIMIT=NONE] -->
+    <string name="wifi_softap_config_change_summary">Your hotspot band has changed.</string>
+
+    <!-- A notification is shown when the user's softap config has been changed due to underlying
+         hardware restrictions. This is the notification's full message.
+         [CHAR_LIMIT=NONE] -->
+    <string name="wifi_softap_config_change_detailed">This device doesn\u2019t support your preference for 5GHz only. Instead, this device will use the 5GHz band when available.</string>
+
     <!-- A notification might be shown if the device switches to another network type (e.g., mobile data) because it detects that the network it was using (e.g., Wi-Fi) has lost Internet connectivity. This is the notification's title. %1$s is the network type that the device switched to, e.g., cellular data. It is one of the strings in the network_switch_type_name array. -->
     <string name="network_switch_metered">Switched to <xliff:g id="network_type">%1$s</xliff:g></string>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d9642cb..c746f47 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1061,6 +1061,9 @@
   <java-symbol type="string" name="network_switch_type_name_unknown" />
   <java-symbol type="string" name="wifi_no_internet" />
   <java-symbol type="string" name="wifi_no_internet_detailed" />
+  <java-symbol type="string" name="wifi_softap_config_change" />
+  <java-symbol type="string" name="wifi_softap_config_change_summary" />
+  <java-symbol type="string" name="wifi_softap_config_change_detailed" />
   <java-symbol type="string" name="wifi_connect_alert_title" />
   <java-symbol type="string" name="wifi_connect_alert_message" />
   <java-symbol type="string" name="wifi_connect_default_application" />
@@ -2420,6 +2423,30 @@
   <java-symbol type="integer" name="config_fingerprintMaxTemplatesPerUser"/>
   <java-symbol type="bool" name="config_fingerprintSupportsGestures"/>
 
+  <!-- Face authentication messages -->
+  <java-symbol type="string" name="face_error_unable_to_process" />
+  <java-symbol type="string" name="face_error_hw_not_available" />
+  <java-symbol type="string" name="face_error_no_space" />
+  <java-symbol type="string" name="face_error_timeout" />
+  <java-symbol type="array" name="face_error_vendor" />
+  <java-symbol type="string" name="face_error_canceled" />
+  <java-symbol type="string" name="face_error_lockout" />
+  <java-symbol type="string" name="face_error_lockout_permanent" />
+  <java-symbol type="string" name="face_error_not_enrolled" />
+  <java-symbol type="string" name="face_error_hw_not_present" />
+  <java-symbol type="string" name="face_acquired_insufficient" />
+  <java-symbol type="string" name="face_acquired_too_bright" />
+  <java-symbol type="string" name="face_acquired_too_dark" />
+  <java-symbol type="string" name="face_acquired_too_close" />
+  <java-symbol type="string" name="face_acquired_too_far" />
+  <java-symbol type="string" name="face_acquired_too_high" />
+  <java-symbol type="string" name="face_acquired_too_low" />
+  <java-symbol type="string" name="face_acquired_too_right" />
+  <java-symbol type="string" name="face_acquired_too_left" />
+  <java-symbol type="string" name="face_acquired_poor_gaze" />
+  <java-symbol type="string" name="face_acquired_not_detected" />
+  <java-symbol type="array" name="face_acquired_vendor" />
+
   <!-- From various Material changes -->
   <java-symbol type="attr" name="titleTextAppearance" />
   <java-symbol type="attr" name="subtitleTextAppearance" />
diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h
index 2a1b36f..9a72706 100644
--- a/libs/hwui/renderthread/VulkanManager.h
+++ b/libs/hwui/renderthread/VulkanManager.h
@@ -79,7 +79,7 @@
     void initialize();
 
     // Quick check to see if the VulkanManager has been initialized.
-    bool hasVkContext() { return mBackendContext.get() != nullptr; }
+    bool hasVkContext() { return mDevice != VK_NULL_HANDLE; }
 
     // Given a window this creates a new VkSurfaceKHR and VkSwapchain and stores them inside a new
     // VulkanSurface object which is returned.
@@ -188,8 +188,6 @@
 
     RenderThread& mRenderThread;
 
-    sk_sp<const GrVkBackendContext> mBackendContext;
-
     VkInstance mInstance = VK_NULL_HANDLE;
     VkPhysicalDevice mPhysicalDevice = VK_NULL_HANDLE;
     VkDevice mDevice = VK_NULL_HANDLE;
diff --git a/packages/ExtServices/res/values/strings.xml b/packages/ExtServices/res/values/strings.xml
index 72647ab..617e49a 100644
--- a/packages/ExtServices/res/values/strings.xml
+++ b/packages/ExtServices/res/values/strings.xml
@@ -18,7 +18,6 @@
     <string name="app_name">Android Services Library</string>
 
     <string name="notification_assistant">Notification Assistant</string>
-    <string name="prompt_block_reason">Too many dismissals:views</string>
 
     <string name="autofill_field_classification_default_algorithm">EDIT_DISTANCE</string>
     <string-array name="autofill_field_classification_available_algorithms">
diff --git a/packages/ExtServices/src/android/ext/services/notification/Assistant.java b/packages/ExtServices/src/android/ext/services/notification/Assistant.java
index f878822..fdd6a9c 100644
--- a/packages/ExtServices/src/android/ext/services/notification/Assistant.java
+++ b/packages/ExtServices/src/android/ext/services/notification/Assistant.java
@@ -292,8 +292,7 @@
         if (DEBUG) Log.d(TAG, "User probably doesn't want " + key);
         Bundle signals = new Bundle();
         signals.putInt(Adjustment.KEY_USER_SENTIMENT, USER_SENTIMENT_NEGATIVE);
-        return new Adjustment(packageName, key,  signals,
-                getContext().getString(R.string.prompt_block_reason), user);
+        return new Adjustment(packageName, key,  signals, "", user);
     }
 
     // for testing
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index f728684..9d398b5 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -841,7 +841,19 @@
             WifiConfiguration config = WifiConfiguration
                     .getWifiConfigFromBackup(new DataInputStream(new ByteArrayInputStream(data)));
             if (DEBUG) Log.d(TAG, "Successfully unMarshaled WifiConfiguration ");
+            int originalApBand = config.apBand;
             mWifiManager.setWifiApConfiguration(config);
+
+            // Depending on device hardware, we may need to notify the user of a setting change for
+            // the apBand preference
+            boolean dualMode = mWifiManager.isDualModeSupported();
+            int storedApBand = mWifiManager.getWifiApConfiguration().apBand;
+            if (dualMode) {
+                if (storedApBand != originalApBand) {
+                    Log.d(TAG, "restored ap configuration requires a conversion, notify the user");
+                    mWifiManager.notifyUserOfApBandConversion();
+                }
+            }
         } catch (IOException | BackupUtils.BadVersionException e) {
             Log.e(TAG, "Failed to unMarshal SoftAPConfiguration " + e.getMessage());
         }
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index beb4e9e..eab4b97 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -120,6 +120,7 @@
     <uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL" />
     <uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" />
     <uses-permission android:name="android.permission.TRUST_LISTENER" />
+    <uses-permission android:name="android.permission.USE_BIOMETRIC" />
     <uses-permission android:name="android.permission.USE_FINGERPRINT" />
     <uses-permission android:name="android.permission.RESET_FINGERPRINT_LOCKOUT" />
     <uses-permission android:name="android.permission.MANAGE_SLICE_PERMISSIONS" />
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index 513d848..6bc0965 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -381,7 +381,10 @@
     </plurals>
 
     <!-- Fingerprint hint message when finger was not recognized.-->
-    <string name="fingerprint_not_recognized">Not recognized</string>
+    <string name="kg_fingerprint_not_recognized">Not recognized</string>
+
+    <!-- Face hint message when finger was not recognized. [CHAR LIMIT=20] -->
+    <string name="kg_face_not_recognized">Not recognized</string>
 
     <!-- Instructions telling the user remaining times when enter SIM PIN view.  -->
     <plurals name="kg_password_default_pin_message">
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/NavigationBarCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/NavigationBarCompat.java
index cd831d1..d38cc0f 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/NavigationBarCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/NavigationBarCompat.java
@@ -54,6 +54,7 @@
     public static final int HIT_TARGET_HOME = 2;
     public static final int HIT_TARGET_OVERVIEW = 3;
     public static final int HIT_TARGET_ROTATION = 4;
+    public static final int HIT_TARGET_DEAD_ZONE = 5;
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({FLAG_DISABLE_SWIPE_UP,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardConstants.java b/packages/SystemUI/src/com/android/keyguard/KeyguardConstants.java
index 62b8e7c..0340904 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardConstants.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardConstants.java
@@ -27,5 +27,5 @@
      */
     public static final boolean DEBUG = false;
     public static final boolean DEBUG_SIM_STATES = true;
-    public static final boolean DEBUG_FP_WAKELOCK = true;
+    public static final boolean DEBUG_BIOMETRIC_WAKELOCK = true;
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 075faa1..d639fbf 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -39,6 +39,7 @@
 import android.app.UserSwitchObserver;
 import android.app.admin.DevicePolicyManager;
 import android.app.trust.TrustManager;
+import android.hardware.biometrics.BiometricSourceType;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -48,6 +49,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.database.ContentObserver;
+import android.hardware.face.FaceManager;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback;
 import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
@@ -72,7 +74,6 @@
 import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
 import android.telephony.TelephonyManager;
 import android.util.Log;
-import android.util.Slog;
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 
@@ -145,26 +146,26 @@
     private static final int MSG_DREAMING_STATE_CHANGED = 333;
     private static final int MSG_USER_UNLOCKED = 334;
     private static final int MSG_ASSISTANT_STACK_CHANGED = 335;
-    private static final int MSG_FINGERPRINT_AUTHENTICATION_CONTINUE = 336;
+    private static final int MSG_BIOMETRIC_AUTHENTICATION_CONTINUE = 336;
     private static final int MSG_DEVICE_POLICY_MANAGER_STATE_CHANGED = 337;
 
-    /** Fingerprint state: Not listening to fingerprint. */
-    private static final int FINGERPRINT_STATE_STOPPED = 0;
+    /** Biometric authentication state: Not listening. */
+    private static final int BIOMETRIC_STATE_STOPPED = 0;
 
-    /** Fingerprint state: Listening. */
-    private static final int FINGERPRINT_STATE_RUNNING = 1;
+    /** Biometric authentication state: Listening. */
+    private static final int BIOMETRIC_STATE_RUNNING = 1;
 
     /**
-     * Fingerprint state: Cancelling and waiting for the confirmation from FingerprintService to
+     * Biometric authentication: Cancelling and waiting for the relevant biometric service to
      * send us the confirmation that cancellation has happened.
      */
-    private static final int FINGERPRINT_STATE_CANCELLING = 2;
+    private static final int BIOMETRIC_STATE_CANCELLING = 2;
 
     /**
-     * Fingerprint state: During cancelling we got another request to start listening, so when we
+     * Biometric state: During cancelling we got another request to start listening, so when we
      * receive the cancellation done signal, we should start listening again.
      */
-    private static final int FINGERPRINT_STATE_CANCELLING_RESTARTING = 3;
+    private static final int BIOMETRIC_STATE_CANCELLING_RESTARTING = 3;
 
     private static final int DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT = 5000000;
 
@@ -227,7 +228,8 @@
     private List<SubscriptionInfo> mSubscriptionInfo;
     private TrustManager mTrustManager;
     private UserManager mUserManager;
-    private int mFingerprintRunningState = FINGERPRINT_STATE_STOPPED;
+    private int mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
+    private int mFaceRunningState = BIOMETRIC_STATE_STOPPED;
     private LockPatternUtils mLockPatternUtils;
     private final IDreamManager mDreamManager;
     private boolean mIsDreaming;
@@ -235,14 +237,15 @@
     private boolean mLogoutEnabled;
 
     /**
-     * Short delay before restarting fingerprint authentication after a successful try
-     * This should be slightly longer than the time between onFingerprintAuthenticated and
-     * setKeyguardGoingAway(true).
+     * Short delay before restarting biometric authentication after a successful try
+     * This should be slightly longer than the time between on<biometric>Authenticated
+     * (e.g. onFingerprintAuthenticated) and setKeyguardGoingAway(true).
      */
-    private static final int FINGERPRINT_CONTINUE_DELAY_MS = 500;
+    private static final int BIOMETRIC_CONTINUE_DELAY_MS = 500;
 
     // If FP daemon dies, keyguard should retry after a short delay
-    private int mHardwareUnavailableRetryCount = 0;
+    private int mHardwareFingerprintUnavailableRetryCount = 0;
+    private int mHardwareFaceUnavailableRetryCount = 0;
     private static final int HW_UNAVAILABLE_TIMEOUT = 3000; // ms
     private static final int HW_UNAVAILABLE_RETRY_MAX = 3;
 
@@ -333,10 +336,10 @@
                     break;
                 case MSG_ASSISTANT_STACK_CHANGED:
                     mAssistantVisible = (boolean)msg.obj;
-                    updateFingerprintListeningState();
+                    updateBiometricListeningState();
                     break;
-                case MSG_FINGERPRINT_AUTHENTICATION_CONTINUE:
-                    updateFingerprintListeningState();
+                case MSG_BIOMETRIC_AUTHENTICATION_CONTINUE:
+                    updateBiometricListeningState();
                     break;
                 case MSG_DEVICE_POLICY_MANAGER_STATE_CHANGED:
                     updateLogoutEnabled();
@@ -359,10 +362,11 @@
     private SparseBooleanArray mUserHasTrust = new SparseBooleanArray();
     private SparseBooleanArray mUserTrustIsManaged = new SparseBooleanArray();
     private SparseBooleanArray mUserFingerprintAuthenticated = new SparseBooleanArray();
+    private SparseBooleanArray mUserFaceAuthenticated = new SparseBooleanArray();
     private SparseBooleanArray mUserFaceUnlockRunning = new SparseBooleanArray();
 
     private static int sCurrentUser;
-    private Runnable mUpdateFingerprintListeningState = this::updateFingerprintListeningState;
+    private Runnable mUpdateBiometricListeningState = this::updateBiometricListeningState;
     private static boolean sDisableHandlerCheckForTesting;
 
     public synchronized static void setCurrentUser(int currentUser) {
@@ -487,7 +491,7 @@
      */
     public void setKeyguardOccluded(boolean occluded) {
         mKeyguardOccluded = occluded;
-        updateFingerprintListeningState();
+        updateBiometricListeningState();
     }
 
     /**
@@ -515,19 +519,19 @@
         mUserFingerprintAuthenticated.put(userId, true);
         // Update/refresh trust state only if user can skip bouncer
         if (getUserCanSkipBouncer(userId)) {
-            mTrustManager.unlockedByFingerprintForUser(userId);
+            mTrustManager.unlockedByBiometricForUser(userId, BiometricSourceType.FINGERPRINT);
         }
         // Don't send cancel if authentication succeeds
         mFingerprintCancelSignal = null;
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
-                cb.onFingerprintAuthenticated(userId);
+                cb.onBiometricAuthenticated(userId, BiometricSourceType.FINGERPRINT);
             }
         }
 
-        mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_FINGERPRINT_AUTHENTICATION_CONTINUE),
-                FINGERPRINT_CONTINUE_DELAY_MS);
+        mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATION_CONTINUE),
+                BIOMETRIC_CONTINUE_DELAY_MS);
 
         // Only authenticate fingerprint once when assistant is visible
         mAssistantVisible = false;
@@ -539,10 +543,10 @@
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
-                cb.onFingerprintAuthFailed();
+                cb.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
             }
         }
-        handleFingerprintHelp(-1, mContext.getString(R.string.fingerprint_not_recognized));
+        handleFingerprintHelp(-1, mContext.getString(R.string.kg_fingerprint_not_recognized));
     }
 
     private void handleFingerprintAcquired(int acquireInfo) {
@@ -552,7 +556,7 @@
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
-                cb.onFingerprintAcquired();
+                cb.onBiometricAcquired(BiometricSourceType.FINGERPRINT);
             }
         }
     }
@@ -577,7 +581,7 @@
             }
             onFingerprintAuthenticated(userId);
         } finally {
-            setFingerprintRunningState(FINGERPRINT_STATE_STOPPED);
+            setFingerprintRunningState(BIOMETRIC_STATE_STOPPED);
         }
         Trace.endSection();
     }
@@ -586,7 +590,7 @@
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
-                cb.onFingerprintHelp(msgId, helpString);
+                cb.onBiometricHelp(msgId, helpString, BiometricSourceType.FINGERPRINT);
             }
         }
     }
@@ -595,23 +599,23 @@
         @Override
         public void run() {
             Log.w(TAG, "Retrying fingerprint after HW unavailable, attempt " +
-                    mHardwareUnavailableRetryCount);
+                    mHardwareFingerprintUnavailableRetryCount);
             updateFingerprintListeningState();
         }
     };
 
     private void handleFingerprintError(int msgId, String errString) {
         if (msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED
-                && mFingerprintRunningState == FINGERPRINT_STATE_CANCELLING_RESTARTING) {
-            setFingerprintRunningState(FINGERPRINT_STATE_STOPPED);
+                && mFingerprintRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING) {
+            setFingerprintRunningState(BIOMETRIC_STATE_STOPPED);
             startListeningForFingerprint();
         } else {
-            setFingerprintRunningState(FINGERPRINT_STATE_STOPPED);
+            setFingerprintRunningState(BIOMETRIC_STATE_STOPPED);
         }
 
         if (msgId == FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE) {
-            if (mHardwareUnavailableRetryCount < HW_UNAVAILABLE_RETRY_MAX) {
-                mHardwareUnavailableRetryCount++;
+            if (mHardwareFingerprintUnavailableRetryCount < HW_UNAVAILABLE_RETRY_MAX) {
+                mHardwareFingerprintUnavailableRetryCount++;
                 mHandler.removeCallbacks(mRetryFingerprintAuthentication);
                 mHandler.postDelayed(mRetryFingerprintAuthentication, HW_UNAVAILABLE_TIMEOUT);
             }
@@ -626,7 +630,7 @@
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
-                cb.onFingerprintError(msgId, errString);
+                cb.onBiometricError(msgId, errString, BiometricSourceType.FINGERPRINT);
             }
         }
     }
@@ -636,8 +640,8 @@
     }
 
     private void setFingerprintRunningState(int fingerprintRunningState) {
-        boolean wasRunning = mFingerprintRunningState == FINGERPRINT_STATE_RUNNING;
-        boolean isRunning = fingerprintRunningState == FINGERPRINT_STATE_RUNNING;
+        boolean wasRunning = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING;
+        boolean isRunning = fingerprintRunningState == BIOMETRIC_STATE_RUNNING;
         mFingerprintRunningState = fingerprintRunningState;
 
         // Clients of KeyguardUpdateMonitor don't care about the internal state about the
@@ -653,10 +657,162 @@
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
-                cb.onFingerprintRunningStateChanged(isFingerprintDetectionRunning());
+                cb.onBiometricRunningStateChanged(isFingerprintDetectionRunning(),
+                        BiometricSourceType.FINGERPRINT);
             }
         }
     }
+
+    private void onFaceAuthenticated(int userId) {
+        Trace.beginSection("KeyGuardUpdateMonitor#onFaceAuthenticated");
+        mUserFaceAuthenticated.put(userId, true);
+        // Update/refresh trust state only if user can skip bouncer
+        if (getUserCanSkipBouncer(userId)) {
+            mTrustManager.unlockedByBiometricForUser(userId, BiometricSourceType.FACE);
+        }
+        // Don't send cancel if authentication succeeds
+        mFaceCancelSignal = null;
+        for (int i = 0; i < mCallbacks.size(); i++) {
+            KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+            if (cb != null) {
+                cb.onBiometricAuthenticated(userId,
+                        BiometricSourceType.FACE);
+            }
+        }
+
+        mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATION_CONTINUE),
+                BIOMETRIC_CONTINUE_DELAY_MS);
+
+        // Only authenticate face once when assistant is visible
+        mAssistantVisible = false;
+
+        Trace.endSection();
+    }
+
+    private void handleFaceAuthFailed() {
+        for (int i = 0; i < mCallbacks.size(); i++) {
+            KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+            if (cb != null) {
+                cb.onBiometricAuthFailed(BiometricSourceType.FACE);
+            }
+        }
+        handleFaceHelp(-1, mContext.getString(R.string.kg_face_not_recognized));
+    }
+
+    private void handleFaceAcquired(int acquireInfo) {
+        if (acquireInfo != FaceManager.FACE_ACQUIRED_GOOD) {
+            return;
+        }
+        for (int i = 0; i < mCallbacks.size(); i++) {
+            KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+            if (cb != null) {
+                cb.onBiometricAcquired(BiometricSourceType.FACE);
+            }
+        }
+    }
+
+    private void handleFaceAuthenticated(int authUserId) {
+        Trace.beginSection("KeyGuardUpdateMonitor#handlerFaceAuthenticated");
+        try {
+            final int userId;
+            try {
+                userId = ActivityManager.getService().getCurrentUser().id;
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to get current user id: ", e);
+                return;
+            }
+            if (userId != authUserId) {
+                Log.d(TAG, "Face authenticated for wrong user: " + authUserId);
+                return;
+            }
+            if (isFaceDisabled(userId)) {
+                Log.d(TAG, "Face authentication disabled by DPM for userId: " + userId);
+                return;
+            }
+            onFaceAuthenticated(userId);
+        } finally {
+            setFaceRunningState(BIOMETRIC_STATE_STOPPED);
+        }
+        Trace.endSection();
+    }
+
+    private void handleFaceHelp(int msgId, String helpString) {
+        for (int i = 0; i < mCallbacks.size(); i++) {
+            KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+            if (cb != null) {
+                cb.onBiometricHelp(msgId, helpString, BiometricSourceType.FACE);
+            }
+        }
+    }
+
+    private Runnable mRetryFaceAuthentication = new Runnable() {
+        @Override
+        public void run() {
+            Log.w(TAG, "Retrying face after HW unavailable, attempt " +
+                    mHardwareFaceUnavailableRetryCount);
+            updateFaceListeningState();
+        }
+    };
+
+    private void handleFaceError(int msgId, String errString) {
+        if (msgId == FaceManager.FACE_ERROR_CANCELED
+                && mFaceRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING) {
+            setFaceRunningState(BIOMETRIC_STATE_STOPPED);
+            startListeningForFace();
+        } else {
+            setFaceRunningState(BIOMETRIC_STATE_STOPPED);
+        }
+
+        if (msgId == FaceManager.FACE_ERROR_HW_UNAVAILABLE) {
+            if (mHardwareFaceUnavailableRetryCount < HW_UNAVAILABLE_RETRY_MAX) {
+                mHardwareFaceUnavailableRetryCount++;
+                mHandler.removeCallbacks(mRetryFaceAuthentication);
+                mHandler.postDelayed(mRetryFaceAuthentication, HW_UNAVAILABLE_TIMEOUT);
+            }
+        }
+
+        if (msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT) {
+            mLockPatternUtils.requireStrongAuth(
+                    LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT,
+                    getCurrentUser());
+        }
+
+        for (int i = 0; i < mCallbacks.size(); i++) {
+            KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+            if (cb != null) {
+                cb.onBiometricError(msgId, errString,
+                        BiometricSourceType.FACE);
+            }
+        }
+    }
+
+    private void handleFaceLockoutReset() {
+        updateFaceListeningState();
+    }
+
+    private void setFaceRunningState(int faceRunningState) {
+        boolean wasRunning = mFaceRunningState == BIOMETRIC_STATE_RUNNING;
+        boolean isRunning = faceRunningState == BIOMETRIC_STATE_RUNNING;
+        mFaceRunningState = faceRunningState;
+
+        // Clients of KeyguardUpdateMonitor don't care about the internal state or about the
+        // asynchronousness of the cancel cycle. So only notify them if the actualy running state
+        // has changed.
+        if (wasRunning != isRunning) {
+            notifyFaceRunningStateChanged();
+        }
+    }
+
+    private void notifyFaceRunningStateChanged() {
+        for (int i = 0; i < mCallbacks.size(); i++) {
+            KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+            if (cb != null) {
+                cb.onBiometricRunningStateChanged(isFaceDetectionRunning(),
+                        BiometricSourceType.FACE);
+            }
+        }
+    }
+
     private void handleFaceUnlockStateChanged(boolean running, int userId) {
         checkIsHandlerThread();
         mUserFaceUnlockRunning.put(userId, running);
@@ -673,7 +829,11 @@
     }
 
     public boolean isFingerprintDetectionRunning() {
-        return mFingerprintRunningState == FINGERPRINT_STATE_RUNNING;
+        return mFingerprintRunningState == BIOMETRIC_STATE_RUNNING;
+    }
+
+    public boolean isFaceDetectionRunning() {
+        return mFaceRunningState == BIOMETRIC_STATE_RUNNING;
     }
 
     private boolean isTrustDisabled(int userId) {
@@ -692,9 +852,18 @@
                 || isSimPinSecure();
     }
 
+    private boolean isFaceDisabled(int userId) {
+        final DevicePolicyManager dpm =
+                (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+        return dpm != null && (dpm.getKeyguardDisabledFeatures(null, userId)
+                & DevicePolicyManager.KEYGUARD_DISABLE_FACE) != 0
+                || isSimPinSecure();
+    }
+
+
     public boolean getUserCanSkipBouncer(int userId) {
         return getUserHasTrust(userId) || (mUserFingerprintAuthenticated.get(userId)
-                && isUnlockingWithFingerprintAllowed());
+                && isUnlockingWithBiometricAllowed());
     }
 
     public boolean getUserHasTrust(int userId) {
@@ -705,8 +874,8 @@
         return mUserTrustIsManaged.get(userId) && !isTrustDisabled(userId);
     }
 
-    public boolean isUnlockingWithFingerprintAllowed() {
-        return mStrongAuthTracker.isUnlockingWithFingerprintAllowed();
+    public boolean isUnlockingWithBiometricAllowed() {
+        return mStrongAuthTracker.isUnlockingWithBiometricAllowed();
     }
 
     public boolean isUserInLockdown(int userId) {
@@ -862,7 +1031,7 @@
         }
     };
 
-    private final FingerprintManager.LockoutResetCallback mLockoutResetCallback
+    private final FingerprintManager.LockoutResetCallback mFingerprintLockoutResetCallback
             = new FingerprintManager.LockoutResetCallback() {
         @Override
         public void onLockoutReset() {
@@ -870,13 +1039,21 @@
         }
     };
 
-    private FingerprintManager.AuthenticationCallback mAuthenticationCallback
+    private final FaceManager.LockoutResetCallback mFaceLockoutResetCallback
+            = new FaceManager.LockoutResetCallback() {
+        @Override
+        public void onLockoutReset() {
+            handleFaceLockoutReset();
+        }
+    };
+
+    private FingerprintManager.AuthenticationCallback mFingerprintAuthenticationCallback
             = new AuthenticationCallback() {
 
         @Override
         public void onAuthenticationFailed() {
             handleFingerprintAuthFailed();
-        };
+        }
 
         @Override
         public void onAuthenticationSucceeded(AuthenticationResult result) {
@@ -900,8 +1077,42 @@
             handleFingerprintAcquired(acquireInfo);
         }
     };
+
+    private FaceManager.AuthenticationCallback mFaceAuthenticationCallback
+            = new FaceManager.AuthenticationCallback() {
+
+        @Override
+        public void onAuthenticationFailed() {
+            handleFaceAuthFailed();
+        }
+
+        @Override
+        public void onAuthenticationSucceeded(FaceManager.AuthenticationResult result) {
+            Trace.beginSection("KeyguardUpdateMonitor#onAuthenticationSucceeded");
+            handleFaceAuthenticated(result.getUserId());
+            Trace.endSection();
+        }
+
+        @Override
+        public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
+            handleFaceHelp(helpMsgId, helpString.toString());
+        }
+
+        @Override
+        public void onAuthenticationError(int errMsgId, CharSequence errString) {
+            handleFaceError(errMsgId, errString.toString());
+        }
+
+        @Override
+        public void onAuthenticationAcquired(int acquireInfo) {
+            handleFaceAcquired(acquireInfo);
+        }
+    };
+
     private CancellationSignal mFingerprintCancelSignal;
+    private CancellationSignal mFaceCancelSignal;
     private FingerprintManager mFpm;
+    private FaceManager mFaceAuthenticationManager;
 
     /**
      * When we receive a
@@ -1049,9 +1260,9 @@
             super(context);
         }
 
-        public boolean isUnlockingWithFingerprintAllowed() {
+        public boolean isUnlockingWithBiometricAllowed() {
             int userId = getCurrentUser();
-            return isFingerprintAllowedForUser(userId);
+            return isBiometricAllowedForUser(userId);
         }
 
         public boolean hasUserAuthenticatedSinceBoot() {
@@ -1075,7 +1286,7 @@
 
     protected void handleStartedWakingUp() {
         Trace.beginSection("KeyguardUpdateMonitor#handleStartedWakingUp");
-        updateFingerprintListeningState();
+        updateBiometricListeningState();
         final int count = mCallbacks.size();
         for (int i = 0; i < count; i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
@@ -1087,7 +1298,7 @@
     }
 
     protected void handleStartedGoingToSleep(int arg1) {
-        clearFingerprintRecognized();
+        clearBiometricRecognized();
         final int count = mCallbacks.size();
         for (int i = 0; i < count; i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
@@ -1096,7 +1307,7 @@
             }
         }
         mGoingToSleep = true;
-        updateFingerprintListeningState();
+        updateBiometricListeningState();
     }
 
     protected void handleFinishedGoingToSleep(int arg1) {
@@ -1108,7 +1319,7 @@
                 cb.onFinishedGoingToSleep(arg1);
             }
         }
-        updateFingerprintListeningState();
+        updateBiometricListeningState();
     }
 
     private void handleScreenTurnedOn() {
@@ -1122,7 +1333,8 @@
     }
 
     private void handleScreenTurnedOff() {
-        mHardwareUnavailableRetryCount = 0;
+        mHardwareFingerprintUnavailableRetryCount = 0;
+        mHardwareFaceUnavailableRetryCount = 0;
         final int count = mCallbacks.size();
         for (int i = 0; i < count; i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
@@ -1141,7 +1353,7 @@
                 cb.onDreamingStateChanged(mIsDreaming);
             }
         }
-        updateFingerprintListeningState();
+        updateBiometricListeningState();
     }
 
     private void handleUserInfoChanged(int userId) {
@@ -1238,9 +1450,17 @@
         if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
             mFpm = (FingerprintManager) context.getSystemService(Context.FINGERPRINT_SERVICE);
         }
-        updateFingerprintListeningState();
+
+        if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE)) {
+            mFaceAuthenticationManager =
+                    (FaceManager) context.getSystemService(Context.FACE_SERVICE);
+        }
+        updateBiometricListeningState();
         if (mFpm != null) {
-            mFpm.addLockoutResetCallback(mLockoutResetCallback);
+            mFpm.addLockoutResetCallback(mFingerprintLockoutResetCallback);
+        }
+        if (mFaceAuthenticationManager != null) {
+            mFaceAuthenticationManager.addLockoutResetCallback(mFaceLockoutResetCallback);
         }
 
         ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
@@ -1249,28 +1469,55 @@
         mLogoutEnabled = mDevicePolicyManager.isLogoutEnabled();
     }
 
+    private void updateBiometricListeningState() {
+        updateFingerprintListeningState();
+        updateFaceListeningState();
+    }
+
     private void updateFingerprintListeningState() {
         // If this message exists, we should not authenticate again until this message is
         // consumed by the handler
-        if (mHandler.hasMessages(MSG_FINGERPRINT_AUTHENTICATION_CONTINUE)) {
+        if (mHandler.hasMessages(MSG_BIOMETRIC_AUTHENTICATION_CONTINUE)) {
             return;
         }
         mHandler.removeCallbacks(mRetryFingerprintAuthentication);
         boolean shouldListenForFingerprint = shouldListenForFingerprint();
-        if (mFingerprintRunningState == FINGERPRINT_STATE_RUNNING && !shouldListenForFingerprint) {
+        if (mFingerprintRunningState == BIOMETRIC_STATE_RUNNING && !shouldListenForFingerprint) {
             stopListeningForFingerprint();
-        } else if (mFingerprintRunningState != FINGERPRINT_STATE_RUNNING
+        } else if (mFingerprintRunningState != BIOMETRIC_STATE_RUNNING
                 && shouldListenForFingerprint) {
             startListeningForFingerprint();
         }
     }
 
+    private void updateFaceListeningState() {
+        // If this message exists, we should not authenticate again until this message is
+        // consumed by the handler
+        if (mHandler.hasMessages(MSG_BIOMETRIC_AUTHENTICATION_CONTINUE)) {
+            return;
+        }
+        mHandler.removeCallbacks(mRetryFaceAuthentication);
+        boolean shouldListenForFace = shouldListenForFace();
+        if (mFaceRunningState == BIOMETRIC_STATE_RUNNING && !shouldListenForFace) {
+            stopListeningForFace();
+        } else if (mFaceRunningState != BIOMETRIC_STATE_RUNNING
+                && shouldListenForFace) {
+            startListeningForFace();
+        }
+    }
+
     private boolean shouldListenForFingerprintAssistant() {
         return mAssistantVisible && mKeyguardOccluded
                 && !mUserFingerprintAuthenticated.get(getCurrentUser(), false)
                 && !mUserHasTrust.get(getCurrentUser(), false);
     }
 
+    private boolean shouldListenForFaceAssistant() {
+        return mAssistantVisible && mKeyguardOccluded
+                && !mUserFaceAuthenticated.get(getCurrentUser(), false)
+                && !mUserHasTrust.get(getCurrentUser(), false);
+    }
+
     private boolean shouldListenForFingerprint() {
         return (mKeyguardIsVisible || !mDeviceInteractive ||
                 (mBouncer && !mKeyguardGoingAway) || mGoingToSleep ||
@@ -1279,9 +1526,18 @@
                 && !mKeyguardGoingAway;
     }
 
+    private boolean shouldListenForFace() {
+        return (mKeyguardIsVisible || !mDeviceInteractive ||
+                (mBouncer && !mKeyguardGoingAway) || mGoingToSleep ||
+                shouldListenForFaceAssistant() || (mKeyguardOccluded && mIsDreaming))
+                && !mSwitchingUser && !isFaceDisabled(getCurrentUser())
+                && !mKeyguardGoingAway;
+    }
+
+
     private void startListeningForFingerprint() {
-        if (mFingerprintRunningState == FINGERPRINT_STATE_CANCELLING) {
-            setFingerprintRunningState(FINGERPRINT_STATE_CANCELLING_RESTARTING);
+        if (mFingerprintRunningState == BIOMETRIC_STATE_CANCELLING) {
+            setFingerprintRunningState(BIOMETRIC_STATE_CANCELLING_RESTARTING);
             return;
         }
         if (DEBUG) Log.v(TAG, "startListeningForFingerprint()");
@@ -1291,9 +1547,27 @@
                 mFingerprintCancelSignal.cancel();
             }
             mFingerprintCancelSignal = new CancellationSignal();
-            mFpm.authenticate(null, mFingerprintCancelSignal, 0, mAuthenticationCallback, null,
-                    userId);
-            setFingerprintRunningState(FINGERPRINT_STATE_RUNNING);
+            mFpm.authenticate(null, mFingerprintCancelSignal, 0, mFingerprintAuthenticationCallback,
+                    null, userId);
+            setFingerprintRunningState(BIOMETRIC_STATE_RUNNING);
+        }
+    }
+
+    private void startListeningForFace() {
+        if (mFaceRunningState == BIOMETRIC_STATE_CANCELLING) {
+            setFaceRunningState(BIOMETRIC_STATE_CANCELLING_RESTARTING);
+            return;
+        }
+        if (DEBUG) Log.v(TAG, "startListeningForFace()");
+        int userId = ActivityManager.getCurrentUser();
+        if (isUnlockWithFacePossible(userId)) {
+            if (mFaceCancelSignal != null) {
+                mFaceCancelSignal.cancel();
+            }
+            mFaceCancelSignal = new CancellationSignal();
+            mFaceAuthenticationManager.authenticate(null, mFaceCancelSignal, 0,
+                    mFaceAuthenticationCallback, null, userId);
+            setFaceRunningState(BIOMETRIC_STATE_RUNNING);
         }
     }
 
@@ -1302,17 +1576,37 @@
                 && mFpm.getEnrolledFingerprints(userId).size() > 0;
     }
 
+    public boolean isUnlockWithFacePossible(int userId) {
+        return mFaceAuthenticationManager != null && mFaceAuthenticationManager.isHardwareDetected()
+                && !isFaceDisabled(userId)
+                && mFaceAuthenticationManager.hasEnrolledFace(userId);
+    }
+
     private void stopListeningForFingerprint() {
         if (DEBUG) Log.v(TAG, "stopListeningForFingerprint()");
-        if (mFingerprintRunningState == FINGERPRINT_STATE_RUNNING) {
+        if (mFingerprintRunningState == BIOMETRIC_STATE_RUNNING) {
             if (mFingerprintCancelSignal != null) {
                 mFingerprintCancelSignal.cancel();
                 mFingerprintCancelSignal = null;
             }
-            setFingerprintRunningState(FINGERPRINT_STATE_CANCELLING);
+            setFingerprintRunningState(BIOMETRIC_STATE_CANCELLING);
         }
-        if (mFingerprintRunningState == FINGERPRINT_STATE_CANCELLING_RESTARTING) {
-            setFingerprintRunningState(FINGERPRINT_STATE_CANCELLING);
+        if (mFingerprintRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING) {
+            setFingerprintRunningState(BIOMETRIC_STATE_CANCELLING);
+        }
+    }
+
+    private void stopListeningForFace() {
+        if (DEBUG) Log.v(TAG, "stopListeningForFace()");
+        if (mFaceRunningState == BIOMETRIC_STATE_RUNNING) {
+            if (mFaceCancelSignal != null) {
+                mFaceCancelSignal.cancel();
+                mFaceCancelSignal = null;
+            }
+            setFaceRunningState(BIOMETRIC_STATE_CANCELLING);
+        }
+        if (mFaceRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING) {
+            setFaceRunningState(BIOMETRIC_STATE_CANCELLING);
         }
     }
 
@@ -1609,7 +1903,7 @@
                 cb.onKeyguardVisibilityChangedRaw(showing);
             }
         }
-        updateFingerprintListeningState();
+        updateBiometricListeningState();
     }
 
     /**
@@ -1617,7 +1911,7 @@
      */
     private void handleKeyguardReset() {
         if (DEBUG) Log.d(TAG, "handleKeyguardReset");
-        updateFingerprintListeningState();
+        updateBiometricListeningState();
         mNeedsSlowUnlockTransition = resolveNeedsSlowUnlockTransition();
     }
 
@@ -1646,7 +1940,7 @@
                 cb.onKeyguardBouncerChanged(isBouncer);
             }
         }
-        updateFingerprintListeningState();
+        updateBiometricListeningState();
     }
 
     /**
@@ -1729,7 +2023,7 @@
     public void setSwitchingUser(boolean switching) {
         mSwitchingUser = switching;
         // Since this comes in on a binder thread, we need to post if first
-        mHandler.post(mUpdateFingerprintListeningState);
+        mHandler.post(mUpdateBiometricListeningState);
     }
 
     private void sendUpdates(KeyguardUpdateMonitorCallback callback) {
@@ -1817,9 +2111,11 @@
         mFailedAttempts.put(userId, getFailedUnlockAttempts(userId) + 1);
     }
 
-    public void clearFingerprintRecognized() {
+    public void clearBiometricRecognized() {
         mUserFingerprintAuthenticated.clear();
-        mTrustManager.clearAllFingerprints();
+        mUserFaceAuthenticated.clear();
+        mTrustManager.clearAllBiometricRecognized(BiometricSourceType.FINGERPRINT);
+        mTrustManager.clearAllBiometricRecognized(BiometricSourceType.FACE);
     }
 
     public boolean isSimPinVoiceSecure() {
@@ -2054,7 +2350,7 @@
             final int userId = ActivityManager.getCurrentUser();
             final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId);
             pw.println("  Fingerprint state (user=" + userId + ")");
-            pw.println("    allowed=" + isUnlockingWithFingerprintAllowed());
+            pw.println("    allowed=" + isUnlockingWithBiometricAllowed());
             pw.println("    auth'd=" + mUserFingerprintAuthenticated.get(userId));
             pw.println("    authSinceBoot="
                     + getStrongAuthTracker().hasUserAuthenticatedSinceBoot());
@@ -2063,5 +2359,18 @@
             pw.println("    strongAuthFlags=" + Integer.toHexString(strongAuthFlags));
             pw.println("    trustManaged=" + getUserTrustIsManaged(userId));
         }
+        if (mFaceAuthenticationManager != null && mFaceAuthenticationManager.isHardwareDetected()) {
+            final int userId = ActivityManager.getCurrentUser();
+            final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId);
+            pw.println("  Face authentication state (user=" + userId + ")");
+            pw.println("    allowed=" + isUnlockingWithBiometricAllowed());
+            pw.println("    auth'd=" + mUserFaceAuthenticated.get(userId));
+            pw.println("    authSinceBoot="
+                    + getStrongAuthTracker().hasUserAuthenticatedSinceBoot());
+            pw.println("    disabled(DPM)=" + isFaceDisabled(userId));
+            pw.println("    possible=" + isUnlockWithFacePossible(userId));
+            pw.println("    strongAuthFlags=" + Integer.toHexString(strongAuthFlags));
+            pw.println("    trustManaged=" + getUserTrustIsManaged(userId));
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 67571bb..8135ac5 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -17,7 +17,7 @@
 
 import android.app.admin.DevicePolicyManager;
 import android.graphics.Bitmap;
-import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.biometrics.BiometricSourceType;
 import android.media.AudioManager;
 import android.os.SystemClock;
 import android.telephony.TelephonyManager;
@@ -213,38 +213,46 @@
     public void onTrustGrantedWithFlags(int flags, int userId) { }
 
     /**
-     * Called when a finger has been acquired.
+     * Called when a biometric has been acquired.
      * <p>
-     * It is guaranteed that either {@link #onFingerprintAuthenticated} or
-     * {@link #onFingerprintAuthFailed()} is called after this method eventually.
+     * It is guaranteed that either {@link #onBiometricAuthenticated} or
+     * {@link #onBiometricAuthFailed(BiometricSourceType)} is called after this method eventually.
+     * @param biometricSourceType
      */
-    public void onFingerprintAcquired() { }
+    public void onBiometricAcquired(BiometricSourceType biometricSourceType) { }
 
     /**
-     * Called when a fingerprint couldn't be authenticated.
+     * Called when a biometric couldn't be authenticated.
+     * @param biometricSourceType
      */
-    public void onFingerprintAuthFailed() { }
+    public void onBiometricAuthFailed(BiometricSourceType biometricSourceType) { }
 
     /**
-     * Called when a fingerprint is recognized.
-     * @param userId the user id for which the fingerprint was authenticated
+     * Called when a biometric is recognized.
+     * @param userId the user id for which the biometric sample was authenticated
+     * @param biometricSourceType
      */
-    public void onFingerprintAuthenticated(int userId) { }
+    public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType) { }
 
     /**
-     * Called when fingerprint provides help string (e.g. "Try again")
+     * Called when biometric authentication provides help string (e.g. "Try again")
      * @param msgId
      * @param helpString
+     * @param biometricSourceType
      */
-    public void onFingerprintHelp(int msgId, String helpString) { }
+    public void onBiometricHelp(int msgId, String helpString,
+            BiometricSourceType biometricSourceType) { }
 
     /**
-     * Called when fingerprint provides an semi-permanent error message
-     * (e.g. "Hardware not available").
-     * @param msgId one of the error messages listed in {@link FingerprintManager}
+     * Called when biometric authentication method provides a semi-permanent
+     * error message (e.g. "Hardware not available").
+     * @param msgId one of the error messages listed in
+     *        {@link android.hardware.biometrics.BiometricConstants}
      * @param errString
+     * @param biometricSourceType
      */
-    public void onFingerprintError(int msgId, String errString) { }
+    public void onBiometricError(int msgId, String errString,
+            BiometricSourceType biometricSourceType) { }
 
     /**
      * Called when the state of face unlock changed.
@@ -252,9 +260,10 @@
     public void onFaceUnlockStateChanged(boolean running, int userId) { }
 
     /**
-     * Called when the fingerprint running state changed.
+     * Called when biometric running state changed.
      */
-    public void onFingerprintRunningStateChanged(boolean running) { }
+    public void onBiometricRunningStateChanged(boolean running,
+            BiometricSourceType biometricSourceType) { }
 
     /**
      * Called when the state that the user hasn't used strong authentication since quite some time
diff --git a/packages/SystemUI/src/com/android/systemui/LatencyTester.java b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
index cbb69ee..1e458fa 100644
--- a/packages/SystemUI/src/com/android/systemui/LatencyTester.java
+++ b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui;
 
+import android.hardware.biometrics.BiometricSourceType;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -26,7 +27,7 @@
 
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.internal.util.LatencyTracker;
-import com.android.systemui.statusbar.phone.FingerprintUnlockController;
+import com.android.systemui.statusbar.phone.BiometricUnlockController;
 import com.android.systemui.statusbar.phone.StatusBar;
 
 /**
@@ -72,10 +73,10 @@
     }
 
     private void fakeWakeAndUnlock() {
-        FingerprintUnlockController fingerprintUnlockController = getComponent(StatusBar.class)
-                .getFingerprintUnlockController();
-        fingerprintUnlockController.onFingerprintAcquired();
-        fingerprintUnlockController.onFingerprintAuthenticated(
-                KeyguardUpdateMonitor.getCurrentUser());
+        BiometricUnlockController biometricUnlockController = getComponent(StatusBar.class)
+                .getBiometricUnlockController();
+        biometricUnlockController.onBiometricAcquired(BiometricSourceType.FINGERPRINT);
+        biometricUnlockController.onBiometricAuthenticated(
+                KeyguardUpdateMonitor.getCurrentUser(), BiometricSourceType.FINGERPRINT);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index cf6f16a..b9d1021 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -30,6 +30,7 @@
 import android.app.AlarmManager;
 import android.app.PendingIntent;
 import android.app.StatusBarManager;
+import android.hardware.biometrics.BiometricSourceType;
 import android.app.trust.TrustManager;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
@@ -82,8 +83,8 @@
 import com.android.systemui.SystemUIFactory;
 import com.android.systemui.UiOffloadThread;
 import com.android.systemui.classifier.FalsingManager;
-import com.android.systemui.statusbar.phone.FingerprintUnlockController;
 import com.android.systemui.statusbar.phone.NotificationPanelView;
+import com.android.systemui.statusbar.phone.BiometricUnlockController;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 
@@ -522,18 +523,18 @@
         }
 
         @Override
-        public void onFingerprintAuthFailed() {
+        public void onBiometricAuthFailed(BiometricSourceType biometricSourceType) {
             final int currentUser = KeyguardUpdateMonitor.getCurrentUser();
             if (mLockPatternUtils.isSecure(currentUser)) {
-                mLockPatternUtils.getDevicePolicyManager().reportFailedFingerprintAttempt(
+                mLockPatternUtils.getDevicePolicyManager().reportFailedBiometricAttempt(
                         currentUser);
             }
         }
 
         @Override
-        public void onFingerprintAuthenticated(int userId) {
+        public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType) {
             if (mLockPatternUtils.isSecure(userId)) {
-                mLockPatternUtils.getDevicePolicyManager().reportSuccessfulFingerprintAttempt(
+                mLockPatternUtils.getDevicePolicyManager().reportSuccessfulBiometricAttempt(
                         userId);
             }
         }
@@ -1645,7 +1646,7 @@
         }
 
         mUpdateMonitor.clearFailedUnlockAttempts();
-        mUpdateMonitor.clearFingerprintRecognized();
+        mUpdateMonitor.clearBiometricRecognized();
 
         if (mGoingToSleep) {
             Log.i(TAG, "Device is going to sleep, aborting keyguardDone");
@@ -2051,9 +2052,9 @@
 
     public StatusBarKeyguardViewManager registerStatusBar(StatusBar statusBar,
             ViewGroup container, NotificationPanelView panelView,
-            FingerprintUnlockController fingerprintUnlockController) {
+            BiometricUnlockController biometricUnlockController) {
         mStatusBarKeyguardViewManager.registerStatusBar(statusBar, container, panelView,
-                fingerprintUnlockController, mDismissCallbackRegistry);
+                biometricUnlockController, mDismissCallbackRegistry);
         return mStatusBarKeyguardViewManager;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 35e1511..294e2f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -19,6 +19,7 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.app.admin.DevicePolicyManager;
+import android.hardware.biometrics.BiometricSourceType;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -26,6 +27,7 @@
 import android.content.res.Resources;
 import android.content.res.ColorStateList;
 import android.graphics.Color;
+import android.hardware.face.FaceManager;
 import android.hardware.fingerprint.FingerprintManager;
 import android.os.BatteryManager;
 import android.os.BatteryStats;
@@ -70,8 +72,8 @@
     private static final boolean DEBUG_CHARGING_SPEED = false;
 
     private static final int MSG_HIDE_TRANSIENT = 1;
-    private static final int MSG_CLEAR_FP_MSG = 2;
-    private static final long TRANSIENT_FP_ERROR_TIMEOUT = 1300;
+    private static final int MSG_CLEAR_BIOMETRIC_MSG = 2;
+    private static final long TRANSIENT_BIOMETRIC_ERROR_TIMEOUT = 1300;
 
     private final Context mContext;
     private ViewGroup mIndicationArea;
@@ -461,7 +463,7 @@
         public void handleMessage(Message msg) {
             if (msg.what == MSG_HIDE_TRANSIENT) {
                 hideTransientIndication();
-            } else if (msg.what == MSG_CLEAR_FP_MSG) {
+            } else if (msg.what == MSG_CLEAR_BIOMETRIC_MSG) {
                 mLockIcon.setTransientFpError(false);
             }
         }
@@ -526,9 +528,10 @@
         }
 
         @Override
-        public void onFingerprintHelp(int msgId, String helpString) {
+        public void onBiometricHelp(int msgId, String helpString,
+                BiometricSourceType biometricSourceType) {
             KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
-            if (!updateMonitor.isUnlockingWithFingerprintAllowed()) {
+            if (!updateMonitor.isUnlockingWithBiometricAllowed()) {
                 return;
             }
             ColorStateList errorColorState = Utils.getColorError(mContext);
@@ -538,10 +541,10 @@
             } else if (updateMonitor.isScreenOn()) {
                 mLockIcon.setTransientFpError(true);
                 showTransientIndication(helpString, errorColorState);
-                hideTransientIndicationDelayed(TRANSIENT_FP_ERROR_TIMEOUT);
-                mHandler.removeMessages(MSG_CLEAR_FP_MSG);
-                mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CLEAR_FP_MSG),
-                        TRANSIENT_FP_ERROR_TIMEOUT);
+                hideTransientIndicationDelayed(TRANSIENT_BIOMETRIC_ERROR_TIMEOUT);
+                mHandler.removeMessages(MSG_CLEAR_BIOMETRIC_MSG);
+                mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CLEAR_BIOMETRIC_MSG),
+                        TRANSIENT_BIOMETRIC_ERROR_TIMEOUT);
             }
             // Help messages indicate that there was actually a try since the last error, so those
             // are not two successive error messages anymore.
@@ -549,16 +552,15 @@
         }
 
         @Override
-        public void onFingerprintError(int msgId, String errString) {
+        public void onBiometricError(int msgId, String errString,
+                BiometricSourceType biometricSourceType) {
             KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
-            if ((!updateMonitor.isUnlockingWithFingerprintAllowed()
-                    && msgId != FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT)
-                    || msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED) {
+            if (shouldSuppressBiometricError(msgId, biometricSourceType, updateMonitor)) {
                 return;
             }
             ColorStateList errorColorState = Utils.getColorError(mContext);
             if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
-                // When swiping up right after receiving a fingerprint error, the bouncer calls
+                // When swiping up right after receiving a biometric error, the bouncer calls
                 // authenticate leading to the same message being shown again on the bouncer.
                 // We want to avoid this, as it may confuse the user when the message is too
                 // generic.
@@ -576,6 +578,28 @@
             mLastSuccessiveErrorMessage = msgId;
         }
 
+        private boolean shouldSuppressBiometricError(int msgId,
+                BiometricSourceType biometricSourceType, KeyguardUpdateMonitor updateMonitor) {
+            if (biometricSourceType == BiometricSourceType.FINGERPRINT)
+                return shouldSuppressFingerprintError(msgId, updateMonitor);
+            if (biometricSourceType == BiometricSourceType.FACE)
+                return shouldSuppressFaceError(msgId, updateMonitor);
+            return false;
+        }
+
+        private boolean shouldSuppressFingerprintError(int msgId,
+                KeyguardUpdateMonitor updateMonitor) {
+            return ((!updateMonitor.isUnlockingWithBiometricAllowed()
+                    && msgId != FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT)
+                    || msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED);
+        }
+
+        private boolean shouldSuppressFaceError(int msgId, KeyguardUpdateMonitor updateMonitor) {
+            return ((!updateMonitor.isUnlockingWithBiometricAllowed()
+                    && msgId != FaceManager.FACE_ERROR_LOCKOUT_PERMANENT)
+                    || msgId == FaceManager.FACE_ERROR_CANCELED);
+        }
+
         @Override
         public void onTrustAgentErrorMessage(CharSequence message) {
             showTransientIndication(message, Utils.getColorError(mContext));
@@ -592,21 +616,22 @@
         }
 
         @Override
-        public void onFingerprintRunningStateChanged(boolean running) {
+        public void onBiometricRunningStateChanged(boolean running,
+                BiometricSourceType biometricSourceType) {
             if (running) {
                 mMessageToShowOnScreenOn = null;
             }
         }
 
         @Override
-        public void onFingerprintAuthenticated(int userId) {
-            super.onFingerprintAuthenticated(userId);
+        public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType) {
+            super.onBiometricAuthenticated(userId, biometricSourceType);
             mLastSuccessiveErrorMessage = -1;
         }
 
         @Override
-        public void onFingerprintAuthFailed() {
-            super.onFingerprintAuthFailed();
+        public void onBiometricAuthFailed(BiometricSourceType biometricSourceType) {
+            super.onBiometricAuthFailed(biometricSourceType);
             mLastSuccessiveErrorMessage = -1;
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
similarity index 77%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index f0b1a82..0b6fd13 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.phone;
 
+import android.hardware.biometrics.BiometricSourceType;
 import android.content.Context;
 import android.os.Handler;
 import android.os.PowerManager;
@@ -35,14 +36,14 @@
 import java.io.PrintWriter;
 
 /**
- * Controller which coordinates all the fingerprint unlocking actions with the UI.
+ * Controller which coordinates all the biometric unlocking actions with the UI.
  */
-public class FingerprintUnlockController extends KeyguardUpdateMonitorCallback {
+public class BiometricUnlockController extends KeyguardUpdateMonitorCallback {
 
-    private static final String TAG = "FingerprintController";
-    private static final boolean DEBUG_FP_WAKELOCK = KeyguardConstants.DEBUG_FP_WAKELOCK;
-    private static final long FINGERPRINT_WAKELOCK_TIMEOUT_MS = 15 * 1000;
-    private static final String FINGERPRINT_WAKE_LOCK_NAME = "wake-and-unlock wakelock";
+    private static final String TAG = "BiometricUnlockController";
+    private static final boolean DEBUG_BIO_WAKELOCK = KeyguardConstants.DEBUG_BIOMETRIC_WAKELOCK;
+    private static final long BIOMETRIC_WAKELOCK_TIMEOUT_MS = 15 * 1000;
+    private static final String BIOMETRIC_WAKE_LOCK_NAME = "wake-and-unlock wakelock";
 
     /**
      * Mode in which we don't need to wake up the device when we get a fingerprint.
@@ -90,9 +91,9 @@
     public static final int MODE_WAKE_AND_UNLOCK_FROM_DREAM = 7;
 
     /**
-     * How much faster we collapse the lockscreen when authenticating with fingerprint.
+     * How much faster we collapse the lockscreen when authenticating with biometric.
      */
-    private static final float FINGERPRINT_COLLAPSE_SPEEDUP_FACTOR = 1.1f;
+    private static final float BIOMETRIC_COLLAPSE_SPEEDUP_FACTOR = 1.1f;
 
     private PowerManager mPowerManager;
     private Handler mHandler = new Handler();
@@ -108,15 +109,16 @@
     private final UnlockMethodCache mUnlockMethodCache;
     private final Context mContext;
     private int mPendingAuthenticatedUserId = -1;
+    private BiometricSourceType mPendingAuthenticatedBioSourceType = null;
     private boolean mPendingShowBouncer;
     private boolean mHasScreenTurnedOnSinceAuthenticating;
 
-    public FingerprintUnlockController(Context context,
-            DozeScrimController dozeScrimController,
-            KeyguardViewMediator keyguardViewMediator,
-            ScrimController scrimController,
-            StatusBar statusBar,
-            UnlockMethodCache unlockMethodCache) {
+    public BiometricUnlockController(Context context,
+                                     DozeScrimController dozeScrimController,
+                                     KeyguardViewMediator keyguardViewMediator,
+                                     ScrimController scrimController,
+                                     StatusBar statusBar,
+                                     UnlockMethodCache unlockMethodCache) {
         mContext = context;
         mPowerManager = context.getSystemService(PowerManager.class);
         mUpdateMonitor = KeyguardUpdateMonitor.getInstance(context);
@@ -136,21 +138,21 @@
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
     }
 
-    private final Runnable mReleaseFingerprintWakeLockRunnable = new Runnable() {
+    private final Runnable mReleaseBiometricWakeLockRunnable = new Runnable() {
         @Override
         public void run() {
-            if (DEBUG_FP_WAKELOCK) {
-                Log.i(TAG, "fp wakelock: TIMEOUT!!");
+            if (DEBUG_BIO_WAKELOCK) {
+                Log.i(TAG, "biometric wakelock: TIMEOUT!!");
             }
-            releaseFingerprintWakeLock();
+            releaseBiometricWakeLock();
         }
     };
 
-    private void releaseFingerprintWakeLock() {
+    private void releaseBiometricWakeLock() {
         if (mWakeLock != null) {
-            mHandler.removeCallbacks(mReleaseFingerprintWakeLockRunnable);
-            if (DEBUG_FP_WAKELOCK) {
-                Log.i(TAG, "releasing fp wakelock");
+            mHandler.removeCallbacks(mReleaseBiometricWakeLockRunnable);
+            if (DEBUG_BIO_WAKELOCK) {
+                Log.i(TAG, "releasing biometric wakelock");
             }
             mWakeLock.release();
             mWakeLock = null;
@@ -158,24 +160,27 @@
     }
 
     @Override
-    public void onFingerprintAcquired() {
-        Trace.beginSection("FingerprintUnlockController#onFingerprintAcquired");
-        releaseFingerprintWakeLock();
+    public void onBiometricAcquired(BiometricSourceType biometricSourceType) {
+        Trace.beginSection("BiometricUnlockController#onBiometricAcquired");
+        releaseBiometricWakeLock();
         if (!mUpdateMonitor.isDeviceInteractive()) {
             if (LatencyTracker.isEnabled(mContext)) {
-                LatencyTracker.getInstance(mContext).onActionStart(
-                        LatencyTracker.ACTION_FINGERPRINT_WAKE_AND_UNLOCK);
+                int action = LatencyTracker.ACTION_FINGERPRINT_WAKE_AND_UNLOCK;
+                if (biometricSourceType == BiometricSourceType.FACE) {
+                    action = LatencyTracker.ACTION_FACE_WAKE_AND_UNLOCK;
+                }
+                LatencyTracker.getInstance(mContext).onActionStart(action);
             }
             mWakeLock = mPowerManager.newWakeLock(
-                    PowerManager.PARTIAL_WAKE_LOCK, FINGERPRINT_WAKE_LOCK_NAME);
+                    PowerManager.PARTIAL_WAKE_LOCK, BIOMETRIC_WAKE_LOCK_NAME);
             Trace.beginSection("acquiring wake-and-unlock");
             mWakeLock.acquire();
             Trace.endSection();
-            if (DEBUG_FP_WAKELOCK) {
-                Log.i(TAG, "fingerprint acquired, grabbing fp wakelock");
+            if (DEBUG_BIO_WAKELOCK) {
+                Log.i(TAG, "biometric acquired, grabbing biometric wakelock");
             }
-            mHandler.postDelayed(mReleaseFingerprintWakeLockRunnable,
-                    FINGERPRINT_WAKELOCK_TIMEOUT_MS);
+            mHandler.postDelayed(mReleaseBiometricWakeLockRunnable,
+                    BIOMETRIC_WAKELOCK_TIMEOUT_MS);
         }
         Trace.endSection();
     }
@@ -187,10 +192,11 @@
     }
 
     @Override
-    public void onFingerprintAuthenticated(int userId) {
-        Trace.beginSection("FingerprintUnlockController#onFingerprintAuthenticated");
+    public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType) {
+        Trace.beginSection("BiometricUnlockController#onBiometricAuthenticated");
         if (mUpdateMonitor.isGoingToSleep()) {
             mPendingAuthenticatedUserId = userId;
+            mPendingAuthenticatedBioSourceType = biometricSourceType;
             Trace.endSection();
             return;
         }
@@ -211,13 +217,13 @@
             mStatusBarWindowManager.setForceDozeBrightness(true);
         }
         if (!wasDeviceInteractive) {
-            if (DEBUG_FP_WAKELOCK) {
-                Log.i(TAG, "fp wakelock: Authenticated, waking up...");
+            if (DEBUG_BIO_WAKELOCK) {
+                Log.i(TAG, "bio wakelock: Authenticated, waking up...");
             }
-            mPowerManager.wakeUp(SystemClock.uptimeMillis(), "android.policy:FINGERPRINT");
+            mPowerManager.wakeUp(SystemClock.uptimeMillis(), "android.policy:BIOMETRIC");
         }
         Trace.beginSection("release wake-and-unlock");
-        releaseFingerprintWakeLock();
+        releaseBiometricWakeLock();
         Trace.endSection();
         switch (mMode) {
             case MODE_DISMISS_BOUNCER:
@@ -261,7 +267,7 @@
             case MODE_NONE:
                 break;
         }
-        mStatusBar.notifyFpAuthModeChanged();
+        mStatusBar.notifyBiometricAuthModeChanged();
         Trace.endSection();
     }
 
@@ -270,7 +276,7 @@
             mStatusBarKeyguardViewManager.showBouncer(false);
         }
         mStatusBarKeyguardViewManager.animateCollapsePanels(
-                FINGERPRINT_COLLAPSE_SPEEDUP_FACTOR);
+                BIOMETRIC_COLLAPSE_SPEEDUP_FACTOR);
         mPendingShowBouncer = false;
     }
 
@@ -278,28 +284,31 @@
     public void onStartedGoingToSleep(int why) {
         resetMode();
         mPendingAuthenticatedUserId = -1;
+        mPendingAuthenticatedBioSourceType = null;
     }
 
     @Override
     public void onFinishedGoingToSleep(int why) {
-        Trace.beginSection("FingerprintUnlockController#onFinishedGoingToSleep");
+        Trace.beginSection("BiometricUnlockController#onFinishedGoingToSleep");
         if (mPendingAuthenticatedUserId != -1) {
 
             // Post this to make sure it's executed after the device is fully locked.
             mHandler.post(new Runnable() {
                 @Override
                 public void run() {
-                    onFingerprintAuthenticated(mPendingAuthenticatedUserId);
+                    onBiometricAuthenticated(mPendingAuthenticatedUserId,
+                            mPendingAuthenticatedBioSourceType);
                 }
             });
         }
         mPendingAuthenticatedUserId = -1;
+        mPendingAuthenticatedBioSourceType = null;
         Trace.endSection();
     }
 
     public boolean hasPendingAuthentication() {
         return mPendingAuthenticatedUserId != -1
-                && mUpdateMonitor.isUnlockingWithFingerprintAllowed()
+                && mUpdateMonitor.isUnlockingWithBiometricAllowed()
                 && mPendingAuthenticatedUserId == KeyguardUpdateMonitor.getCurrentUser();
     }
 
@@ -308,7 +317,7 @@
     }
 
     private int calculateMode() {
-        boolean unlockingAllowed = mUpdateMonitor.isUnlockingWithFingerprintAllowed();
+        boolean unlockingAllowed = mUpdateMonitor.isUnlockingWithBiometricAllowed();
         boolean deviceDreaming = mUpdateMonitor.isDreaming();
 
         if (!mUpdateMonitor.isDeviceInteractive()) {
@@ -338,17 +347,18 @@
     }
 
     @Override
-    public void onFingerprintAuthFailed() {
+    public void onBiometricAuthFailed(BiometricSourceType biometricSourceType) {
         cleanup();
     }
 
     @Override
-    public void onFingerprintError(int msgId, String errString) {
+    public void onBiometricError(int msgId, String errString,
+            BiometricSourceType biometricSourceType) {
         cleanup();
     }
 
     private void cleanup() {
-        releaseFingerprintWakeLock();
+        releaseBiometricWakeLock();
     }
 
     public void startKeyguardFadingAway() {
@@ -372,7 +382,7 @@
         if (mStatusBar.getNavigationBarView() != null) {
             mStatusBar.getNavigationBarView().setWakeAndUnlocking(false);
         }
-        mStatusBar.notifyFpAuthModeChanged();
+        mStatusBar.notifyBiometricAuthModeChanged();
     }
 
     private final WakefulnessLifecycle.Observer mWakefulnessObserver =
@@ -380,7 +390,7 @@
         @Override
         public void onFinishedWakingUp() {
             if (mPendingShowBouncer) {
-                FingerprintUnlockController.this.showBouncer();
+                BiometricUnlockController.this.showBouncer();
             }
         }
     };
@@ -398,7 +408,7 @@
     }
 
     public void dump(PrintWriter pw) {
-        pw.println(" FingerprintUnlockController:");
+        pw.println(" BiometricUnlockController:");
         pw.print("   mMode="); pw.println(mMode);
         pw.print("   mWakeLock="); pw.println(mWakeLock);
     }
@@ -415,7 +425,7 @@
     /**
      * Successful authentication with fingerprint when the screen was either on or off.
      */
-    public boolean isFingerprintUnlock() {
+    public boolean isBiometricUnlock() {
         return isWakeAndUnlock() || mMode == MODE_UNLOCK;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 3b12051..25b97bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -28,6 +28,7 @@
 import android.app.ActivityOptions;
 import android.app.ActivityTaskManager;
 import android.app.admin.DevicePolicyManager;
+import android.hardware.biometrics.BiometricSourceType;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -774,7 +775,8 @@
                 }
 
                 @Override
-                public void onFingerprintRunningStateChanged(boolean running) {
+                public void onBiometricRunningStateChanged(boolean running,
+                        BiometricSourceType biometricSourceType) {
                     mLockIcon.update();
                 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
index 8b8cbfe..8cace72 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
@@ -44,7 +44,7 @@
 
     private final DarkIconDispatcher mStatusBarIconController;
     private final BatteryController mBatteryController;
-    private FingerprintUnlockController mFingerprintUnlockController;
+    private BiometricUnlockController mBiometricUnlockController;
 
     private LightBarTransitionsController mNavigationBarController;
     private int mSystemUiVisibility;
@@ -89,9 +89,9 @@
         updateNavigation();
     }
 
-    public void setFingerprintUnlockController(
-            FingerprintUnlockController fingerprintUnlockController) {
-        mFingerprintUnlockController = fingerprintUnlockController;
+    public void setBiometricUnlockController(
+            BiometricUnlockController biometricUnlockController) {
+        mBiometricUnlockController = biometricUnlockController;
     }
 
     public void onSystemUiVisibilityChanged(int fullscreenStackVis, int dockedStackVis,
@@ -178,12 +178,12 @@
     }
 
     private boolean animateChange() {
-        if (mFingerprintUnlockController == null) {
+        if (mBiometricUnlockController == null) {
             return false;
         }
-        int unlockMode = mFingerprintUnlockController.getMode();
-        return unlockMode != FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
-                && unlockMode != FingerprintUnlockController.MODE_WAKE_AND_UNLOCK;
+        int unlockMode = mBiometricUnlockController.getMode();
+        return unlockMode != BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
+                && unlockMode != BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
     }
 
     private void updateStatus(Rect fullscreenStackBounds, Rect dockedStackBounds) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
index 4b66ee5a..8928530 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
@@ -306,7 +306,7 @@
     private int getState() {
         KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
         boolean fingerprintRunning = updateMonitor.isFingerprintDetectionRunning();
-        boolean unlockingAllowed = updateMonitor.isUnlockingWithFingerprintAllowed();
+        boolean unlockingAllowed = updateMonitor.isUnlockingWithBiometricAllowed();
         if (mTransientFpError) {
             return STATE_FINGERPRINT_ERROR;
         } else if (mUnlockMethodCache.canSkipBouncer()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 4a98262..6077e79 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -18,6 +18,7 @@
 
 import static android.view.MotionEvent.ACTION_DOWN;
 import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_BACK;
+import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_DEAD_ZONE;
 import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_HOME;
 import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_NONE;
 
@@ -336,15 +337,15 @@
 
     @Override
     public boolean onInterceptTouchEvent(MotionEvent event) {
-        if (shouldDeadZoneConsumeTouchEvents(event)) {
-            return true;
-        }
+        final boolean deadZoneConsumed = shouldDeadZoneConsumeTouchEvents(event);
         switch (event.getActionMasked()) {
             case ACTION_DOWN:
                 int x = (int) event.getX();
                 int y = (int) event.getY();
                 mDownHitTarget = HIT_TARGET_NONE;
-                if (getBackButton().isVisible() && mBackButtonBounds.contains(x, y)) {
+                if (deadZoneConsumed) {
+                    mDownHitTarget = HIT_TARGET_DEAD_ZONE;
+                } else if (getBackButton().isVisible() && mBackButtonBounds.contains(x, y)) {
                     mDownHitTarget = HIT_TARGET_BACK;
                 } else if (getHomeButton().isVisible() && mHomeButtonBounds.contains(x, y)) {
                     mDownHitTarget = HIT_TARGET_HOME;
@@ -361,9 +362,7 @@
 
     @Override
     public boolean onTouchEvent(MotionEvent event) {
-        if (shouldDeadZoneConsumeTouchEvents(event)) {
-            return true;
-        }
+        shouldDeadZoneConsumeTouchEvents(event);
         if (mGestureHelper.onTouchEvent(event)) {
             return true;
         }
@@ -772,7 +771,7 @@
                 showSwipeUpUI ? mQuickStepAccessibilityDelegate : null);
     }
 
-    private void updateSlippery() {
+    public void updateSlippery() {
         setSlippery(!isQuickStepSwipeUpEnabled() || mPanelView.isFullyExpanded());
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java
index 15790d4..765cc49 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java
@@ -22,6 +22,7 @@
 import static com.android.systemui.Interpolators.ALPHA_OUT;
 import static com.android.systemui.OverviewProxyService.DEBUG_OVERVIEW_PROXY;
 import static com.android.systemui.OverviewProxyService.TAG_OPS;
+import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_DEAD_ZONE;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -185,6 +186,8 @@
     }
 
     private boolean handleTouchEvent(MotionEvent event) {
+        final boolean deadZoneConsumed =
+                mNavigationBarView.getDownHitTarget() == HIT_TARGET_DEAD_ZONE;
         if (mOverviewEventSender.getProxy() == null || (!mNavigationBarView.isQuickScrubEnabled()
                 && !mNavigationBarView.isQuickStepSwipeUpEnabled())) {
             return false;
@@ -304,7 +307,7 @@
                 || action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP)) {
             proxyMotionEvents(event);
         }
-        return mQuickScrubActive || mQuickStepStarted;
+        return mQuickScrubActive || mQuickStepStarted || deadZoneConsumed;
     }
 
     @Override
@@ -438,6 +441,9 @@
             mTrackAnimator.playTogether(trackAnimator, navBarAnimator);
             mTrackAnimator.start();
 
+            // Disable slippery for quick scrub to not cancel outside the nav bar
+            mNavigationBarView.updateSlippery();
+
             try {
                 mOverviewEventSender.getProxy().onQuickScrubStart();
                 if (DEBUG_OVERVIEW_PROXY) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index c2f31bf..a9f1b4b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -361,7 +361,7 @@
     private VolumeComponent mVolumeComponent;
     private BrightnessMirrorController mBrightnessMirrorController;
     private boolean mBrightnessMirrorVisible;
-    protected FingerprintUnlockController mFingerprintUnlockController;
+    protected BiometricUnlockController mBiometricUnlockController;
     private LightBarController mLightBarController;
     protected LockscreenWallpaper mLockscreenWallpaper;
 
@@ -505,7 +505,7 @@
     protected NotificationLockscreenUserManager mLockscreenUserManager;
     protected NotificationRemoteInputManager mRemoteInputManager;
 
-    private BroadcastReceiver mWallpaperChangedReceiver = new BroadcastReceiver() {
+    private final BroadcastReceiver mWallpaperChangedReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             WallpaperManager wallpaperManager = context.getSystemService(WallpaperManager.class);
@@ -513,7 +513,7 @@
                 Log.w(TAG, "WallpaperManager not available");
                 return;
             }
-            WallpaperInfo info = wallpaperManager.getWallpaperInfo();
+            WallpaperInfo info = wallpaperManager.getWallpaperInfo(UserHandle.USER_CURRENT);
             final boolean supportsAmbientMode = info != null &&
                     info.getSupportsAmbientMode();
 
@@ -707,7 +707,8 @@
 
         // Make sure we always have the most current wallpaper info.
         IntentFilter wallpaperChangedFilter = new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED);
-        mContext.registerReceiver(mWallpaperChangedReceiver, wallpaperChangedFilter);
+        mContext.registerReceiverAsUser(mWallpaperChangedReceiver, UserHandle.ALL,
+                wallpaperChangedFilter, null /* broadcastPermission */, null /* scheduler */);
         mWallpaperChangedReceiver.onReceive(mContext, null);
 
         mLockscreenUserManager.setUpWithPresenter(this, mEntryManager);
@@ -1322,18 +1323,18 @@
     protected void startKeyguard() {
         Trace.beginSection("StatusBar#startKeyguard");
         KeyguardViewMediator keyguardViewMediator = getComponent(KeyguardViewMediator.class);
-        mFingerprintUnlockController = new FingerprintUnlockController(mContext,
+        mBiometricUnlockController = new BiometricUnlockController(mContext,
                 mDozeScrimController, keyguardViewMediator,
                 mScrimController, this, UnlockMethodCache.getInstance(mContext));
         mStatusBarKeyguardViewManager = keyguardViewMediator.registerStatusBar(this,
-                getBouncerContainer(), mNotificationPanel, mFingerprintUnlockController);
+                getBouncerContainer(), mNotificationPanel, mBiometricUnlockController);
         mKeyguardIndicationController
                 .setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
-        mFingerprintUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
+        mBiometricUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
         mRemoteInputManager.getController().addCallback(mStatusBarKeyguardViewManager);
 
         mKeyguardViewMediatorCallback = keyguardViewMediator.getViewMediatorCallback();
-        mLightBarController.setFingerprintUnlockController(mFingerprintUnlockController);
+        mLightBarController.setBiometricUnlockController(mBiometricUnlockController);
         Dependency.get(KeyguardDismissUtil.class).setDismissHandler(this::executeWhenUnlocked);
         Trace.endSection();
     }
@@ -1617,8 +1618,8 @@
             return; // called too early
         }
 
-        boolean wakeAndUnlock = mFingerprintUnlockController != null
-            && mFingerprintUnlockController.isWakeAndUnlock();
+        boolean wakeAndUnlock = mBiometricUnlockController != null
+            && mBiometricUnlockController.isWakeAndUnlock();
         if (mLaunchTransitionFadingAway || wakeAndUnlock) {
             mBackdrop.setVisibility(View.INVISIBLE);
             Trace.endSection();
@@ -1670,8 +1671,8 @@
 
         if ((hasArtwork || DEBUG_MEDIA_FAKE_ARTWORK)
                 && (mState != StatusBarState.SHADE || allowWhenShade)
-                && mFingerprintUnlockController.getMode()
-                        != FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
+                && mBiometricUnlockController.getMode()
+                        != BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
                 && !hideBecauseOccluded) {
             // time to show some art!
             if (mBackdrop.getVisibility() != View.VISIBLE) {
@@ -1736,8 +1737,8 @@
                     Log.v(TAG, "DEBUG_MEDIA: Fading out album artwork");
                 }
                 boolean cannotAnimateDoze = mDozing && !ScrimState.AOD.getAnimateChange();
-                if (mFingerprintUnlockController.getMode()
-                        == FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
+                if (mBiometricUnlockController.getMode()
+                        == BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
                         || hideBecauseOccluded || cannotAnimateDoze) {
 
                     // We are unlocking directly - no animation!
@@ -2469,8 +2470,8 @@
         return mGestureRec;
     }
 
-    public FingerprintUnlockController getFingerprintUnlockController() {
-        return mFingerprintUnlockController;
+    public BiometricUnlockController getBiometricUnlockController() {
+        return mBiometricUnlockController;
     }
 
     @Override // CommandQueue
@@ -2804,8 +2805,8 @@
 
         DozeLog.dump(pw);
 
-        if (mFingerprintUnlockController != null) {
-            mFingerprintUnlockController.dump(pw);
+        if (mBiometricUnlockController != null) {
+            mBiometricUnlockController.dump(pw);
         }
 
         if (mKeyguardIndicationController != null) {
@@ -3113,10 +3114,10 @@
                 && mUnlockMethodCache.canSkipBouncer()
                 && !mLeaveOpenOnKeyguardHide
                 && isPulsing()) {
-            // Reuse the fingerprint wake-and-unlock transition if we dismiss keyguard from a pulse.
-            // TODO: Factor this transition out of FingerprintUnlockController.
-            mFingerprintUnlockController.startWakeAndUnlock(
-                    FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING);
+            // Reuse the biometric wake-and-unlock transition if we dismiss keyguard from a pulse.
+            // TODO: Factor this transition out of BiometricUnlockController.
+            mBiometricUnlockController.startWakeAndUnlock(
+                    BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING);
         }
         if (mStatusBarKeyguardViewManager.isShowing()) {
             mStatusBarKeyguardViewManager.dismissWithAction(action, cancelAction,
@@ -3156,6 +3157,7 @@
         updateNotificationViews();
         mMediaManager.clearCurrentMediaNotification();
         setLockscreenUser(newUserId);
+        mWallpaperChangedReceiver.onReceive(mContext, null);
     }
 
     @Override
@@ -3520,8 +3522,8 @@
     }
 
     private boolean updateIsKeyguard() {
-        boolean wakeAndUnlocking = mFingerprintUnlockController.getMode()
-                == FingerprintUnlockController.MODE_WAKE_AND_UNLOCK;
+        boolean wakeAndUnlocking = mBiometricUnlockController.getMode()
+                == BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
 
         // For dozing, keyguard needs to be shown whenever the device is non-interactive. Otherwise
         // there's no surface we can show to the user. Note that the device goes fully interactive
@@ -3568,8 +3570,8 @@
     }
 
     private void updatePanelExpansionForKeyguard() {
-        if (mState == StatusBarState.KEYGUARD && mFingerprintUnlockController.getMode()
-                != FingerprintUnlockController.MODE_WAKE_AND_UNLOCK) {
+        if (mState == StatusBarState.KEYGUARD && mBiometricUnlockController.getMode()
+                != BiometricUnlockController.MODE_WAKE_AND_UNLOCK) {
             instantExpandNotificationsPanel();
         } else if (mState == StatusBarState.FULLSCREEN_USER_SWITCHER) {
             instantCollapseNotificationPanel();
@@ -4697,7 +4699,7 @@
                 || mScreenLifecycle.getScreenState() == ScreenLifecycle.SCREEN_ON;
     }
 
-    public void notifyFpAuthModeChanged() {
+    public void notifyBiometricAuthModeChanged() {
         updateDozing();
         updateScrimController();
     }
@@ -4706,13 +4708,13 @@
         Trace.beginSection("StatusBar#updateDozing");
         // When in wake-and-unlock while pulsing, keep dozing state until fully unlocked.
         boolean dozing = mDozingRequested && mState == StatusBarState.KEYGUARD
-                || mFingerprintUnlockController.getMode()
-                        == FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
+                || mBiometricUnlockController.getMode()
+                        == BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
         final boolean alwaysOn = DozeParameters.getInstance(mContext).getAlwaysOn();
         // When in wake-and-unlock we may not have received a change to mState
         // but we still should not be dozing, manually set to false.
-        if (mFingerprintUnlockController.getMode() ==
-                FingerprintUnlockController.MODE_WAKE_AND_UNLOCK) {
+        if (mBiometricUnlockController.getMode() ==
+                mBiometricUnlockController.MODE_WAKE_AND_UNLOCK) {
             dozing = false;
         }
         if (mDozing != dozing) {
@@ -4736,11 +4738,11 @@
 
         // We don't want to end up in KEYGUARD state when we're unlocking with
         // fingerprint from doze. We should cross fade directly from black.
-        boolean wakeAndUnlocking = mFingerprintUnlockController.isWakeAndUnlock();
+        boolean wakeAndUnlocking = mBiometricUnlockController.isWakeAndUnlock();
 
         // Do not animate the scrim expansion when triggered by the fingerprint sensor.
         mScrimController.setExpansionAffectsAlpha(
-                !mFingerprintUnlockController.isFingerprintUnlock());
+                !mBiometricUnlockController.isBiometricUnlock());
 
         if (mBouncerShowing) {
             // Bouncer needs the front scrim when it's on top of an activity,
@@ -4882,8 +4884,8 @@
 
         @Override
         public boolean isPulsingBlocked() {
-            return mFingerprintUnlockController.getMode()
-                    == FingerprintUnlockController.MODE_WAKE_AND_UNLOCK;
+            return mBiometricUnlockController.getMode()
+                    == BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
         }
 
         @Override
@@ -4894,7 +4896,7 @@
 
         @Override
         public boolean isBlockingDoze() {
-            if (mFingerprintUnlockController.hasPendingAuthentication()) {
+            if (mBiometricUnlockController.hasPendingAuthentication()) {
                 Log.i(TAG, "Blocking AOD because fingerprint has authenticated");
                 return true;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index affc424..378910a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -17,8 +17,8 @@
 package com.android.systemui.statusbar.phone;
 
 import static com.android.keyguard.KeyguardHostView.OnDismissAction;
-import static com.android.systemui.statusbar.phone.FingerprintUnlockController.MODE_WAKE_AND_UNLOCK;
-import static com.android.systemui.statusbar.phone.FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
+import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
+import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
 
 import android.content.ComponentCallbacks2;
 import android.content.Context;
@@ -91,7 +91,7 @@
     protected ViewMediatorCallback mViewMediatorCallback;
     protected StatusBar mStatusBar;
     private NotificationPanelView mNotificationPanelView;
-    private FingerprintUnlockController mFingerprintUnlockController;
+    private BiometricUnlockController mBiometricUnlockController;
 
     private ViewGroup mContainer;
 
@@ -108,7 +108,7 @@
     private boolean mLastBouncerDismissible;
     protected boolean mLastRemoteInputActive;
     private boolean mLastDozing;
-    private int mLastFpMode;
+    private int mLastBiometricMode;
     private boolean mGoingToSleepVisibleNotOccluded;
 
     private OnDismissAction mAfterKeyguardGoneAction;
@@ -142,11 +142,11 @@
     public void registerStatusBar(StatusBar statusBar,
             ViewGroup container,
             NotificationPanelView notificationPanelView,
-            FingerprintUnlockController fingerprintUnlockController,
+            BiometricUnlockController biometricUnlockController,
             DismissCallbackRegistry dismissCallbackRegistry) {
         mStatusBar = statusBar;
         mContainer = container;
-        mFingerprintUnlockController = fingerprintUnlockController;
+        mBiometricUnlockController = biometricUnlockController;
         mBouncer = SystemUIFactory.getInstance().createKeyguardBouncer(mContext,
                 mViewMediatorCallback, mLockPatternUtils, container, dismissCallbackRegistry,
                 mExpansionCallback);
@@ -259,7 +259,7 @@
     }
 
     private boolean isWakeAndUnlocking() {
-        int mode = mFingerprintUnlockController.getMode();
+        int mode = mBiometricUnlockController.getMode();
         return mode == MODE_WAKE_AND_UNLOCK || mode == MODE_WAKE_AND_UNLOCK_PULSING;
     }
 
@@ -442,13 +442,13 @@
         } else {
             executeAfterKeyguardGoneAction();
             boolean wakeUnlockPulsing =
-                    mFingerprintUnlockController.getMode() == MODE_WAKE_AND_UNLOCK_PULSING;
+                    mBiometricUnlockController.getMode() == MODE_WAKE_AND_UNLOCK_PULSING;
             if (wakeUnlockPulsing) {
                 delay = 0;
                 fadeoutDuration = 240;
             }
             mStatusBar.setKeyguardFadingAway(startTime, delay, fadeoutDuration);
-            mFingerprintUnlockController.startKeyguardFadingAway();
+            mBiometricUnlockController.startKeyguardFadingAway();
             hideBouncer(true /* destroyView */);
             if (wakeUnlockPulsing) {
                 mStatusBar.fadeKeyguardWhilePulsing();
@@ -460,7 +460,7 @@
                     wakeAndUnlockDejank();
                 } else {
                     mStatusBar.finishKeyguardFadingAway();
-                    mFingerprintUnlockController.finishKeyguardFadingAway();
+                    mBiometricUnlockController.finishKeyguardFadingAway();
                 }
             }
             updateStates();
@@ -484,14 +484,14 @@
         mContainer.postDelayed(() -> mStatusBarWindowManager.setKeyguardFadingAway(false),
                 100);
         mStatusBar.finishKeyguardFadingAway();
-        mFingerprintUnlockController.finishKeyguardFadingAway();
+        mBiometricUnlockController.finishKeyguardFadingAway();
         WindowManagerGlobal.getInstance().trimMemory(
                 ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
 
     }
 
     private void wakeAndUnlockDejank() {
-        if (mFingerprintUnlockController.getMode() == MODE_WAKE_AND_UNLOCK
+        if (mBiometricUnlockController.getMode() == MODE_WAKE_AND_UNLOCK
                 && LatencyTracker.isEnabled(mContext)) {
             DejankUtils.postAfterTraversal(() ->
                     LatencyTracker.getInstance(mContext).onActionEnd(
@@ -618,7 +618,7 @@
         mLastBouncerDismissible = bouncerDismissible;
         mLastRemoteInputActive = remoteInputActive;
         mLastDozing = mDozing;
-        mLastFpMode = mFingerprintUnlockController.getMode();
+        mLastBiometricMode = mBiometricUnlockController.getMode();
         mStatusBar.onKeyguardViewManagerStatesUpdated();
     }
 
@@ -643,9 +643,9 @@
      * @return Whether the navigation bar should be made visible based on the current state.
      */
     protected boolean isNavBarVisible() {
-        int fpMode = mFingerprintUnlockController.getMode();
+        int biometricMode = mBiometricUnlockController.getMode();
         boolean keyguardShowing = mShowing && !mOccluded;
-        boolean hideWhileDozing = mDozing && fpMode != MODE_WAKE_AND_UNLOCK_PULSING;
+        boolean hideWhileDozing = mDozing && biometricMode != MODE_WAKE_AND_UNLOCK_PULSING;
         return (!keyguardShowing && !hideWhileDozing || mBouncer.isShowing()
                 || mRemoteInputActive);
     }
@@ -655,7 +655,7 @@
      */
     protected boolean getLastNavBarVisible() {
         boolean keyguardShowing = mLastShowing && !mLastOccluded;
-        boolean hideWhileDozing = mLastDozing && mLastFpMode != MODE_WAKE_AND_UNLOCK_PULSING;
+        boolean hideWhileDozing = mLastDozing && mLastBiometricMode != MODE_WAKE_AND_UNLOCK_PULSING;
         return (!keyguardShowing && !hideWhileDozing || mLastBouncerShowing
                 || mLastRemoteInputActive);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java
index f9c2130..e5925f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.phone;
 
+import android.hardware.biometrics.BiometricSourceType;
 import android.content.Context;
 import android.os.Trace;
 
@@ -135,9 +136,9 @@
         }
 
         @Override
-        public void onFingerprintAuthenticated(int userId) {
-            Trace.beginSection("KeyguardUpdateMonitorCallback#onFingerprintAuthenticated");
-            if (!mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed()) {
+        public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType) {
+            Trace.beginSection("KeyguardUpdateMonitorCallback#onBiometricAuthenticated");
+            if (!mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed()) {
                 Trace.endSection();
                 return;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
index 67fa049..c468fef 100644
--- a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
+++ b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
@@ -113,7 +113,7 @@
         NotificationChannel screenshotChannel = new NotificationChannel(SCREENSHOTS_HEADSUP,
                 name, NotificationManager.IMPORTANCE_HIGH); // pop on screen
 
-        screenshotChannel.setSound(Uri.parse(""), // silent
+        screenshotChannel.setSound(null, // silent
                 new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION).build());
         screenshotChannel.setBlockableSystem(true);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 94ab9d2..6933328 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -60,7 +60,7 @@
     @Mock
     private NotificationPanelView mNotificationPanelView;
     @Mock
-    private FingerprintUnlockController mFingerprintUnlockController;
+    private BiometricUnlockController mBiometrucUnlockController;
     @Mock
     private DismissCallbackRegistry mDismissCallbackRegistry;
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@@ -72,7 +72,7 @@
         mStatusBarKeyguardViewManager = new TestableStatusBarKeyguardViewManager(getContext(),
                 mViewMediatorCallback, mLockPatternUtils);
         mStatusBarKeyguardViewManager.registerStatusBar(mStatusBar, mContainer,
-                mNotificationPanelView, mFingerprintUnlockController, mDismissCallbackRegistry);
+                mNotificationPanelView, mBiometrucUnlockController, mDismissCallbackRegistry);
         mStatusBarKeyguardViewManager.show(null);
     }
 
@@ -170,8 +170,8 @@
 
     @Test
     public void onPanelExpansionChanged_neverTranslatesBouncerWhenWakeAndUnlock() {
-        when(mFingerprintUnlockController.getMode())
-                .thenReturn(FingerprintUnlockController.MODE_WAKE_AND_UNLOCK);
+        when(mBiometrucUnlockController.getMode())
+                .thenReturn(BiometricUnlockController.MODE_WAKE_AND_UNLOCK);
         mStatusBarKeyguardViewManager.onPanelExpansionChanged(KeyguardBouncer.EXPANSION_VISIBLE,
                 false /* tracking */);
         verify(mBouncer, never()).setExpansion(anyFloat());
@@ -196,7 +196,7 @@
         @Override
         public void registerStatusBar(StatusBar statusBar, ViewGroup container,
                 NotificationPanelView notificationPanelView,
-                FingerprintUnlockController fingerprintUnlockController,
+                BiometricUnlockController fingerprintUnlockController,
                 DismissCallbackRegistry dismissCallbackRegistry) {
             super.registerStatusBar(statusBar, container, notificationPanelView,
                     fingerprintUnlockController, dismissCallbackRegistry);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index cf23bfc..f908dfb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -132,7 +132,7 @@
     @Mock private IStatusBarService mBarService;
     @Mock private ScrimController mScrimController;
     @Mock private ArrayList<Entry> mNotificationList;
-    @Mock private FingerprintUnlockController mFingerprintUnlockController;
+    @Mock private BiometricUnlockController mBiometricUnlockController;
     @Mock private NotificationData mNotificationData;
 
     // Mock dependencies:
@@ -207,7 +207,7 @@
                 mKeyguardIndicationController, mStackScroller, mHeadsUpManager,
                 mPowerManager, mNotificationPanelView, mBarService, mNotificationListener,
                 mNotificationLogger, mVisualStabilityManager, mViewHierarchyManager,
-                mEntryManager, mScrimController, mFingerprintUnlockController,
+                mEntryManager, mScrimController, mBiometricUnlockController,
                 mock(ActivityLaunchAnimator.class), mKeyguardViewMediator,
                 mRemoteInputManager, mock(NotificationGroupManager.class),
                 mock(FalsingManager.class), mock(StatusBarWindowManager.class),
@@ -572,15 +572,15 @@
 
     @Test
     public void testFingerprintNotification_UpdatesScrims() {
-        mStatusBar.notifyFpAuthModeChanged();
+        mStatusBar.notifyBiometricAuthModeChanged();
         verify(mScrimController).transitionTo(any(), any());
     }
 
     @Test
     public void testFingerprintUnlock_UpdatesScrims() {
         // Simulate unlocking from AoD with fingerprint.
-        when(mFingerprintUnlockController.getMode())
-                .thenReturn(FingerprintUnlockController.MODE_WAKE_AND_UNLOCK);
+        when(mBiometricUnlockController.getMode())
+                .thenReturn(BiometricUnlockController.MODE_WAKE_AND_UNLOCK);
         mStatusBar.updateScrimController();
         verify(mScrimController).transitionTo(eq(ScrimState.UNLOCKED), any());
     }
@@ -717,7 +717,7 @@
                 VisualStabilityManager visualStabilityManager,
                 NotificationViewHierarchyManager viewHierarchyManager,
                 TestableNotificationEntryManager entryManager, ScrimController scrimController,
-                FingerprintUnlockController fingerprintUnlockController,
+                BiometricUnlockController biometricUnlockController,
                 ActivityLaunchAnimator launchAnimator, KeyguardViewMediator keyguardViewMediator,
                 NotificationRemoteInputManager notificationRemoteInputManager,
                 NotificationGroupManager notificationGroupManager,
@@ -743,7 +743,7 @@
             mViewHierarchyManager = viewHierarchyManager;
             mEntryManager = entryManager;
             mScrimController = scrimController;
-            mFingerprintUnlockController = fingerprintUnlockController;
+            mBiometricUnlockController = biometricUnlockController;
             mActivityLaunchAnimator = launchAnimator;
             mKeyguardViewMediator = keyguardViewMediator;
             mClearAllEnabled = true;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/ChannelsTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/ChannelsTest.java
index 50b4f3f..0d398be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/ChannelsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/ChannelsTest.java
@@ -103,7 +103,7 @@
                 NotificationManager.IMPORTANCE_MIN);
         NotificationChannel newChannel =
                 NotificationChannels.createScreenshotChannel("newName", legacyChannel);
-        assertEquals(Uri.EMPTY, newChannel.getSound());
+        assertEquals(null, newChannel.getSound());
         assertEquals("newName", newChannel.getName());
         // MIN importance not user locked, so HIGH wins out.
         assertEquals(NotificationManager.IMPORTANCE_HIGH, newChannel.getImportance());
@@ -113,7 +113,7 @@
     public void testInheritFromLegacy_noLegacyExists() {
         NotificationChannel newChannel =
                 NotificationChannels.createScreenshotChannel("newName", null);
-        assertEquals(Uri.EMPTY, newChannel.getSound());
+        assertEquals(null, newChannel.getSound());
         assertEquals("newName", newChannel.getName());
         assertEquals(NotificationManager.IMPORTANCE_HIGH, newChannel.getImportance());
     }
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index a9a14ca..fba639c 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -212,6 +212,10 @@
     // Package: android
     NOTE_AUTO_SAVER_SUGGESTION = 49;
 
+    // Notify the user that their softap config preference has changed.
+    // Package: android
+    NOTE_SOFTAP_CONFIG_CHANGED = 50;
+
     // ADD_NEW_IDS_ABOVE_THIS_LINE
     // Legacy IDs with arbitrary values appear below
     // Legacy IDs existed as stable non-conflicting constants prior to the O release
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 10b721e..7503ec2 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -1639,6 +1639,8 @@
      * @param markFrozenIfConfigChanged Whether to set {@link ActivityRecord#frozenBeforeDestroy} to
      *                                  {@code true} if config changed.
      * @param deferResume Whether to defer resume while updating config.
+     * @return 'true' if starting activity was kept or wasn't provided, 'false' if it was relaunched
+     *         because of configuration update.
      */
     boolean ensureVisibilityAndConfig(ActivityRecord starting, int displayId,
             boolean markFrozenIfConfigChanged, boolean deferResume) {
@@ -1649,6 +1651,11 @@
         ensureActivitiesVisibleLocked(null /* starting */, 0 /* configChanges */,
                 false /* preserveWindows */, false /* notifyClients */);
 
+        if (displayId == INVALID_DISPLAY) {
+            // The caller didn't provide a valid display id, skip updating config.
+            return true;
+        }
+
         // Force-update the orientation from the WindowManager, since we need the true configuration
         // to send to the client now.
         final Configuration config = mWindowManager.updateOrientationFromAppTokens(
diff --git a/services/core/java/com/android/server/biometrics/face/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/face/AuthenticationClient.java
new file mode 100644
index 0000000..f9e8793
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/face/AuthenticationClient.java
@@ -0,0 +1,169 @@
+/**
+ * Copyright (C) 2018 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 com.android.server.biometrics.face;
+
+import android.hardware.biometrics.face.V1_0.IBiometricsFace;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
+import android.content.Context;
+import android.hardware.face.Face;
+import android.hardware.face.FaceManager;
+import android.hardware.face.IFaceServiceReceiver;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+/**
+ * A class to keep track of the authentication state for a given client.
+ */
+public abstract class AuthenticationClient extends ClientMonitor {
+    private long mOpId;
+
+    public abstract int handleFailedAttempt();
+    public abstract void resetFailedAttempts();
+
+    public static final int LOCKOUT_NONE = 0;
+    public static final int LOCKOUT_TIMED = 1;
+    public static final int LOCKOUT_PERMANENT = 2;
+
+    public AuthenticationClient(Context context, long halDeviceId, IBinder token,
+            IFaceServiceReceiver receiver, int targetUserId, long opId,
+            boolean restricted, String owner) {
+        super(context, halDeviceId, token, receiver, targetUserId, restricted, owner);
+        mOpId = opId;
+    }
+
+    @Override
+    public boolean onAuthenticated(int faceId) {
+        boolean result = false;
+        boolean authenticated = faceId != 0;
+
+        IFaceServiceReceiver receiver = getReceiver();
+        if (receiver != null) {
+            try {
+                MetricsLogger.action(getContext(), MetricsEvent.ACTION_FINGERPRINT_AUTH,
+                        authenticated); // TODO: Define ACTION_FACE_AUTH constant and use here
+                if (!authenticated) {
+                    receiver.onAuthenticationFailed(getHalDeviceId());
+                } else {
+                    if (DEBUG) {
+                        Slog.v(TAG, "onAuthenticated(owner=" + getOwnerString());
+                    }
+                    Face face = !getIsRestricted()
+                            ? new Face("" /* TODO */, faceId, getHalDeviceId())
+                            : null;
+                    receiver.onAuthenticationSucceeded(getHalDeviceId(), face);
+                }
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Failed to notify Authenticated:", e);
+                result = true; // client failed
+            }
+        } else {
+            result = true; // client not listening
+        }
+        if (!authenticated) {
+            // allow system-defined limit of number of attempts before giving up
+            int lockoutMode =  handleFailedAttempt();
+            if (lockoutMode != LOCKOUT_NONE) {
+                try {
+                    Slog.w(TAG, "Forcing lockout (face authentication driver code should do " +
+                            "this!), mode(" + lockoutMode + ")");
+                    stop(false);
+                    int errorCode = lockoutMode == LOCKOUT_TIMED ?
+                            FaceManager.FACE_ERROR_LOCKOUT :
+                            FaceManager.FACE_ERROR_LOCKOUT_PERMANENT;
+                    receiver.onError(getHalDeviceId(), errorCode, 0 /* vendorCode */);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Failed to notify lockout:", e);
+                }
+            }
+            result |= lockoutMode != LOCKOUT_NONE; // in a lockout mode
+        } else {
+            if (receiver != null) {
+                vibrateSuccess();
+            }
+            result |= true; // we have a valid face, done
+            resetFailedAttempts();
+        }
+        return result;
+    }
+
+    /**
+     * Start authentication
+     */
+    @Override
+    public int start() {
+        IBiometricsFace daemon = getFaceDaemon();
+        if (daemon == null) {
+            Slog.w(TAG, "start authentication: no face HAL!");
+            return ERROR_ESRCH;
+        }
+        try {
+            final int result = daemon.authenticate(mOpId);
+            if (result != 0) {
+                Slog.w(TAG, "startAuthentication failed, result=" + result);
+                MetricsLogger.histogram(getContext(), "faced_auth_start_error", result);
+                onError(FaceManager.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
+                return result;
+            }
+            if (DEBUG) Slog.w(TAG, "client " + getOwnerString() + " is authenticating...");
+        } catch (RemoteException e) {
+            Slog.e(TAG, "startAuthentication failed", e);
+            return ERROR_ESRCH;
+        }
+        return 0; // success
+    }
+
+    @Override
+    public int stop(boolean initiatedByClient) {
+        if (mAlreadyCancelled) {
+            Slog.w(TAG, "stopAuthentication: already cancelled!");
+            return 0;
+        }
+        IBiometricsFace daemon = getFaceDaemon();
+        if (daemon == null) {
+            Slog.w(TAG, "stopAuthentication: no face HAL!");
+            return ERROR_ESRCH;
+        }
+        try {
+            final int result = daemon.cancel();
+            if (result != 0) {
+                Slog.w(TAG, "stopAuthentication failed, result=" + result);
+                return result;
+            }
+            if (DEBUG) Slog.w(TAG, "client " + getOwnerString() + " is no longer authenticating");
+        } catch (RemoteException e) {
+            Slog.e(TAG, "stopAuthentication failed", e);
+            return ERROR_ESRCH;
+        }
+        mAlreadyCancelled = true;
+        return 0; // success
+    }
+
+    @Override
+    public boolean onEnrollResult(int faceId, int remaining) {
+        if (DEBUG) Slog.w(TAG, "onEnrollResult() called for authenticate!");
+        return true; // Invalid for Authenticate
+    }
+
+    @Override
+    public boolean onRemoved(int faceId, int remaining) {
+        if (DEBUG) Slog.w(TAG, "onRemoved() called for authenticate!");
+        return true; // Invalid for Authenticate
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/face/ClientMonitor.java b/services/core/java/com/android/server/biometrics/face/ClientMonitor.java
new file mode 100644
index 0000000..4b13db9
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/face/ClientMonitor.java
@@ -0,0 +1,243 @@
+/**
+ * Copyright (C) 2018 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 com.android.server.biometrics.face;
+
+import android.Manifest;
+import android.content.Context;
+import android.hardware.biometrics.face.V1_0.IBiometricsFace;
+import android.hardware.face.FaceManager;
+import android.hardware.face.IFaceServiceReceiver;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.util.Slog;
+
+import java.util.NoSuchElementException;
+
+/**
+ * Abstract base class for keeping track and dispatching events from face HAL to the
+ * current client.  Subclasses are responsible for coordinating the interaction with
+ * face HAL for the specific action (e.g. authenticate, enroll, enumerate, etc.).
+ */
+public abstract class ClientMonitor implements IBinder.DeathRecipient {
+    protected static final String TAG = FaceService.TAG; // TODO: get specific name
+    protected static final int ERROR_ESRCH = 3; // Likely face HAL is dead. See errno.h.
+    protected static final boolean DEBUG = FaceService.DEBUG;
+    private static final long[] DEFAULT_SUCCESS_VIBRATION_PATTERN = new long[] {0, 30};
+    private final Context mContext;
+    private final long mHalDeviceId;
+    private final int mTargetUserId;
+    // True if client does not have MANAGE_FACE_AUTHENTICATION permission
+    private final boolean mIsRestricted;
+    private final String mOwner;
+    private final VibrationEffect mSuccessVibrationEffect;
+    private IBinder mToken;
+    private IFaceServiceReceiver mReceiver;
+    protected boolean mAlreadyCancelled;
+
+    /**
+     * @param context context of FaceService
+     * @param halDeviceId the HAL device ID of the associated face authentication hardware
+     * @param token a unique token for the client
+     * @param receiver recipient of related events (e.g. authentication)
+     * @param userId target user id for operation
+     * @param restricted whether or not client has the {@link Manifest#MANAGE_FACE}
+     * permission
+     * @param owner name of the client that owns this
+     */
+    public ClientMonitor(Context context, long halDeviceId, IBinder token,
+            IFaceServiceReceiver receiver, int userId, boolean restricted,
+            String owner) {
+        mContext = context;
+        mHalDeviceId = halDeviceId;
+        mToken = token;
+        mReceiver = receiver;
+        mTargetUserId = userId;
+        mIsRestricted = restricted;
+        mOwner = owner;
+        mSuccessVibrationEffect = getSuccessVibrationEffect(context);
+        try {
+            if (token != null) {
+                token.linkToDeath(this, 0);
+            }
+        } catch (RemoteException e) {
+            Slog.w(TAG, "caught remote exception in linkToDeath: ", e);
+        }
+    }
+
+    /**
+     * Contacts face HAL to start the client.
+     * @return 0 on success, errno from driver on failure
+     */
+    public abstract int start();
+
+    /**
+     * Contacts face HAL to stop the client.
+     * @param initiatedByClient whether the operation is at the request of a client
+     */
+    public abstract int stop(boolean initiatedByClient);
+
+    /**
+     * Method to explicitly poke powermanager on events
+     */
+    public abstract void notifyUserActivity();
+
+    /**
+     * Gets the face daemon from the cached state in the container class.
+     */
+    public abstract IBiometricsFace getFaceDaemon();
+
+    // Event callbacks from driver. Inappropriate calls is flagged/logged by the
+    // respective client (e.g. enrolling shouldn't get authenticate events).
+    // All of these return 'true' if the operation is completed and it's ok to move
+    // to the next client (e.g. authentication accepts or rejects a face).
+    public abstract boolean onEnrollResult(int faceId, int remaining);
+    public abstract boolean onAuthenticated(int faceId);
+    public abstract boolean onRemoved(int faceId, int remaining);
+
+    /**
+     * Called when we get notification from face HAL that an image has been acquired.
+     * Common to authenticate and enroll.
+     * @param acquiredInfo info about the current image acquisition
+     * @return true if client should be removed
+     */
+    public boolean onAcquired(int acquiredInfo, int vendorCode) {
+        if (mReceiver == null)
+            return true; // client not connected
+        try {
+            mReceiver.onAcquired(getHalDeviceId(), acquiredInfo, vendorCode);
+            return false; // acquisition continues...
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Failed to invoke sendAcquired:", e);
+            return true; // client failed
+        } finally {
+            // Good scans will keep the device awake
+            if (acquiredInfo == FaceManager.FACE_ACQUIRED_GOOD) {
+                notifyUserActivity();
+            }
+        }
+    }
+
+    /**
+     * Called when we get notification from face HAL that an error has occurred with the
+     * current operation. Common to authenticate, enroll, enumerate and remove.
+     * @param error
+     * @return true if client should be removed
+     */
+    public boolean onError(int error, int vendorCode) {
+        if (mReceiver != null) {
+            try {
+                mReceiver.onError(getHalDeviceId(), error, vendorCode);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Failed to invoke sendError:", e);
+            }
+        }
+        return true; // errors always remove current client
+    }
+
+    public void destroy() {
+        if (mToken != null) {
+            try {
+                mToken.unlinkToDeath(this, 0);
+            } catch (NoSuchElementException e) {
+                // TODO: remove when duplicate call bug is found
+                Slog.e(TAG, "destroy(): " + this + ":", new Exception("here"));
+            }
+            mToken = null;
+        }
+        mReceiver = null;
+    }
+
+    @Override
+    public void binderDied() {
+        mToken = null;
+        mReceiver = null;
+        onError(FaceManager.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            if (mToken != null) {
+                if (DEBUG) Slog.w(TAG, "removing leaked reference: " + mToken);
+                onError(FaceManager.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
+            }
+        } finally {
+            super.finalize();
+        }
+    }
+
+    public final Context getContext() {
+        return mContext;
+    }
+
+    public final long getHalDeviceId() {
+        return mHalDeviceId;
+    }
+
+    public final String getOwnerString() {
+        return mOwner;
+    }
+
+    public final IFaceServiceReceiver getReceiver() {
+        return mReceiver;
+    }
+
+    public final boolean getIsRestricted() {
+        return mIsRestricted;
+    }
+
+    public final int getTargetUserId() {
+        return mTargetUserId;
+    }
+
+    public final IBinder getToken() {
+        return mToken;
+    }
+
+    public final void vibrateSuccess() {
+        Vibrator vibrator = mContext.getSystemService(Vibrator.class);
+        if (vibrator != null) {
+            vibrator.vibrate(mSuccessVibrationEffect);
+        }
+    }
+
+//    public final void vibrateError() {
+//    }
+
+    private static VibrationEffect getSuccessVibrationEffect(Context ctx) {
+        int[] arr = ctx.getResources().getIntArray(
+                com.android.internal.R.array.config_longPressVibePattern);
+        final long[] vibePattern;
+        if (arr == null || arr.length == 0) {
+            vibePattern = DEFAULT_SUCCESS_VIBRATION_PATTERN;
+        } else {
+            vibePattern = new long[arr.length];
+            for (int i = 0; i < arr.length; i++) {
+                vibePattern[i] = arr[i];
+            }
+        }
+        if (vibePattern.length == 1) {
+            return VibrationEffect.createOneShot(
+                    vibePattern[0], VibrationEffect.DEFAULT_AMPLITUDE);
+        } else {
+            return VibrationEffect.createWaveform(vibePattern, -1);
+        }
+    }
+
+}
diff --git a/services/core/java/com/android/server/biometrics/face/EnrollClient.java b/services/core/java/com/android/server/biometrics/face/EnrollClient.java
new file mode 100644
index 0000000..91e489a
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/face/EnrollClient.java
@@ -0,0 +1,142 @@
+/**
+ * Copyright (C) 2018 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 com.android.server.biometrics.face;
+
+import android.content.Context;
+import android.hardware.biometrics.face.V1_0.IBiometricsFace;
+import android.hardware.face.FaceManager;
+import android.hardware.face.IFaceServiceReceiver;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * A class to keep track of the enrollment state for a given client.
+ */
+public abstract class EnrollClient extends ClientMonitor {
+    private static final long MS_PER_SEC = 1000;
+    private static final int ENROLLMENT_TIMEOUT_MS = 60 * 1000; // 1 minute
+    private byte[] mCryptoToken;
+
+    public EnrollClient(Context context, long halDeviceId, IBinder token,
+            IFaceServiceReceiver receiver, int userId, byte [] cryptoToken, boolean restricted,
+            String owner) {
+        super(context, halDeviceId, token, receiver, userId, restricted, owner);
+        mCryptoToken = Arrays.copyOf(cryptoToken, cryptoToken.length);
+    }
+
+    @Override
+    public boolean onEnrollResult(int faceId, int remaining) {
+        if (remaining == 0) {
+            FaceUtils.getInstance().addFaceForUser(getContext(), faceId, getTargetUserId());
+        }
+        return sendEnrollResult(faceId, remaining);
+    }
+
+    /*
+     * @return true if we're done.
+     */
+    private boolean sendEnrollResult(int faceId, int remaining) {
+        IFaceServiceReceiver receiver = getReceiver();
+        if (receiver == null)
+            return true; // client not listening
+
+        vibrateSuccess();
+        MetricsLogger.action(getContext(), MetricsEvent.ACTION_FINGERPRINT_ENROLL);
+        // TODO: create ACTION_FACE_ENROLL constant and use it here
+        try {
+            receiver.onEnrollResult(getHalDeviceId(), faceId, remaining);
+            return remaining == 0;
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Failed to notify EnrollResult:", e);
+            return true;
+        }
+    }
+
+    @Override
+    public int start() {
+        IBiometricsFace daemon = getFaceDaemon();
+        if (daemon == null) {
+            Slog.w(TAG, "enroll: no face HAL!");
+            return ERROR_ESRCH;
+        }
+        final int timeout = (int) (ENROLLMENT_TIMEOUT_MS / MS_PER_SEC);
+        try {
+            // ugh...
+            final ArrayList<Byte> token = new ArrayList<>();
+            for (int i = 0; i < mCryptoToken.length; i++) {
+                token.add(mCryptoToken[i]);
+            }
+            final int result = daemon.enroll(token, timeout);
+            if (result != 0) {
+                Slog.w(TAG, "startEnroll failed, result=" + result);
+                MetricsLogger.histogram(getContext(), "faced_enroll_start_error", result);
+                onError(FaceManager.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
+                return result;
+            }
+        } catch (RemoteException e) {
+            Slog.e(TAG, "startEnroll failed", e);
+        }
+        return 0; // success
+    }
+
+    @Override
+    public int stop(boolean initiatedByClient) {
+        if (mAlreadyCancelled) {
+            Slog.w(TAG, "stopEnroll: already cancelled!");
+            return 0;
+        }
+        IBiometricsFace daemon = getFaceDaemon();
+        if (daemon == null) {
+            Slog.w(TAG, "stopEnrollment: no face HAL!");
+            return ERROR_ESRCH;
+        }
+        try {
+            final int result = daemon.cancel();
+            if (result != 0) {
+                Slog.w(TAG, "startEnrollCancel failed, result = " + result);
+                return result;
+            }
+        } catch (RemoteException e) {
+            Slog.e(TAG, "stopEnrollment failed", e);
+        }
+        if (initiatedByClient) {
+            onError(FaceManager.FACE_ERROR_CANCELED, 0 /* vendorCode */);
+        }
+        mAlreadyCancelled = true;
+        return 0;
+    }
+
+    @Override
+    public boolean onRemoved(int faceId, int remaining) {
+        if (DEBUG) Slog.w(TAG, "onRemoved() called for enroll!");
+        return true; // Invalid for EnrollClient
+    }
+
+    @Override
+    public boolean onAuthenticated(int faceId) {
+        if (DEBUG) Slog.w(TAG, "onAuthenticated() called for enroll!");
+        return true; // Invalid for EnrollClient
+    }
+
+}
diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java
new file mode 100644
index 0000000..a9a5ad0
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/face/FaceService.java
@@ -0,0 +1,1254 @@
+/*
+ * Copyright (C) 2018 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 com.android.server.biometrics.face;
+
+import static android.Manifest.permission.INTERACT_ACROSS_USERS;
+import static android.Manifest.permission.MANAGE_FACE;
+import static android.Manifest.permission.RESET_FACE_LOCKOUT;
+import static android.Manifest.permission.USE_BIOMETRIC;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
+
+import android.app.ActivityManager;
+import android.app.ActivityManager.RunningAppProcessInfo;
+import android.app.AlarmManager;
+import android.app.AppOpsManager;
+import android.app.PendingIntent;
+import android.app.SynchronousUserSwitchObserver;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.hardware.biometrics.face.V1_0.IBiometricsFace;
+import android.hardware.biometrics.face.V1_0.IBiometricsFaceClientCallback;
+import android.hardware.face.Face;
+import android.hardware.face.FaceManager;
+import android.hardware.face.IFaceService;
+import android.hardware.face.IFaceServiceLockoutResetCallback;
+import android.hardware.face.IFaceServiceReceiver;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.DeadObjectException;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IHwBinder;
+import android.os.IRemoteCallback;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.os.RemoteException;
+import android.os.SELinux;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.security.KeyStore;
+import android.service.face.FaceActionStatsProto;
+import android.service.face.FaceServiceDumpProto;
+import android.service.face.FaceUserStatsProto;
+import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.util.DumpUtils;
+import com.android.server.SystemServerInitThreadPool;
+import com.android.server.SystemService;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A service to manage multiple clients that want to access the face HAL API.
+ * The service is responsible for maintaining a list of clients and dispatching all
+ * face -related events.
+ *
+ * @hide
+ */
+public class FaceService extends SystemService implements IHwBinder.DeathRecipient {
+    static final String TAG = "FaceService";
+    static final boolean DEBUG = true;
+    private static final String FACE_DATA_DIR = "facedata";
+    private static final int MSG_USER_SWITCHING = 10;
+    private static final String ACTION_LOCKOUT_RESET =
+            "com.android.server.biometrics.face.ACTION_LOCKOUT_RESET";
+
+    private class PerformanceStats {
+        int accept; // number of accepted faces
+        int reject; // number of rejected faces
+        int acquire; // total number of acquisitions. Should be >= accept+reject due to poor image
+                     // acquisition in some cases (too high, too low, poor gaze, etc.)
+        int lockout; // total number of lockouts
+        int permanentLockout; // total number of permanent lockouts
+    }
+
+    private final ArrayList<FaceServiceLockoutResetMonitor> mLockoutMonitors =
+            new ArrayList<>();
+    private final Map<Integer, Long> mAuthenticatorIds =
+            Collections.synchronizedMap(new HashMap<>());
+    private final AppOpsManager mAppOps;
+    private static final long FAIL_LOCKOUT_TIMEOUT_MS = 30*1000;
+    private static final int MAX_FAILED_ATTEMPTS_LOCKOUT_TIMED = 5;
+    private static final int MAX_FAILED_ATTEMPTS_LOCKOUT_PERMANENT = 20;
+
+    private static final long CANCEL_TIMEOUT_LIMIT_MS = 3000; // max wait for onCancel() from HAL,in ms
+    private final String mKeyguardPackage;
+    private int mCurrentUserId = UserHandle.USER_NULL;
+    private final FaceUtils mFaceUtils = FaceUtils.getInstance();
+    private Context mContext;
+    private long mHalDeviceId;
+    private boolean mTimedLockoutCleared;
+    private int mFailedAttempts;
+    @GuardedBy("this")
+    private IBiometricsFace mDaemon;
+    private final PowerManager mPowerManager;
+    private final AlarmManager mAlarmManager;
+    private final UserManager mUserManager;
+    private ClientMonitor mCurrentClient;
+    private ClientMonitor mPendingClient;
+    private PerformanceStats mPerformanceStats;
+
+
+    private IBinder mToken = new Binder(); // used for internal FaceService enumeration
+
+    private class UserFace {
+        Face f;
+        int userId;
+        public UserFace(Face f, int userId) {
+            this.f = f;
+            this.userId = userId;
+        }
+    }
+
+    // Normal face authentications are tracked by mPerformanceMap.
+    private HashMap<Integer, PerformanceStats> mPerformanceMap = new HashMap<>();
+
+    // Transactions that make use of CryptoObjects are tracked by mCryptoPerformaceMap.
+    private HashMap<Integer, PerformanceStats> mCryptoPerformanceMap = new HashMap<>();
+
+    private Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(android.os.Message msg) {
+            switch (msg.what) {
+                case MSG_USER_SWITCHING:
+                    handleUserSwitching(msg.arg1);
+                    break;
+
+                default:
+                    Slog.w(TAG, "Unknown message:" + msg.what);
+            }
+        }
+    };
+
+    private final BroadcastReceiver mLockoutReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (ACTION_LOCKOUT_RESET.equals(intent.getAction())) {
+                resetFailedAttempts(false /* clearAttemptCounter */);
+            }
+        }
+    };
+
+    private final Runnable mResetFailedAttemptsRunnable = new Runnable() {
+        @Override
+        public void run() {
+            resetFailedAttempts(true /* clearAttemptCounter */);
+        }
+    };
+
+    private final Runnable mResetClientState = new Runnable() {
+        @Override
+        public void run() {
+            // Warning: if we get here, the driver never confirmed our call to cancel the current
+            // operation (authenticate, enroll, remove, enumerate, etc), which is
+            // really bad.  The result will be a 3-second delay in starting each new client.
+            // If you see this on a device, make certain the driver notifies with
+            // {@link FaceAuthenticationManager#FACE_ERROR_CANCEL} in response to cancel()
+            // once it has successfully switched to the IDLE state in the face HAL.
+            // Additionally,{@link FaceAuthenticationManager#FACE_ERROR_CANCEL} should only be sent
+            // in response to an actual cancel() call.
+            Slog.w(TAG, "Client "
+                    + (mCurrentClient != null ? mCurrentClient.getOwnerString() : "null")
+                    + " failed to respond to cancel, starting client "
+                    + (mPendingClient != null ? mPendingClient.getOwnerString() : "null"));
+
+            mCurrentClient = null;
+            startClient(mPendingClient, false);
+        }
+    };
+
+    public FaceService(Context context) {
+        super(context);
+        mContext = context;
+        mKeyguardPackage = ComponentName.unflattenFromString(context.getResources().getString(
+                com.android.internal.R.string.config_keyguardComponent)).getPackageName();
+        mAppOps = context.getSystemService(AppOpsManager.class);
+        mPowerManager = mContext.getSystemService(PowerManager.class);
+        mAlarmManager = mContext.getSystemService(AlarmManager.class);
+        mContext.registerReceiver(mLockoutReceiver, new IntentFilter(ACTION_LOCKOUT_RESET),
+                RESET_FACE_LOCKOUT, null /* handler */);
+        mUserManager = UserManager.get(mContext);
+    }
+
+    @Override
+    public void serviceDied(long cookie) {
+        Slog.v(TAG, "face HAL died");
+        MetricsLogger.count(mContext, "faced_died", 1);
+        handleError(mHalDeviceId, FaceManager.FACE_ERROR_HW_UNAVAILABLE,
+                0 /*vendorCode */);
+    }
+
+    public synchronized IBiometricsFace getFaceDaemon() {
+        if (mDaemon == null) {
+            Slog.v(TAG, "mDaemon was null, reconnect to face");
+            try {
+                mDaemon = IBiometricsFace.getService();
+            } catch (java.util.NoSuchElementException e) {
+                // Service doesn't exist or cannot be opened. Logged below.
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to get biometric interface", e);
+            }
+            if (mDaemon == null) {
+                Slog.w(TAG, "face HIDL not available");
+                return null;
+            }
+
+            mDaemon.asBinder().linkToDeath(this, 0);
+
+            try {
+                mHalDeviceId = mDaemon.setCallback(mDaemonCallback).value;
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to open face HAL", e);
+                mDaemon = null; // try again later!
+            }
+
+            if (DEBUG) Slog.v(TAG, "face HAL id: " + mHalDeviceId);
+            if (mHalDeviceId != 0) {
+                loadAuthenticatorIds();
+                updateActiveGroup(ActivityManager.getCurrentUser(), null);
+            } else {
+                Slog.w(TAG, "Failed to open Face HAL!");
+                MetricsLogger.count(mContext, "faced_openhal_error", 1);
+                mDaemon = null;
+            }
+        }
+        return mDaemon;
+    }
+
+    /** Populates existing authenticator ids. To be used only during the start of the service. */
+    private void loadAuthenticatorIds() {
+        // This operation can be expensive, so keep track of the elapsed time. Might need to move to
+        // background if it takes too long.
+        long t = System.currentTimeMillis();
+        mAuthenticatorIds.clear();
+        for (UserInfo user : UserManager.get(mContext).getUsers(true /* excludeDying */)) {
+            int userId = getUserOrWorkProfileId(null, user.id);
+            if (!mAuthenticatorIds.containsKey(userId)) {
+                updateActiveGroup(userId, null);
+            }
+        }
+
+        t = System.currentTimeMillis() - t;
+        if (t > 1000) {
+            Slog.w(TAG, "loadAuthenticatorIds() taking too long: " + t + "ms");
+        }
+    }
+
+    protected void handleError(long deviceId, int error, int vendorCode) {
+        ClientMonitor client = mCurrentClient;
+        if (client != null && client.onError(error, vendorCode)) {
+            removeClient(client);
+        }
+
+        if (DEBUG) Slog.v(TAG, "handleError(client="
+                + (client != null ? client.getOwnerString() : "null") + ", error = " + error + ")");
+        // This is the magic code that starts the next client when the old client finishes.
+        if (error == FaceManager.FACE_ERROR_CANCELED) {
+            mHandler.removeCallbacks(mResetClientState);
+            if (mPendingClient != null) {
+                if (DEBUG) Slog.v(TAG, "start pending client " + mPendingClient.getOwnerString());
+                startClient(mPendingClient, false);
+                mPendingClient = null;
+            }
+        } else if (error == FaceManager.FACE_ERROR_HW_UNAVAILABLE) {
+            // If we get HW_UNAVAILABLE, try to connect again later...
+            Slog.w(TAG, "Got ERROR_HW_UNAVAILABLE; try reconnecting next client.");
+            synchronized (this) {
+                mDaemon = null;
+                mHalDeviceId = 0;
+                mCurrentUserId = UserHandle.USER_NULL;
+            }
+        }
+    }
+
+    protected void handleRemoved(long deviceId, int faceId, int userId, int remaining) {
+        if (DEBUG) Slog.w(TAG, "Removed: fid=" + faceId
+                + ", uid=" + userId
+                + ", dev=" + deviceId
+                + ", rem=" + remaining);
+
+        ClientMonitor client = mCurrentClient;
+        if (client != null && client.onRemoved(faceId, remaining)) {
+            removeClient(client);
+        }
+    }
+
+    protected void handleAuthenticated(long deviceId, int faceId, int userId,
+            ArrayList<Byte> token) {
+        ClientMonitor client = mCurrentClient;
+        if (faceId != 0) {
+            // Ugh...
+            final byte[] byteToken = new byte[token.size()];
+            for (int i = 0; i < token.size(); i++) {
+                byteToken[i] = token.get(i);
+            }
+            // Send to Keystore
+            KeyStore.getInstance().addAuthToken(byteToken);
+        }
+        if (client != null && client.onAuthenticated(faceId)) {
+            removeClient(client);
+        }
+        if (faceId != 0) {
+            mPerformanceStats.accept++;
+        } else {
+            mPerformanceStats.reject++;
+        }
+    }
+
+    protected void handleAcquired(long deviceId, int acquiredInfo, int vendorCode) {
+        ClientMonitor client = mCurrentClient;
+        if (client != null && client.onAcquired(acquiredInfo, vendorCode)) {
+            removeClient(client);
+        }
+        if (mPerformanceStats != null && getLockoutMode() == AuthenticationClient.LOCKOUT_NONE
+                && client instanceof AuthenticationClient) {
+            // ignore enrollment acquisitions or acquisitions when we're locked out
+            mPerformanceStats.acquire++;
+        }
+    }
+
+    protected void handleEnrollResult(long deviceId, int faceId, int userId, int remaining) {
+        ClientMonitor client = mCurrentClient;
+        if (client != null && client.onEnrollResult(faceId, remaining)) {
+            removeClient(client);
+        }
+    }
+
+    private void userActivity() {
+        long now = SystemClock.uptimeMillis();
+        mPowerManager.userActivity(now, PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0);
+    }
+
+    void handleUserSwitching(int userId) {
+        updateActiveGroup(userId, null);
+    }
+
+    private void removeClient(ClientMonitor client) {
+        if (client != null) {
+            client.destroy();
+            if (client != mCurrentClient && mCurrentClient != null) {
+                Slog.w(TAG, "Unexpected client: " + client.getOwnerString() + "expected: "
+                        + mCurrentClient != null ? mCurrentClient.getOwnerString() : "null");
+            }
+        }
+        if (mCurrentClient != null) {
+            if (DEBUG) Slog.v(TAG, "Done with client: " + client.getOwnerString());
+            mCurrentClient = null;
+        }
+    }
+
+    private int getLockoutMode() {
+        if (mFailedAttempts >= MAX_FAILED_ATTEMPTS_LOCKOUT_PERMANENT) {
+            return AuthenticationClient.LOCKOUT_PERMANENT;
+        } else if (mFailedAttempts > 0 && mTimedLockoutCleared == false &&
+                (mFailedAttempts % MAX_FAILED_ATTEMPTS_LOCKOUT_TIMED == 0)) {
+            return AuthenticationClient.LOCKOUT_TIMED;
+        }
+        return AuthenticationClient.LOCKOUT_NONE;
+    }
+
+    private void scheduleLockoutReset() {
+        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                SystemClock.elapsedRealtime() + FAIL_LOCKOUT_TIMEOUT_MS, getLockoutResetIntent());
+    }
+
+    private void cancelLockoutReset() {
+        mAlarmManager.cancel(getLockoutResetIntent());
+    }
+
+    private PendingIntent getLockoutResetIntent() {
+        return PendingIntent.getBroadcast(mContext, 0,
+                new Intent(ACTION_LOCKOUT_RESET), PendingIntent.FLAG_UPDATE_CURRENT);
+    }
+
+    public long startPreEnroll(IBinder token) {
+        IBiometricsFace daemon = getFaceDaemon();
+        if (daemon == null) {
+            Slog.w(TAG, "startPreEnroll: no face HAL!");
+            return 0;
+        }
+        try {
+            return daemon.preEnroll().value;
+        } catch (RemoteException e) {
+            Slog.e(TAG, "startPreEnroll failed", e);
+        }
+        return 0;
+    }
+
+    public int startPostEnroll(IBinder token) {
+        IBiometricsFace daemon = getFaceDaemon();
+        if (daemon == null) {
+            Slog.w(TAG, "startPostEnroll: no face HAL!");
+            return 0;
+        }
+        try {
+            return daemon.postEnroll();
+        } catch (RemoteException e) {
+            Slog.e(TAG, "startPostEnroll failed", e);
+        }
+        return 0;
+    }
+
+    /**
+     * Calls face HAL to switch states to the new task. If there's already a current task,
+     * it calls cancel() and sets mPendingClient to begin when the current task finishes
+     * ({@link FaceManager#FACE_ERROR_CANCELED}).
+     * @param newClient the new client that wants to connect
+     * @param initiatedByClient true for authenticate, remove and enroll
+     */
+    private void startClient(ClientMonitor newClient, boolean initiatedByClient) {
+        ClientMonitor currentClient = mCurrentClient;
+        if (currentClient != null) {
+            if (DEBUG) Slog.v(TAG, "request stop current client " + currentClient.getOwnerString());
+            if (currentClient instanceof InternalRemovalClient) {
+                // This condition means we're currently running internal diagnostics to
+                // remove a face in the hardware and/or the software
+                // TODO: design an escape hatch in case client never finishes
+            }
+            else {
+                currentClient.stop(initiatedByClient);
+            }
+            mPendingClient = newClient;
+            mHandler.removeCallbacks(mResetClientState);
+            mHandler.postDelayed(mResetClientState, CANCEL_TIMEOUT_LIMIT_MS);
+        } else if (newClient != null) {
+            mCurrentClient = newClient;
+            if (DEBUG) Slog.v(TAG, "starting client "
+                    + newClient.getClass().getSuperclass().getSimpleName()
+                    + "(" + newClient.getOwnerString() + ")"
+                    + ", initiatedByClient = " + initiatedByClient + ")");
+            newClient.start();
+        }
+    }
+
+    void startRemove(IBinder token, int userId, IFaceServiceReceiver receiver, boolean restricted,
+                     boolean internal) {
+        IBiometricsFace daemon = getFaceDaemon();
+        if (daemon == null) {
+            Slog.w(TAG, "startRemove: no face HAL!");
+            return;
+        }
+
+        if (internal) {
+            Context context = getContext();
+            InternalRemovalClient client = new InternalRemovalClient(context, mHalDeviceId,
+                    token, receiver, userId, restricted, context.getOpPackageName()) {
+                @Override
+                public void notifyUserActivity() {
+
+                }
+                @Override
+                public IBiometricsFace getFaceDaemon() {
+                    return FaceService.this.getFaceDaemon();
+                }
+            };
+            startClient(client, true);
+        }
+        else {
+            RemovalClient client = new RemovalClient(getContext(), mHalDeviceId, token,
+                    receiver, userId, restricted, token.toString()) {
+                @Override
+                public void notifyUserActivity() {
+                    FaceService.this.userActivity();
+                }
+
+                @Override
+                public IBiometricsFace getFaceDaemon() {
+                    return FaceService.this.getFaceDaemon();
+                }
+            };
+            startClient(client, true);
+        }
+    }
+
+    /*
+     * @hide
+     */
+    public Face getEnrolledFace(int userId) {
+        return mFaceUtils.getFaceForUser(mContext, userId);
+    }
+
+    public boolean hasEnrolledFace(int userId) {
+        if (userId != UserHandle.getCallingUserId()) {
+            checkPermission(INTERACT_ACROSS_USERS);
+        }
+        return mFaceUtils.getFaceForUser(mContext, userId) != null;
+    }
+
+    boolean hasPermission(String permission) {
+        return getContext().checkCallingOrSelfPermission(permission)
+                == PackageManager.PERMISSION_GRANTED;
+    }
+
+    void checkPermission(String permission) {
+        getContext().enforceCallingOrSelfPermission(permission,
+                "Must have " + permission + " permission.");
+    }
+
+    int getEffectiveUserId(int userId) {
+        UserManager um = UserManager.get(mContext);
+        if (um != null) {
+            final long callingIdentity = Binder.clearCallingIdentity();
+            userId = um.getCredentialOwnerProfile(userId);
+            Binder.restoreCallingIdentity(callingIdentity);
+        } else {
+            Slog.e(TAG, "Unable to acquire UserManager");
+        }
+        return userId;
+    }
+
+    boolean isCurrentUserOrProfile(int userId) {
+        UserManager um = UserManager.get(mContext);
+        if (um == null) {
+            Slog.e(TAG, "Unable to acquire UserManager");
+            return false;
+        }
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            // Allow current user or profiles of the current user...
+            for (int profileId : um.getEnabledProfileIds(ActivityManager.getCurrentUser())) {
+                if (profileId == userId) {
+                    return true;
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+
+        return false;
+    }
+
+    private boolean isForegroundActivity(int uid, int pid) {
+        try {
+            List<RunningAppProcessInfo> procs =
+                    ActivityManager.getService().getRunningAppProcesses();
+            int N = procs.size();
+            for (int i = 0; i < N; i++) {
+                RunningAppProcessInfo proc = procs.get(i);
+                if (proc.pid == pid && proc.uid == uid
+                        && proc.importance == IMPORTANCE_FOREGROUND) {
+                    return true;
+                }
+            }
+        } catch (RemoteException e) {
+            Slog.w(TAG, "am.getRunningAppProcesses() failed");
+        }
+        return false;
+    }
+
+    /**
+     * @param opPackageName name of package for caller
+     * @param requireForeground only allow this call while app is in the foreground
+     * @return true if caller can use face API
+     */
+    private boolean canUseFace(String opPackageName, boolean requireForeground, int uid,
+            int pid, int userId) {
+        checkPermission(USE_BIOMETRIC);
+        if (isKeyguard(opPackageName)) {
+            return true; // Keyguard is always allowed
+        }
+        if (!isCurrentUserOrProfile(userId)) {
+            Slog.w(TAG,"Rejecting " + opPackageName + " ; not a current user or profile");
+            return false;
+        }
+        if (mAppOps.noteOp(AppOpsManager.OP_USE_FACE, uid, opPackageName)
+                != AppOpsManager.MODE_ALLOWED) {
+            Slog.w(TAG, "Rejecting " + opPackageName + " ; permission denied");
+            return false;
+        }
+        if (requireForeground && !(isForegroundActivity(uid, pid) || currentClient(opPackageName))){
+            Slog.w(TAG, "Rejecting " + opPackageName + " ; not in foreground");
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * @param opPackageName package of the caller
+     * @return true if this is the same client currently using face
+     */
+    private boolean currentClient(String opPackageName) {
+        return mCurrentClient != null && mCurrentClient.getOwnerString().equals(opPackageName);
+    }
+
+    /**
+     * @param clientPackage
+     * @return true if this is keyguard package
+     */
+    private boolean isKeyguard(String clientPackage) {
+        return mKeyguardPackage.equals(clientPackage);
+    }
+
+    private void addLockoutResetMonitor(FaceServiceLockoutResetMonitor monitor) {
+        if (!mLockoutMonitors.contains(monitor)) {
+            mLockoutMonitors.add(monitor);
+        }
+    }
+
+    private void removeLockoutResetCallback(
+            FaceServiceLockoutResetMonitor monitor) {
+        mLockoutMonitors.remove(monitor);
+    }
+
+    private void notifyLockoutResetMonitors() {
+        for (int i = 0; i < mLockoutMonitors.size(); i++) {
+            mLockoutMonitors.get(i).sendLockoutReset();
+        }
+    }
+
+    private void startAuthentication(IBinder token, long opId, int callingUserId,
+                IFaceServiceReceiver receiver, int flags, boolean restricted,
+                String opPackageName) {
+        updateActiveGroup(callingUserId, opPackageName);
+
+        if (DEBUG) Slog.v(TAG, "startAuthentication(" + opPackageName + ")");
+
+        AuthenticationClient client = new AuthenticationClient(getContext(), mHalDeviceId, token,
+                receiver, mCurrentUserId, opId, restricted, opPackageName) {
+            @Override
+            public int handleFailedAttempt() {
+                mFailedAttempts++;
+                mTimedLockoutCleared = false;
+                final int lockoutMode = getLockoutMode();
+                if (lockoutMode == AuthenticationClient.LOCKOUT_PERMANENT) {
+                    mPerformanceStats.permanentLockout++;
+                } else if (lockoutMode == AuthenticationClient.LOCKOUT_TIMED) {
+                    mPerformanceStats.lockout++;
+                }
+
+                // Failing multiple times will continue to push out the lockout time
+                if (lockoutMode != AuthenticationClient.LOCKOUT_NONE) {
+                    scheduleLockoutReset();
+                    return lockoutMode;
+                }
+                return AuthenticationClient.LOCKOUT_NONE;
+            }
+
+            @Override
+            public void resetFailedAttempts() {
+                FaceService.this.resetFailedAttempts(true /* clearAttemptCounter */);
+            }
+
+            @Override
+            public void notifyUserActivity() {
+                FaceService.this.userActivity();
+            }
+
+            @Override
+            public IBiometricsFace getFaceDaemon() {
+                return FaceService.this.getFaceDaemon();
+            }
+        };
+
+        int lockoutMode = getLockoutMode();
+        if (lockoutMode != AuthenticationClient.LOCKOUT_NONE) {
+            Slog.v(TAG, "In lockout mode(" + lockoutMode +
+                    ") ; disallowing authentication");
+            int errorCode = lockoutMode == AuthenticationClient.LOCKOUT_TIMED ?
+                    FaceManager.FACE_ERROR_LOCKOUT :
+                    FaceManager.FACE_ERROR_LOCKOUT_PERMANENT;
+            if (!client.onError(errorCode, 0 /* vendorCode */)) {
+                Slog.w(TAG, "Cannot send permanent lockout message to client");
+            }
+            return;
+        }
+        startClient(client, true /* initiatedByClient */);
+    }
+
+    private void startEnrollment(IBinder token, byte [] cryptoToken, int userId,
+            IFaceServiceReceiver receiver, int flags, boolean restricted,
+            String opPackageName) {
+        updateActiveGroup(userId, opPackageName);
+
+        EnrollClient client = new EnrollClient(getContext(), mHalDeviceId, token, receiver,
+                userId, cryptoToken, restricted, opPackageName) {
+
+            @Override
+            public IBiometricsFace getFaceDaemon() {
+                return FaceService.this.getFaceDaemon();
+            }
+
+            @Override
+            public void notifyUserActivity() {
+                FaceService.this.userActivity();
+            }
+        };
+        startClient(client, true /* initiatedByClient */);
+    }
+
+    // attempt counter should only be cleared when Keyguard goes away or when
+    // a face is successfully authenticated
+    protected void resetFailedAttempts(boolean clearAttemptCounter) {
+        if (DEBUG && getLockoutMode() != AuthenticationClient.LOCKOUT_NONE) {
+            Slog.v(TAG, "Reset face lockout, clearAttemptCounter=" + clearAttemptCounter);
+        }
+        if (clearAttemptCounter) {
+            mFailedAttempts = 0;
+        }
+        mTimedLockoutCleared = true;
+        // If we're asked to reset failed attempts externally (i.e. from Keyguard),
+        // the alarm might still be pending; remove it.
+        cancelLockoutReset();
+        notifyLockoutResetMonitors();
+    }
+
+    private class FaceServiceLockoutResetMonitor {
+
+        private static final long WAKELOCK_TIMEOUT_MS = 2000;
+        private final IFaceServiceLockoutResetCallback mCallback;
+        private final WakeLock mWakeLock;
+
+        public FaceServiceLockoutResetMonitor(
+                IFaceServiceLockoutResetCallback callback) {
+            mCallback = callback;
+            mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+                    "lockout reset callback");
+        }
+
+        public void sendLockoutReset() {
+            if (mCallback != null) {
+                try {
+                    mWakeLock.acquire(WAKELOCK_TIMEOUT_MS);
+                    mCallback.onLockoutReset(mHalDeviceId, new IRemoteCallback.Stub() {
+
+                        @Override
+                        public void sendResult(Bundle data) throws RemoteException {
+                            if (mWakeLock.isHeld()) {
+                                mWakeLock.release();
+                            }
+                        }
+                    });
+                } catch (DeadObjectException e) {
+                    Slog.w(TAG, "Death object while invoking onLockoutReset: ", e);
+                    mHandler.post(mRemoveCallbackRunnable);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Failed to invoke onLockoutReset: ", e);
+                }
+            }
+        }
+
+        private final Runnable mRemoveCallbackRunnable = new Runnable() {
+            @Override
+            public void run() {
+                if (mWakeLock.isHeld()) {
+                    mWakeLock.release();
+                }
+                removeLockoutResetCallback(FaceServiceLockoutResetMonitor.this);
+            }
+        };
+    }
+
+    private IBiometricsFaceClientCallback mDaemonCallback =
+            new IBiometricsFaceClientCallback.Stub() {
+
+        @Override
+        public void onEnrollResult(final long deviceId, int faceId, int userId, int remaining) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    handleEnrollResult(deviceId, faceId, userId, remaining);
+                }
+            });
+        }
+
+        @Override
+        public void onAcquired(final long deviceId, final int userId, final int acquiredInfo,
+                final int vendorCode) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    handleAcquired(deviceId, acquiredInfo, vendorCode);
+                }
+            });
+        }
+
+        @Override
+        public void onAuthenticated(final long deviceId, final int faceId, final int userId,
+                ArrayList<Byte> token) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    handleAuthenticated(deviceId, faceId, userId, token);
+                }
+            });
+        }
+
+        @Override
+        public void onError(final long deviceId, final int userId, final int error,
+                final int vendorCode) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    handleError(deviceId, error, vendorCode);
+                }
+            });
+        }
+
+        @Override
+        public void onRemoved(final long deviceId, final int faceId, final int userId,
+                final int remaining) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    handleRemoved(deviceId, faceId, userId, remaining);
+                }
+            });
+        }
+
+        @Override
+        public void onEnumerate(long deviceId, ArrayList<Integer> faceIds, int userId)
+                throws RemoteException {
+
+        }
+    };
+
+    private final class FaceServiceWrapper extends IFaceService.Stub {
+        @Override // Binder call
+        public long preEnroll(IBinder token) {
+            checkPermission(MANAGE_FACE);
+            return startPreEnroll(token);
+        }
+
+        @Override // Binder call
+        public int postEnroll(IBinder token) {
+            checkPermission(MANAGE_FACE);
+            return startPostEnroll(token);
+        }
+
+        @Override // Binder call
+        public void enroll(final IBinder token, final byte[] cryptoToken, final int userId,
+                final IFaceServiceReceiver receiver, final int flags,
+                final String opPackageName) {
+            checkPermission(MANAGE_FACE);
+
+            Face enrolledFace = FaceService.this.getEnrolledFace(userId);
+
+            if (enrolledFace != null) {
+                Slog.w(TAG, "Multiple faces enrollment is not supported");
+                return;
+            }
+
+
+            final boolean restricted = isRestricted();
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    startEnrollment(token, cryptoToken, userId, receiver, flags,
+                            restricted, opPackageName);
+                }
+            });
+        }
+
+        private boolean isRestricted() {
+            // Only give privileged apps (like Settings) access to faces info
+            final boolean restricted = !hasPermission(MANAGE_FACE);
+            return restricted;
+        }
+
+        @Override // Binder call
+        public void cancelEnrollment(final IBinder token) {
+            checkPermission(MANAGE_FACE);
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    ClientMonitor client = mCurrentClient;
+                    if (client instanceof EnrollClient && client.getToken() == token) {
+                        client.stop(client.getToken() == token);
+                    }
+                }
+            });
+        }
+
+        @Override // Binder call
+        public void authenticate(final IBinder token, final long opId,
+                                 final IFaceServiceReceiver receiver, final int flags,
+                                 final String opPackageName) {
+            final int callingUid = Binder.getCallingUid();
+            final int callingUserId = UserHandle.getCallingUserId();
+            final int pid = Binder.getCallingPid();
+            final boolean restricted = isRestricted();
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    if (!canUseFace(opPackageName, true /* foregroundOnly */,
+                            callingUid, pid, callingUserId)) {
+                        if (DEBUG) Slog.v(TAG, "authenticate(): reject " + opPackageName);
+                        return;
+                    }
+
+                    MetricsLogger.histogram(mContext, "faces_token", opId != 0L ? 1 : 0);
+
+                    // Get performance stats object for this user.
+                    HashMap<Integer, PerformanceStats> pmap
+                            = (opId == 0) ? mPerformanceMap : mCryptoPerformanceMap;
+                    PerformanceStats stats = pmap.get(mCurrentUserId);
+                    if (stats == null) {
+                        stats = new PerformanceStats();
+                        pmap.put(mCurrentUserId, stats);
+                    }
+                    mPerformanceStats = stats;
+
+                    startAuthentication(token, opId, callingUserId, receiver,
+                            flags, restricted, opPackageName);
+                }
+            });
+        }
+
+        @Override // Binder call
+        public void cancelAuthentication(final IBinder token, final String opPackageName) {
+            final int uid = Binder.getCallingUid();
+            final int pid = Binder.getCallingPid();
+            final int callingUserId = UserHandle.getCallingUserId();
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    if (!canUseFace(opPackageName, true /* foregroundOnly */, uid, pid,
+                            callingUserId)) {
+                        if (DEBUG) Slog.v(TAG, "cancelAuthentication(): reject " + opPackageName);
+                    } else {
+                        ClientMonitor client = mCurrentClient;
+                        if (client instanceof AuthenticationClient) {
+                            if (client.getToken() == token) {
+                                if (DEBUG) Slog.v(TAG, "stop client " + client.getOwnerString());
+                                client.stop(client.getToken() == token);
+                            } else {
+                                if (DEBUG) Slog.v(TAG, "can't stop client "
+                                        + client.getOwnerString() + " since tokens don't match");
+                            }
+                        } else if (client != null) {
+                            if (DEBUG) Slog.v(TAG, "can't cancel non-authenticating client "
+                                    + client.getOwnerString());
+                        }
+                    }
+                }
+            });
+        }
+
+        @Override // Binder call
+        public void setActiveUser(final int userId) {
+            checkPermission(MANAGE_FACE);
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    updateActiveGroup(userId, null);
+                }
+            });
+        }
+
+        @Override // Binder call
+        public void remove(final IBinder token, final int userId,
+                           final IFaceServiceReceiver receiver) {
+            checkPermission(MANAGE_FACE); // TODO: Maybe have another permission
+            final boolean restricted = isRestricted();
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    startRemove(token, userId, receiver, restricted, false /* internal */);
+                }
+            });
+        }
+
+        @Override // Binder call
+        public boolean isHardwareDetected(long deviceId, String opPackageName) {
+            if (!canUseFace(opPackageName, false /* foregroundOnly */,
+                    Binder.getCallingUid(), Binder.getCallingPid(),
+                    UserHandle.getCallingUserId())) {
+                return false;
+            }
+
+            final long token = Binder.clearCallingIdentity();
+            try {
+                IBiometricsFace daemon = getFaceDaemon();
+                return daemon != null && mHalDeviceId != 0;
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override // Binder call
+        public Face getEnrolledFace(int userId, String opPackageName) {
+            if (!canUseFace(opPackageName, false /* foregroundOnly */,
+                    Binder.getCallingUid(), Binder.getCallingPid(),
+                    UserHandle.getCallingUserId())) {
+                return null;
+            }
+
+            return FaceService.this.getEnrolledFace(userId);
+        }
+
+        @Override // Binder call
+        public boolean hasEnrolledFace(int userId, String opPackageName) {
+            if (!canUseFace(opPackageName, false /* foregroundOnly */,
+                    Binder.getCallingUid(), Binder.getCallingPid(),
+                    UserHandle.getCallingUserId())) {
+                return false;
+            }
+
+            return FaceService.this.hasEnrolledFace(userId);
+        }
+
+        @Override // Binder call
+        public long getAuthenticatorId(String opPackageName) {
+            // In this method, we're not checking whether the caller is permitted to use face
+            // API because current authenticator ID is leaked (in a more contrived way) via Android
+            // Keystore (android.security.keystore package): the user of that API can create a key
+            // which requires face authentication for its use, and then query the key's
+            // characteristics (hidden API) which returns, among other things, face
+            // authenticator ID which was active at key creation time.
+            //
+            // Reason: The part of Android Keystore which runs inside an app's process invokes this
+            // method in certain cases. Those cases are not always where the developer demonstrates
+            // explicit intent to use face functionality. Thus, to avoiding throwing an
+            // unexpected SecurityException this method does not check whether its caller is
+            // permitted to use face API.
+            //
+            // The permission check should be restored once Android Keystore no longer invokes this
+            // method from inside app processes.
+
+            return FaceService.this.getAuthenticatorId(opPackageName);
+        }
+
+        @Override // Binder call
+        protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                if (args.length > 0 && "--proto".equals(args[0])) {
+                    dumpProto(fd);
+                } else {
+                    dumpInternal(pw);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        @Override // Binder call
+        public void resetTimeout(byte [] token) {
+            checkPermission(RESET_FACE_LOCKOUT);
+            // TODO: confirm security token when we move timeout management into the HAL layer.
+            mHandler.post(mResetFailedAttemptsRunnable);
+        }
+
+        @Override
+        public void addLockoutResetCallback(final IFaceServiceLockoutResetCallback callback)
+                throws RemoteException {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    addLockoutResetMonitor(
+                            new FaceServiceLockoutResetMonitor(callback));
+                }
+            });
+        }
+    }
+
+    private void dumpInternal(PrintWriter pw) {
+        JSONObject dump = new JSONObject();
+        try {
+            dump.put("service", "Face Manager");
+
+            JSONArray sets = new JSONArray();
+            for (UserInfo user : UserManager.get(getContext()).getUsers()) {
+                final int userId = user.getUserHandle().getIdentifier();
+                PerformanceStats stats = mPerformanceMap.get(userId);
+                PerformanceStats cryptoStats = mCryptoPerformanceMap.get(userId);
+                JSONObject set = new JSONObject();
+                set.put("id", userId);
+                set.put("accept", (stats != null) ? stats.accept : 0);
+                set.put("reject", (stats != null) ? stats.reject : 0);
+                set.put("acquire", (stats != null) ? stats.acquire : 0);
+                set.put("lockout", (stats != null) ? stats.lockout : 0);
+                set.put("permanentLockout", (stats != null) ? stats.permanentLockout : 0);
+                // cryptoStats measures statistics about secure face transactions
+                // (e.g. to unlock password storage, make secure purchases, etc.)
+                set.put("acceptCrypto", (cryptoStats != null) ? cryptoStats.accept : 0);
+                set.put("rejectCrypto", (cryptoStats != null) ? cryptoStats.reject : 0);
+                set.put("acquireCrypto", (cryptoStats != null) ? cryptoStats.acquire : 0);
+                set.put("lockoutCrypto", (cryptoStats != null) ? cryptoStats.lockout : 0);
+                sets.put(set);
+            }
+
+            dump.put("prints", sets);
+        } catch (JSONException e) {
+            Slog.e(TAG, "dump formatting failure", e);
+        }
+        pw.println(dump);
+    }
+
+    private void dumpProto(FileDescriptor fd) {
+        final ProtoOutputStream proto = new ProtoOutputStream(fd);
+        for (UserInfo user : UserManager.get(getContext()).getUsers()) {
+            final int userId = user.getUserHandle().getIdentifier();
+
+            final long userToken = proto.start(FaceServiceDumpProto.USERS);
+
+            proto.write(FaceUserStatsProto.USER_ID, userId);
+
+            // Normal face authentications (e.g. lockscreen)
+            final PerformanceStats normal = mPerformanceMap.get(userId);
+            if (normal != null) {
+                final long countsToken = proto.start(FaceUserStatsProto.NORMAL);
+                proto.write(FaceActionStatsProto.ACCEPT, normal.accept);
+                proto.write(FaceActionStatsProto.REJECT, normal.reject);
+                proto.write(FaceActionStatsProto.ACQUIRE, normal.acquire);
+                proto.write(FaceActionStatsProto.LOCKOUT, normal.lockout);
+                proto.write(FaceActionStatsProto.LOCKOUT_PERMANENT, normal.lockout);
+                proto.end(countsToken);
+            }
+
+            // Statistics about secure face transactions (e.g. to unlock password
+            // storage, make secure purchases, etc.)
+            final PerformanceStats crypto = mCryptoPerformanceMap.get(userId);
+            if (crypto != null) {
+                final long countsToken = proto.start(FaceUserStatsProto.CRYPTO);
+                proto.write(FaceActionStatsProto.ACCEPT, crypto.accept);
+                proto.write(FaceActionStatsProto.REJECT, crypto.reject);
+                proto.write(FaceActionStatsProto.ACQUIRE, crypto.acquire);
+                proto.write(FaceActionStatsProto.LOCKOUT, crypto.lockout);
+                proto.write(FaceActionStatsProto.LOCKOUT_PERMANENT, crypto.lockout);
+                proto.end(countsToken);
+            }
+
+            proto.end(userToken);
+        }
+        proto.flush();
+    }
+
+    @Override
+    public void onStart() {
+        publishBinderService(Context.FACE_SERVICE, new FaceServiceWrapper());
+        SystemServerInitThreadPool.get().submit(this::getFaceDaemon, TAG + ".onStart");
+        listenForUserSwitches();
+    }
+
+    private void updateActiveGroup(int userId, String clientPackage) {
+        IBiometricsFace daemon = getFaceDaemon();
+
+        if (daemon != null) {
+            try {
+                userId = getUserOrWorkProfileId(clientPackage, userId);
+                if (userId != mCurrentUserId) {
+                    final File systemDir = Environment.getUserSystemDirectory(userId);
+                    final File faceDir = new File(systemDir, FACE_DATA_DIR);
+                    if (!faceDir.exists()) {
+                        if (!faceDir.mkdir()) {
+                            Slog.v(TAG, "Cannot make directory: " + faceDir.getAbsolutePath());
+                            return;
+                        }
+                        // Calling mkdir() from this process will create a directory with our
+                        // permissions (inherited from the containing dir). This command fixes
+                        // the label.
+                        if (!SELinux.restorecon(faceDir)) {
+                            Slog.w(TAG, "Restorecons failed. Directory will have wrong label.");
+                            return;
+                        }
+                    }
+                    daemon.setActiveUser(userId, faceDir.getAbsolutePath());
+                    mCurrentUserId = userId;
+                }
+                mAuthenticatorIds.put(userId,
+                        hasEnrolledFace(userId) ? daemon.getAuthenticatorId().value : 0L);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to setActiveUser():", e);
+            }
+        }
+    }
+
+    /**
+     * @param clientPackage the package of the caller
+     * @return the profile id
+     */
+    private int getUserOrWorkProfileId(String clientPackage, int userId) {
+        if (!isKeyguard(clientPackage) && isWorkProfile(userId)) {
+            return userId;
+        }
+        return getEffectiveUserId(userId);
+    }
+
+    /**
+     * @param userId
+     * @return true if this is a work profile
+     */
+    private boolean isWorkProfile(int userId) {
+        UserInfo userInfo = null;
+        final long token = Binder.clearCallingIdentity();
+        try {
+            userInfo = mUserManager.getUserInfo(userId);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        return userInfo != null && userInfo.isManagedProfile();
+    }
+
+    private void listenForUserSwitches() {
+        try {
+            ActivityManager.getService().registerUserSwitchObserver(
+                new SynchronousUserSwitchObserver() {
+                    @Override
+                    public void onUserSwitching(int newUserId) throws RemoteException {
+                        mHandler.obtainMessage(MSG_USER_SWITCHING, newUserId, 0 /* unused */)
+                                .sendToTarget();
+                    }
+                }, TAG);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Failed to listen for user switching event" ,e);
+        }
+    }
+
+    /***
+     * @param opPackageName the name of the calling package
+     * @return authenticator id for the calling user
+     */
+    public long getAuthenticatorId(String opPackageName) {
+        final int userId = getUserOrWorkProfileId(opPackageName, UserHandle.getCallingUserId());
+        return mAuthenticatorIds.getOrDefault(userId, 0L);
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/face/FaceUserState.java b/services/core/java/com/android/server/biometrics/face/FaceUserState.java
new file mode 100644
index 0000000..45b842d
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/face/FaceUserState.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2018 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 com.android.server.biometrics.face;
+
+import android.content.Context;
+import android.hardware.face.Face;
+import android.os.AsyncTask;
+import android.os.Environment;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.annotations.GuardedBy;
+
+import libcore.io.IoUtils;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+
+/**
+ * Class managing the set of faces per user across device reboots.
+ */
+class FaceUserState {
+
+    private static final String TAG = "FaceState";
+    private static final String FACE_FILE = "settings_face.xml";
+
+    private static final String TAG_FACE = "face";
+    private static final String ATTR_DEVICE_ID = "deviceId";
+
+    private final File mFile;
+
+    @GuardedBy("this")
+    private Face mFace = null;
+    private final Context mCtx;
+
+    public FaceUserState(Context ctx, int userId) {
+        mFile = getFileForUser(userId);
+        mCtx = ctx;
+        synchronized (this) {
+            readStateSyncLocked();
+        }
+    }
+
+    public void addFace(int faceId) {
+        synchronized (this) {
+            mFace = new Face("Face", faceId, 0);
+            scheduleWriteStateLocked();
+        }
+    }
+
+    public void removeFace() {
+        synchronized (this) {
+            mFace = null;
+            scheduleWriteStateLocked();
+        }
+    }
+
+    public Face getFace() {
+        synchronized (this) {
+            return getCopy(mFace);
+        }
+    }
+
+    private static File getFileForUser(int userId) {
+        return new File(Environment.getUserSystemDirectory(userId), FACE_FILE);
+    }
+
+    private final Runnable mWriteStateRunnable = new Runnable() {
+        @Override
+        public void run() {
+            doWriteState();
+        }
+    };
+
+    private void scheduleWriteStateLocked() {
+        AsyncTask.execute(mWriteStateRunnable);
+    }
+
+    private Face getCopy(Face f) {
+        if (f == null) {
+            return null;
+        }
+        return new Face(f.getName(), f.getFaceId(), f.getDeviceId());
+    }
+
+    private void doWriteState() {
+        AtomicFile destination = new AtomicFile(mFile);
+
+        Face face;
+
+        synchronized (this) {
+            face = getCopy(mFace);
+        }
+
+        FileOutputStream out = null;
+        try {
+            out = destination.startWrite();
+
+            XmlSerializer serializer = Xml.newSerializer();
+            serializer.setOutput(out, "utf-8");
+            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+            serializer.startDocument(null, true);
+            serializer.startTag(null, TAG_FACE);
+            if (face != null) {
+                serializer.attribute(null, ATTR_DEVICE_ID, Long.toString(face.getDeviceId()));
+            }
+            serializer.endTag(null, TAG_FACE);
+            serializer.endDocument();
+            destination.finishWrite(out);
+
+            // Any error while writing is fatal.
+        } catch (Throwable t) {
+            Slog.wtf(TAG, "Failed to write settings, restoring backup", t);
+            destination.failWrite(out);
+            throw new IllegalStateException("Failed to write face", t);
+        } finally {
+            IoUtils.closeQuietly(out);
+        }
+    }
+
+    private void readStateSyncLocked() {
+        FileInputStream in;
+        if (!mFile.exists()) {
+            return;
+        }
+        try {
+            in = new FileInputStream(mFile);
+        } catch (FileNotFoundException fnfe) {
+            Slog.i(TAG, "No face state");
+            return;
+        }
+        try {
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(in, null);
+            parseStateLocked(parser);
+
+        } catch (XmlPullParserException | IOException e) {
+            throw new IllegalStateException("Failed parsing settings file: "
+                    + mFile , e);
+        } finally {
+            IoUtils.closeQuietly(in);
+        }
+    }
+
+    private void parseStateLocked(XmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        final int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if (tagName.equals(TAG_FACE)) {
+                parseFaceLocked(parser);
+            }
+        }
+    }
+
+    private void parseFaceLocked(XmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        String deviceId = parser.getAttributeValue(null, ATTR_DEVICE_ID);
+
+        mFace = new Face("", 0, Integer.parseInt(deviceId));
+    }
+
+}
diff --git a/services/core/java/com/android/server/biometrics/face/FaceUtils.java b/services/core/java/com/android/server/biometrics/face/FaceUtils.java
new file mode 100644
index 0000000..c4a8499
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/face/FaceUtils.java
@@ -0,0 +1,71 @@
+/**
+ * Copyright (C) 2018 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 com.android.server.biometrics.face;
+
+import android.content.Context;
+import android.hardware.face.Face;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * Utility class for dealing with faces and face settings.
+ */
+public class FaceUtils {
+
+    private static final Object sInstanceLock = new Object();
+    private static FaceUtils sInstance;
+
+    @GuardedBy("this")
+    private final SparseArray<FaceUserState> mUsers = new SparseArray<>();
+
+    public static FaceUtils getInstance() {
+        synchronized (sInstanceLock) {
+            if (sInstance == null) {
+                sInstance = new FaceUtils();
+            }
+        }
+        return sInstance;
+    }
+
+    private FaceUtils() {
+    }
+
+    public Face getFaceForUser(Context ctx, int userId) {
+        return getStateForUser(ctx, userId).getFace();
+    }
+
+    public void addFaceForUser(Context ctx, int faceId, int userId) {
+        getStateForUser(ctx, userId).addFace(faceId);
+    }
+
+    public void removeFaceForUser(Context ctx, int userId) {
+        getStateForUser(ctx, userId).removeFace();
+    }
+
+    private FaceUserState getStateForUser(Context ctx, int userId) {
+        synchronized (this) {
+            FaceUserState state = mUsers.get(userId);
+            if (state == null) {
+                state = new FaceUserState(ctx, userId);
+                mUsers.put(userId, state);
+            }
+            return state;
+        }
+    }
+}
+
diff --git a/services/core/java/com/android/server/biometrics/face/InternalRemovalClient.java b/services/core/java/com/android/server/biometrics/face/InternalRemovalClient.java
new file mode 100644
index 0000000..634bf8b
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/face/InternalRemovalClient.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2018 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 com.android.server.biometrics.face;
+
+import android.content.Context;
+import android.os.IBinder;
+import android.hardware.face.IFaceServiceReceiver;
+import com.android.server.biometrics.face.RemovalClient;
+
+public abstract class InternalRemovalClient extends RemovalClient {
+
+    public InternalRemovalClient(Context context, long halDeviceId, IBinder token,
+            IFaceServiceReceiver receiver, int userId, boolean restricted, String owner) {
+
+        super(context, halDeviceId, token, receiver, userId, restricted, owner);
+
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/face/RemovalClient.java b/services/core/java/com/android/server/biometrics/face/RemovalClient.java
new file mode 100644
index 0000000..ccea9d5
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/face/RemovalClient.java
@@ -0,0 +1,115 @@
+/**
+ * Copyright (C) 2018 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 com.android.server.biometrics.face;
+
+import android.content.Context;
+import android.hardware.biometrics.face.V1_0.IBiometricsFace;
+import android.hardware.face.FaceManager;
+import android.hardware.face.IFaceServiceReceiver;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+import com.android.internal.logging.MetricsLogger;
+
+/**
+ * A class to keep track of the remove state for a given client.
+ */
+public abstract class RemovalClient extends ClientMonitor {
+
+    public RemovalClient(Context context, long halDeviceId, IBinder token,
+            IFaceServiceReceiver receiver, int userId,
+            boolean restricted, String owner) {
+        super(context, halDeviceId, token, receiver, userId, restricted, owner);
+    }
+
+    @Override
+    public int start() {
+        IBiometricsFace daemon = getFaceDaemon();
+        // The face template ids will be removed when we get confirmation from the HAL
+        try {
+            final int result = daemon.remove(getTargetUserId());
+            if (result != 0) {
+                Slog.w(TAG, "startRemove failed, result=" + result);
+                MetricsLogger.histogram(getContext(), "faced_remove_start_error", result);
+                onError(FaceManager.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
+                return result;
+            }
+        } catch (RemoteException e) {
+            Slog.e(TAG, "startRemove failed", e);
+        }
+        return 0;
+    }
+
+    @Override
+    public int stop(boolean initiatedByClient) {
+        if (mAlreadyCancelled) {
+            Slog.w(TAG, "stopRemove: already cancelled!");
+            return 0;
+        }
+        IBiometricsFace daemon = getFaceDaemon();
+        if (daemon == null) {
+            Slog.w(TAG, "stopRemoval: no face HAL!");
+            return ERROR_ESRCH;
+        }
+        try {
+            final int result = daemon.cancel();
+            if (result != 0) {
+                Slog.w(TAG, "stopRemoval failed, result=" + result);
+                return result;
+            }
+            if (DEBUG) Slog.w(TAG, "client " + getOwnerString() + " is no longer removing");
+        } catch (RemoteException e) {
+            Slog.e(TAG, "stopRemoval failed", e);
+            return ERROR_ESRCH;
+        }
+        mAlreadyCancelled = true;
+        return 0; // success
+    }
+
+    /*
+     * @return true if we're done.
+     */
+    private boolean sendRemoved(int faceId, int remaining) {
+        IFaceServiceReceiver receiver = getReceiver();
+        try {
+            if (receiver != null) {
+                receiver.onRemoved(getHalDeviceId(), faceId, remaining);
+            }
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Failed to notify Removed:", e);
+        }
+        return true;
+    }
+
+    @Override
+    public boolean onRemoved(int faceId, int remaining) {
+        FaceUtils.getInstance().removeFaceForUser(getContext(), getTargetUserId());
+        return sendRemoved(faceId, remaining);
+    }
+
+    @Override
+    public boolean onEnrollResult(int faceId, int remaining) {
+        if (DEBUG) Slog.w(TAG, "onEnrollResult() called for remove!");
+        return true; // Invalid for Remove
+    }
+
+    @Override
+    public boolean onAuthenticated(int faceId) {
+        if (DEBUG) Slog.w(TAG, "onAuthenticated() called for remove!");
+        return true; // Invalid for Remove.
+    }
+}
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 4413666..8a135b8 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -20,6 +20,7 @@
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.admin.DevicePolicyManager;
+import android.hardware.biometrics.BiometricSourceType;
 import android.app.trust.ITrustListener;
 import android.app.trust.ITrustManager;
 import android.content.BroadcastReceiver;
@@ -130,8 +131,8 @@
     private final SparseBooleanArray mTrustUsuallyManagedForUser = new SparseBooleanArray();
 
     // set to true only if user can skip bouncer
-    @GuardedBy("mUsersUnlockedByFingerprint")
-    private final SparseBooleanArray mUsersUnlockedByFingerprint = new SparseBooleanArray();
+    @GuardedBy("mUsersUnlockedByBiometric")
+    private final SparseBooleanArray mUsersUnlockedByBiometric = new SparseBooleanArray();
 
     private final StrongAuthTracker mStrongAuthTracker;
 
@@ -440,11 +441,11 @@
             boolean secure = mLockPatternUtils.isSecure(id);
             boolean trusted = aggregateIsTrusted(id);
             boolean showingKeyguard = true;
-            boolean fingerprintAuthenticated = false;
+            boolean biometricAuthenticated = false;
 
             if (mCurrentUser == id) {
-                synchronized(mUsersUnlockedByFingerprint) {
-                    fingerprintAuthenticated = mUsersUnlockedByFingerprint.get(id, false);
+                synchronized(mUsersUnlockedByBiometric) {
+                    biometricAuthenticated = mUsersUnlockedByBiometric.get(id, false);
                 }
                 try {
                     showingKeyguard = wm.isKeyguardLocked();
@@ -452,7 +453,7 @@
                 }
             }
             boolean deviceLocked = secure && showingKeyguard && !trusted &&
-                    !fingerprintAuthenticated;
+                    !biometricAuthenticated;
             setDeviceLockedForUser(id, deviceLocked);
         }
     }
@@ -1021,20 +1022,20 @@
         }
 
         @Override
-        public void unlockedByFingerprintForUser(int userId) {
+        public void unlockedByBiometricForUser(int userId, BiometricSourceType biometricSource) {
             enforceReportPermission();
-            synchronized(mUsersUnlockedByFingerprint) {
-                mUsersUnlockedByFingerprint.put(userId, true);
+            synchronized(mUsersUnlockedByBiometric) {
+                mUsersUnlockedByBiometric.put(userId, true);
             }
             mHandler.obtainMessage(MSG_REFRESH_DEVICE_LOCKED_FOR_USER, userId,
                     0 /* arg2 */).sendToTarget();
         }
 
         @Override
-        public void clearAllFingerprints() {
+        public void clearAllBiometricRecognized(BiometricSourceType biometricSource) {
             enforceReportPermission();
-            synchronized(mUsersUnlockedByFingerprint) {
-                mUsersUnlockedByFingerprint.clear();
+            synchronized(mUsersUnlockedByBiometric) {
+                mUsersUnlockedByBiometric.clear();
             }
             mHandler.obtainMessage(MSG_REFRESH_DEVICE_LOCKED_FOR_USER, UserHandle.USER_ALL,
                     0 /* arg2 */).sendToTarget();
@@ -1188,8 +1189,8 @@
                     synchronized (mTrustUsuallyManagedForUser) {
                         mTrustUsuallyManagedForUser.delete(userId);
                     }
-                    synchronized (mUsersUnlockedByFingerprint) {
-                        mUsersUnlockedByFingerprint.delete(userId);
+                    synchronized (mUsersUnlockedByBiometric) {
+                        mUsersUnlockedByBiometric.delete(userId);
                     }
                     refreshAgentList(userId);
                     refreshDeviceLockedForUser(userId);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 20147d2..9ef806e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -6259,7 +6259,7 @@
     }
 
     @Override
-    public void reportFailedFingerprintAttempt(int userHandle) {
+    public void reportFailedBiometricAttempt(int userHandle) {
         enforceFullCrossUsersPermission(userHandle);
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.BIND_DEVICE_ADMIN, null);
@@ -6270,7 +6270,7 @@
     }
 
     @Override
-    public void reportSuccessfulFingerprintAttempt(int userHandle) {
+    public void reportSuccessfulBiometricAttempt(int userHandle) {
         enforceFullCrossUsersPermission(userHandle);
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.BIND_DEVICE_ADMIN, null);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 2985fec..c3a8d6c 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -75,6 +75,7 @@
 import com.android.server.display.DisplayManagerService;
 import com.android.server.dreams.DreamManagerService;
 import com.android.server.emergency.EmergencyAffordanceService;
+import com.android.server.biometrics.face.FaceService;
 import com.android.server.biometrics.fingerprint.FingerprintService;
 import com.android.server.hdmi.HdmiControlService;
 import com.android.server.input.InputManagerService;
@@ -1513,6 +1514,12 @@
             }
             traceEnd();
 
+            if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)) {
+                traceBeginAndSlog("StartFaceSensor");
+                mSystemServiceManager.startService(FaceService.class);
+                traceEnd();
+            }
+
             if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
                 traceBeginAndSlog("StartFingerprintSensor");
                 mSystemServiceManager.startService(FingerprintService.class);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelTest.java
index 2241047..ca473c6 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelTest.java
@@ -19,20 +19,26 @@
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
 
 import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNull;
 
 import android.app.NotificationChannel;
+import android.net.Uri;
 import android.os.Parcel;
 import android.support.test.runner.AndroidJUnit4;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Xml;
 
 import com.android.internal.util.FastXmlSerializer;
 import com.android.server.UiServiceTestCase;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlSerializer;
 
+import java.io.BufferedInputStream;
 import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 
 @SmallTest
@@ -68,4 +74,23 @@
         serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
         channel.writeXml(serializer);
     }
+
+    @Test
+    public void testBackupEmptySound() throws Exception {
+        NotificationChannel channel = new NotificationChannel("a", "ab", IMPORTANCE_DEFAULT);
+        channel.setSound(Uri.EMPTY, null);
+
+        XmlSerializer serializer = new FastXmlSerializer();
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+        channel.writeXmlForBackup(serializer, getContext());
+
+        XmlPullParser parser = Xml.newPullParser();
+        parser.setInput(new BufferedInputStream(
+                new ByteArrayInputStream(baos.toByteArray())), null);
+        NotificationChannel restored = new NotificationChannel("a", "ab", IMPORTANCE_DEFAULT);
+        restored.populateFromXmlForRestore(parser, getContext());
+
+        assertNull(restored.getSound());
+    }
 }
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 66ccc6c..af44b7e 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -147,6 +147,8 @@
 
     boolean setWifiApConfiguration(in WifiConfiguration wifiConfig, String packageName);
 
+    void notifyUserOfApBandConversion(String packageName);
+
     Messenger getWifiServiceMessenger(String packageName);
 
     void enableTdls(String remoteIPAddress, boolean enable);
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 25f35d0..6963ed0 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -2170,6 +2170,21 @@
     }
 
     /**
+     * Method that triggers a notification to the user about a conversion to their saved AP config.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+    public void notifyUserOfApBandConversion() {
+        Log.d(TAG, "apBand was converted, notify the user");
+        try {
+            mService.notifyUserOfApBandConversion(mContext.getOpPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Enable/Disable TDLS on a specific local route.
      *
      * <p>