Merge "Fix the illumination dot getting stuck"
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index f533760..9c7dc96 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -8179,7 +8179,7 @@
     private static final PropertyInvalidatedCache<PackageInfoQuery, PackageInfo>
             sPackageInfoCache =
             new PropertyInvalidatedCache<PackageInfoQuery, PackageInfo>(
-                    16, PermissionManager.CACHE_KEY_PACKAGE_INFO) {
+                    32, PermissionManager.CACHE_KEY_PACKAGE_INFO) {
                 @Override
                 protected PackageInfo recompute(PackageInfoQuery query) {
                     return getPackageInfoAsUserUncached(
diff --git a/core/java/com/android/internal/os/KernelWakelockReader.java b/core/java/com/android/internal/os/KernelWakelockReader.java
index cffb0ad..3d35d2f 100644
--- a/core/java/com/android/internal/os/KernelWakelockReader.java
+++ b/core/java/com/android/internal/os/KernelWakelockReader.java
@@ -153,19 +153,32 @@
     }
 
     /**
+     * Attempt to wait for suspend_control service if not immediately available.
+     */
+    private ISuspendControlService waitForSuspendControlService() throws ServiceNotFoundException {
+        final String name = "suspend_control";
+        final int numRetries = 5;
+        for (int i = 0; i < numRetries; i++) {
+            mSuspendControlService = ISuspendControlService.Stub.asInterface(
+                                        ServiceManager.getService(name));
+            if (mSuspendControlService != null) {
+                return mSuspendControlService;
+            }
+        }
+        throw new ServiceNotFoundException(name);
+    }
+
+    /**
      * On success, returns the updated stats from SystemSupend, else returns null.
      */
     private KernelWakelockStats getWakelockStatsFromSystemSuspend(
             final KernelWakelockStats staleStats) {
         WakeLockInfo[] wlStats = null;
-        if (mSuspendControlService == null) {
-            try {
-                mSuspendControlService = ISuspendControlService.Stub.asInterface(
-                    ServiceManager.getServiceOrThrow("suspend_control"));
-            } catch (ServiceNotFoundException e) {
-                Slog.wtf(TAG, "Required service suspend_control not available", e);
-                return null;
-            }
+        try {
+            mSuspendControlService = waitForSuspendControlService();
+        } catch (ServiceNotFoundException e) {
+            Slog.wtf(TAG, "Required service suspend_control not available", e);
+            return null;
         }
 
         try {
diff --git a/core/res/res/values-television/config.xml b/core/res/res/values-television/config.xml
index 3ecb1dd..0382dd3 100644
--- a/core/res/res/values-television/config.xml
+++ b/core/res/res/values-television/config.xml
@@ -42,4 +42,13 @@
 
     <!-- Allow SystemUI to show the shutdown dialog -->
     <bool name="config_showSysuiShutdown">true</bool>
+
+    <!-- Control the behavior when the user long presses the power button.
+        0 - Nothing
+        1 - Global actions menu
+        2 - Power off (with confirmation)
+        3 - Power off (without confirmation)
+        4 - Go to voice assist
+        5 - Go to assistant (Settings.Secure.ASSISTANT -->
+    <integer name="config_longPressOnPowerBehavior">3</integer>
 </resources>
diff --git a/data/etc/preinstalled-packages-platform-overlays.xml b/data/etc/preinstalled-packages-platform-overlays.xml
index ecd7d40..84c1897a2 100644
--- a/data/etc/preinstalled-packages-platform-overlays.xml
+++ b/data/etc/preinstalled-packages-platform-overlays.xml
@@ -19,32 +19,250 @@
 <config>
     <install-in-user-type package="com.android.internal.display.cutout.emulation.corner">
         <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
     </install-in-user-type>
     <install-in-user-type package="com.android.internal.display.cutout.emulation.double">
         <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.internal.display.cutout.emulation.hole">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
     </install-in-user-type>
     <install-in-user-type package="com.android.internal.display.cutout.emulation.tall">
         <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
     </install-in-user-type>
-    <install-in-user-type package="com.android.internal.systemui.navbar.gestural">
+    <install-in-user-type package="com.android.internal.display.cutout.emulation.waterfall">
         <install-in user-type="FULL" />
-    </install-in-user-type>
-    <install-in-user-type package="com.android.internal.systemui.navbar.gestural_extra_wide_back">
-        <install-in user-type="FULL" />
-    </install-in-user-type>
-    <install-in-user-type package="com.android.internal.systemui.navbar.gestural_narrow_back">
-        <install-in user-type="FULL" />
-    </install-in-user-type>
-    <install-in-user-type package="com.android.internal.systemui.navbar.gestural_wide_back">
-        <install-in user-type="FULL" />
-    </install-in-user-type>
-    <install-in-user-type package="com.android.internal.systemui.navbar.threebutton">
-        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
     </install-in-user-type>
     <install-in-user-type package="com.android.internal.systemui.navbar.twobutton">
         <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.internal.systemui.navbar.threebutton">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.internal.systemui.navbar.gestural">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.internal.systemui.navbar.gestural_extra_wide_back">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.internal.systemui.navbar.gestural_narrow_back">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.internal.systemui.navbar.gestural_wide_back">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
     </install-in-user-type>
     <install-in-user-type package="com.android.internal.systemui.onehanded.gestural">
         <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+     <install-in-user-type package="com.android.theme.color.amethyst">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.color.aquamarine">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.color.black">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.color.carbon">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.color.cinnamon">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.color.green">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.color.ocean">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.color.orchid">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.color.palette">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.color.purple">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.color.sand">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.color.space">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.color.tangerine">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.font.notoserifsource">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.icon_pack.circular.android">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.icon_pack.circular.launcher">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.icon_pack.circular.settings">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.icon_pack.circular.systemui">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.icon_pack.circular.themepicker">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.icon_pack.filled.android">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.icon_pack.filled.launcher">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.icon_pack.filled.settings">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.icon_pack.filled.systemui">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.icon_pack.filled.themepicker">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.icon_pack.kai.android">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.icon_pack.kai.launcher">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.icon_pack.kai.settings">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.icon_pack.kai.systemui">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.icon_pack.kai.themepicker">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.icon_pack.rounded.android">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.icon_pack.rounded.launcher">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.icon_pack.rounded.settings">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.icon_pack.rounded.systemui">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.icon_pack.rounded.themepicker">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.icon_pack.sam.android">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.icon_pack.sam.launcher">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.icon_pack.sam.settings">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.icon_pack.sam.systemui">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.icon_pack.sam.themepicker">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.icon_pack.victor.android">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.icon_pack.victor.launcher">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.icon_pack.victor.settings">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.icon_pack.victor.systemui">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.icon_pack.victor.themepicker">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.icon.pebble">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.icon.roundedrect">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.icon.squircle">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.icon.taperedrect">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.icon.teardrop">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.theme.icon.vessel">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
     </install-in-user-type>
 </config>
diff --git a/location/java/android/location/package.html b/location/java/android/location/package.html
index 2355e72..20c5c54 100644
--- a/location/java/android/location/package.html
+++ b/location/java/android/location/package.html
@@ -6,7 +6,7 @@
 <p class="warning">
 <strong>This API is not the recommended method for accessing Android location.</strong><br>
 The
-<a href="{@docRoot}reference/com/google/android/gms/location/package-summary.html">Google Location Services API</a>,
+<a href="https://developers.google.com/android/reference/com/google/android/gms/location/package-summary">Google Location Services API</a>,
 part of Google Play services, is the preferred way to add location-awareness to
 your app. It offers a simpler API, higher accuracy, low-power geofencing, and
 more. If you are currently using the android.location API, you are strongly
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 8212d61..a56f6f5 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -92,6 +92,8 @@
 
     <item type="id" name="requires_remeasuring"/>
 
+    <item type="id" name="secondary_home_handle" />
+
     <!-- Whether the icon is from a notification for which targetSdk < L -->
     <item type="id" name="icon_is_pre_L"/>
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
index 332a00d..398a2c9 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.accessibility;
 
+import android.annotation.NonNull;
 import android.content.Context;
 import android.graphics.PixelFormat;
 import android.provider.Settings;
@@ -23,6 +24,7 @@
 import android.view.WindowManager;
 import android.widget.ImageView;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.R;
 
 /**
@@ -42,23 +44,36 @@
     private boolean mIsVisible = false;
 
     MagnificationModeSwitch(Context context) {
+        this(context, createView(context));
+    }
+
+    @VisibleForTesting
+    MagnificationModeSwitch(Context context, @NonNull ImageView imageView) {
         mContext = context;
         mWindowManager = (WindowManager) mContext.getSystemService(
                 Context.WINDOW_SERVICE);
         mParams = createLayoutParams();
-        mImageView = createView(mContext, mMagnificationMode);
+        mImageView = imageView;
+        applyResourcesValues();
         mImageView.setOnClickListener(
                 view -> {
                     removeButton();
                     toggleMagnificationMode();
                 });
+        mImageView.setImageResource(getIconResId(mMagnificationMode));
+    }
+
+    private void applyResourcesValues() {
+        final int padding = mContext.getResources().getDimensionPixelSize(
+                R.dimen.magnification_switch_button_padding);
+        mImageView.setPadding(padding, padding, padding, padding);
     }
 
     void removeButton() {
-        mImageView.animate().cancel();
         if (!mIsVisible) {
             return;
         }
+        mImageView.animate().cancel();
         mWindowManager.removeView(mImageView);
         mIsVisible = false;
     }
@@ -72,7 +87,7 @@
             mWindowManager.addView(mImageView, mParams);
             mIsVisible = true;
         }
-
+        mImageView.setAlpha(1.0f);
         // TODO(b/143852371): use accessibility timeout as a delay.
         // Dismiss the magnification switch button after the button is displayed for a period of
         // time.
@@ -82,30 +97,29 @@
                 .setStartDelay(START_DELAY_MS)
                 .setDuration(DURATION_MS)
                 .withEndAction(
-                        () -> removeButton());
+                        () -> removeButton())
+                .start();
     }
 
     private void toggleMagnificationMode() {
         final int newMode =
                 mMagnificationMode ^ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
         mMagnificationMode = newMode;
+        mImageView.setImageResource(getIconResId(newMode));
         Settings.Secure.putInt(mContext.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE, newMode);
     }
 
-    private static ImageView createView(Context context, int mode) {
-        final int padding = context.getResources().getDimensionPixelSize(
-                R.dimen.magnification_switch_button_padding);
+    private static ImageView createView(Context context) {
         ImageView imageView  = new ImageView(context);
-        imageView.setImageResource(getIconResId(mode));
         imageView.setClickable(true);
         imageView.setFocusable(true);
-        imageView.setPadding(padding, padding, padding, padding);
         imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
         return imageView;
     }
 
-    private static int getIconResId(int mode) {
+    @VisibleForTesting
+    static int getIconResId(int mode) {
         return (mode == Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN)
                 ? R.drawable.ic_open_in_new_window
                 : R.drawable.ic_open_in_new_fullscreen;
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
index 78d7087..e8dba8f 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
@@ -217,10 +217,10 @@
     private fun dumpMessage(message: LogMessage, pw: PrintWriter) {
         pw.print(DATE_FORMAT.format(message.timestamp))
         pw.print(" ")
-        pw.print(message.level)
+        pw.print(message.level.shortString)
         pw.print(" ")
         pw.print(message.tag)
-        pw.print(" ")
+        pw.print(": ")
         pw.println(message.printer(message))
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogLevel.kt b/packages/SystemUI/src/com/android/systemui/log/LogLevel.kt
index 7b9af0f..53f231c 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogLevel.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogLevel.kt
@@ -21,11 +21,14 @@
 /**
  * Enum version of @Log.Level
  */
-enum class LogLevel(@Log.Level val nativeLevel: Int) {
-    VERBOSE(Log.VERBOSE),
-    DEBUG(Log.DEBUG),
-    INFO(Log.INFO),
-    WARNING(Log.WARN),
-    ERROR(Log.ERROR),
-    WTF(Log.ASSERT)
+enum class LogLevel(
+    @Log.Level val nativeLevel: Int,
+    val shortString: String
+) {
+    VERBOSE(Log.VERBOSE, "V"),
+    DEBUG(Log.DEBUG, "D"),
+    INFO(Log.INFO, "I"),
+    WARNING(Log.WARN, "W"),
+    ERROR(Log.ERROR, "E"),
+    WTF(Log.ASSERT, "WTF")
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 27daf86..837543c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -591,6 +591,7 @@
                 .registerDisplayListener(this, new Handler(Looper.getMainLooper()));
 
         mOrientationHandle = new QuickswitchOrientedNavHandle(getContext());
+        mOrientationHandle.setId(R.id.secondary_home_handle);
 
         getBarTransitions().addDarkIntensityListener(mOrientationHandleIntensityListener);
         mOrientationParams = new WindowManager.LayoutParams(0, 0,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
new file mode 100644
index 0000000..71f3d5b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility;
+
+import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
+import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
+
+import static com.android.systemui.accessibility.MagnificationModeSwitch.getIconResId;
+
+import static junit.framework.Assert.assertEquals;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.provider.Settings;
+import android.testing.AndroidTestingRunner;
+import android.view.View;
+import android.view.ViewPropertyAnimator;
+import android.view.WindowManager;
+import android.widget.ImageView;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class MagnificationModeSwitchTest extends SysuiTestCase {
+
+    @Mock
+    private ImageView mMockImageView;
+    @Mock
+    private WindowManager mWindowManager;
+    @Mock
+    private ViewPropertyAnimator mViewPropertyAnimator;
+    private MagnificationModeSwitch mMagnificationModeSwitch;
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
+
+        when(mViewPropertyAnimator.setDuration(anyLong())).thenReturn(mViewPropertyAnimator);
+        when(mViewPropertyAnimator.alpha(anyFloat())).thenReturn(mViewPropertyAnimator);
+        when(mViewPropertyAnimator.setStartDelay(anyLong())).thenReturn(mViewPropertyAnimator);
+        when(mViewPropertyAnimator.withEndAction(any(Runnable.class))).thenReturn(
+                mViewPropertyAnimator);
+
+        when(mMockImageView.animate()).thenReturn(mViewPropertyAnimator);
+
+        mMagnificationModeSwitch = new MagnificationModeSwitch(mContext, mMockImageView);
+    }
+
+    @Test
+    public void removeButton_removeView() {
+        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+
+        mMagnificationModeSwitch.removeButton();
+
+        verify(mWindowManager).removeView(mMockImageView);
+        // First invocation is in showButton.
+        verify(mViewPropertyAnimator, times(2)).cancel();
+    }
+
+    @Test
+    public void showWindowModeButton_fullscreenMode_addViewAndSetImageResource() {
+        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+
+        verify(mMockImageView).setAlpha(1.0f);
+        verify(mMockImageView).setImageResource(
+                getIconResId(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW));
+        verify(mViewPropertyAnimator).cancel();
+        verify(mViewPropertyAnimator).setDuration(anyLong());
+        verify(mViewPropertyAnimator).setStartDelay(anyLong());
+        verify(mViewPropertyAnimator).alpha(anyFloat());
+        ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
+        verify(mViewPropertyAnimator).withEndAction(captor.capture());
+        verify(mWindowManager).addView(eq(mMockImageView), any(WindowManager.LayoutParams.class));
+
+        captor.getValue().run();
+
+        // First invocation is in showButton.
+        verify(mViewPropertyAnimator, times(2)).cancel();
+        verify(mWindowManager).removeView(mMockImageView);
+    }
+
+    @Test
+    public void performClick_fullscreenMode_removeViewAndChangeSettingsValue() {
+        ArgumentCaptor<View.OnClickListener> captor = ArgumentCaptor.forClass(
+                View.OnClickListener.class);
+        verify(mMockImageView).setOnClickListener(captor.capture());
+        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+
+        captor.getValue().onClick(mMockImageView);
+
+        // First invocation is in showButton.
+        verify(mViewPropertyAnimator, times(2)).cancel();
+        verify(mMockImageView).setImageResource(
+                getIconResId(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW));
+        verify(mWindowManager).removeView(mMockImageView);
+        final int actualMode = Settings.Secure.getInt(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE, 0);
+        assertEquals(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW, actualMode);
+    }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index dad0612..d50e9d7 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -175,7 +175,7 @@
      */
     public FullScreenMagnificationGestureHandler(Context context,
             FullScreenMagnificationController fullScreenMagnificationController,
-            MagnificationGestureHandler.ScaleChangedListener listener,
+            ScaleChangedListener listener,
             boolean detectTripleTap,
             boolean detectShortcutTrigger,
             int displayId) {
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java
index f38c38f..d6f53d2 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java
@@ -23,7 +23,7 @@
  */
 public abstract class MagnificationGestureHandler extends BaseEventStreamTransformation {
 
-    protected final MagnificationGestureHandler.ScaleChangedListener mListener;
+    protected final ScaleChangedListener mListener;
 
     protected MagnificationGestureHandler(ScaleChangedListener listener) {
         mListener = listener;
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
index 56c0519..bd25f2b 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
@@ -100,7 +100,7 @@
      */
     public WindowMagnificationGestureHandler(Context context,
             WindowMagnificationManager windowMagnificationMgr,
-            MagnificationGestureHandler.ScaleChangedListener listener, boolean detectTripleTap,
+            ScaleChangedListener listener, boolean detectTripleTap,
             boolean detectShortcutTrigger, int displayId) {
         super(listener);
         if (DEBUG_ALL) {
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
index a3c5d1e..a08c2dd 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
@@ -17,7 +17,10 @@
 package com.android.server.accessibility.magnification;
 
 import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.graphics.Rect;
 import android.os.Binder;
 import android.os.IBinder;
@@ -52,17 +55,28 @@
     static final float MAX_SCALE = FullScreenMagnificationController.MAX_SCALE;
     static final float MIN_SCALE = FullScreenMagnificationController.MIN_SCALE;
 
-    private final Object mLock = new Object();;
+    private final Object mLock = new Object();
     private final Context mContext;
     @VisibleForTesting
     @GuardedBy("mLock")
-    @Nullable WindowMagnificationConnectionWrapper mConnectionWrapper;
+    @Nullable
+    WindowMagnificationConnectionWrapper mConnectionWrapper;
     @GuardedBy("mLock")
     private ConnectionCallback mConnectionCallback;
     @GuardedBy("mLock")
     private SparseArray<WindowMagnifier> mWindowMagnifiers = new SparseArray<>();
     private int mUserId;
 
+    @VisibleForTesting
+    protected final BroadcastReceiver mScreenStateReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final int displayId = context.getDisplayId();
+            removeMagnificationButton(displayId);
+            disableWindowMagnification(displayId);
+        }
+    };
+
     public WindowMagnificationManager(Context context, int userId) {
         mContext = context;
         mUserId = userId;
@@ -133,8 +147,12 @@
             if (connect == isConnected()) {
                 return false;
             }
-            if (!connect) {
+            if (connect) {
+                final IntentFilter intentFilter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
+                mContext.registerReceiver(mScreenStateReceiver, intentFilter);
+            } else {
                 disableAllWindowMagnifiers();
+                mContext.unregisterReceiver(mScreenStateReceiver);
             }
         }
 
diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java
index 29b8493..03ca8fa 100644
--- a/services/core/java/com/android/server/biometrics/Utils.java
+++ b/services/core/java/com/android/server/biometrics/Utils.java
@@ -31,6 +31,7 @@
 import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_NO_HARDWARE;
 import static com.android.server.biometrics.PreAuthInfo.CREDENTIAL_NOT_ENROLLED;
 
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.content.ComponentName;
 import android.content.Context;
@@ -52,6 +53,7 @@
 import android.util.Slog;
 
 import com.android.internal.R;
+import com.android.server.biometrics.sensors.ClientMonitor;
 
 public class Utils {
 
@@ -395,4 +397,8 @@
                 ? keyguardComponent.getPackageName() : null;
         return hasPermission && keyguardPackage != null && keyguardPackage.equals(clientPackage);
     }
+
+    public static String getClientName(@Nullable ClientMonitor<?> client) {
+        return client != null ? client.getClass().getSimpleName() : "null";
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
index 14baaa7..f8e8dd9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
@@ -45,7 +45,7 @@
     private final PowerManager mPowerManager;
     private final VibrationEffect mSuccessVibrationEffect;
     private final VibrationEffect mErrorVibrationEffect;
-    private boolean mErrorAlreadySent;
+    private boolean mShouldSendErrorToClient;
 
     /**
      * Stops the HAL operation specific to the ClientMonitor subclass.
@@ -84,11 +84,11 @@
         // case (success, failure, or error) is received from the HAL (e.g. versions of fingerprint
         // that do not handle lockout under the HAL. In these cases, ensure that the framework only
         // sends errors once per ClientMonitor.
-        if (!mErrorAlreadySent) {
+        if (!mShouldSendErrorToClient) {
             logOnError(getContext(), errorCode, vendorCode, getTargetUserId());
             try {
                 if (getListener() != null) {
-                    mErrorAlreadySent = true;
+                    mShouldSendErrorToClient = true;
                     getListener().onError(getSensorId(), getCookie(), errorCode, vendorCode);
                 }
             } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index 5392f0f..fdc3def 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -17,6 +17,7 @@
 package com.android.server.biometrics.sensors;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.ActivityTaskManager;
 import android.app.IActivityTaskManager;
 import android.app.TaskStackListener;
@@ -41,7 +42,7 @@
     private final boolean mIsStrongBiometric;
     private final boolean mRequireConfirmation;
     private final IActivityTaskManager mActivityTaskManager;
-    private final TaskStackListener mTaskStackListener;
+    @Nullable private final TaskStackListener mTaskStackListener;
     private final LockoutTracker mLockoutTracker;
     private final boolean mIsRestricted;
 
@@ -56,7 +57,7 @@
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
             int targetUserId, long operationId, boolean restricted, @NonNull String owner,
             int cookie, boolean requireConfirmation, int sensorId, boolean isStrongBiometric,
-            int statsModality, int statsClient, @NonNull TaskStackListener taskStackListener,
+            int statsModality, int statsClient, @Nullable TaskStackListener taskStackListener,
             @NonNull LockoutTracker lockoutTracker) {
         super(context, lazyDaemon, token, listener, targetUserId, owner, cookie, sensorId,
                 statsModality, BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient);
@@ -133,10 +134,12 @@
                     vibrateSuccess();
                 }
 
-                try {
-                    mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Could not unregister task stack listener", e);
+                if (mTaskStackListener != null) {
+                    try {
+                        mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "Could not unregister task stack listener", e);
+                    }
                 }
 
                 final byte[] byteToken = new byte[hardwareAuthToken.size()];
@@ -221,10 +224,12 @@
             return;
         }
 
-        try {
-            mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Could not register task stack listener", e);
+        if (mTaskStackListener != null) {
+            try {
+                mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Could not register task stack listener", e);
+            }
         }
 
         if (DEBUG) Slog.w(TAG, "Requesting auth for " + getOwnerString());
@@ -241,10 +246,12 @@
             return;
         }
 
-        try {
-            mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Could not unregister task stack listener", e);
+        if (mTaskStackListener != null) {
+            try {
+                mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Could not unregister task stack listener", e);
+            }
         }
 
         if (DEBUG) Slog.w(TAG, "Requesting cancel for " + getOwnerString());
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
index c9ab313..6bdd783 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -28,7 +28,7 @@
 import android.os.ServiceManager;
 import android.util.Slog;
 
-import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityTracker;
+import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -37,7 +37,6 @@
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Date;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Locale;
 import java.util.Queue;
@@ -133,7 +132,7 @@
         @Override
         public void run() {
             if (operation.state != Operation.STATE_FINISHED) {
-                Slog.e(tag, "[Watchdog] Running for: " + operation);
+                Slog.e(tag, "[Watchdog Triggered]: " + operation);
                 operation.clientMonitor.mFinishCallback
                         .onClientFinished(operation.clientMonitor, false /* success */);
             }
@@ -173,7 +172,7 @@
     }
 
     @NonNull private final String mBiometricTag;
-    @Nullable private final GestureAvailabilityTracker mGestureAvailabilityTracker;
+    @Nullable private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
     @NonNull private final IBiometricService mBiometricService;
     @NonNull private final Handler mHandler = new Handler(Looper.getMainLooper());
     @NonNull private final InternalFinishCallback mInternalFinishCallback;
@@ -195,6 +194,8 @@
                     return;
                 }
 
+                mCurrentOperation.state = Operation.STATE_FINISHED;
+
                 if (mCurrentOperation.clientFinishCallback != null) {
                     mCurrentOperation.clientFinishCallback.onClientFinished(clientMonitor, success);
                 }
@@ -206,12 +207,11 @@
                 }
 
                 Slog.d(getTag(), "[Finished] " + clientMonitor + ", success: " + success);
-                if (mGestureAvailabilityTracker != null) {
-                    mGestureAvailabilityTracker.markSensorActive(
+                if (mGestureAvailabilityDispatcher != null) {
+                    mGestureAvailabilityDispatcher.markSensorActive(
                             mCurrentOperation.clientMonitor.getSensorId(), false /* active */);
                 }
 
-                mCurrentOperation.state = Operation.STATE_FINISHED;
                 mCurrentOperation = null;
                 startNextOperationIfIdle();
             });
@@ -221,15 +221,15 @@
     /**
      * Creates a new scheduler.
      * @param tag for the specific instance of the scheduler. Should be unique.
-     * @param gestureAvailabilityTracker may be null if the sensor does not support gestures (such
-     *                                   as fingerprint swipe).
+     * @param gestureAvailabilityDispatcher may be null if the sensor does not support gestures
+     *                                      (such as fingerprint swipe).
      */
     public BiometricScheduler(@NonNull String tag,
-            @Nullable GestureAvailabilityTracker gestureAvailabilityTracker) {
+            @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
         mBiometricTag = tag;
         mInternalFinishCallback = new InternalFinishCallback();
-        mGestureAvailabilityTracker = gestureAvailabilityTracker;
-        mPendingOperations = new LinkedList<>();
+        mGestureAvailabilityDispatcher = gestureAvailabilityDispatcher;
+        mPendingOperations = new ArrayDeque<>();
         mBiometricService = IBiometricService.Stub.asInterface(
                 ServiceManager.getService(Context.BIOMETRIC_SERVICE));
         mCrashStates = new ArrayDeque<>();
@@ -268,9 +268,9 @@
             return;
         }
 
-        if (mGestureAvailabilityTracker != null
+        if (mGestureAvailabilityDispatcher != null
                 && mCurrentOperation.clientMonitor instanceof AcquisitionClient) {
-            mGestureAvailabilityTracker.markSensorActive(
+            mGestureAvailabilityDispatcher.markSensorActive(
                     mCurrentOperation.clientMonitor.getSensorId(),
                     true /* active */);
         }
@@ -297,15 +297,23 @@
      * Starts the {@link #mCurrentOperation} if
      * 1) its state is {@link Operation#STATE_WAITING_FOR_COOKIE} and
      * 2) its cookie matches this cookie
+     *
+     * This is currently only used by {@link com.android.server.biometrics.BiometricService}, which
+     * requests sensors to prepare for authentication with a cookie. Once sensor(s) are ready (e.g.
+     * the BiometricService client becomes the current client in the scheduler), the cookie is
+     * returned to BiometricService. Once BiometricService decides that authentication can start,
+     * it invokes this code path.
+     *
      * @param cookie of the operation to be started
      */
     public void startPreparedClient(int cookie) {
         if (mCurrentOperation == null) {
-            Slog.e(getTag(), "Current operation null");
+            Slog.e(getTag(), "Current operation is null");
             return;
         }
         if (mCurrentOperation.state != Operation.STATE_WAITING_FOR_COOKIE) {
-            Slog.e(getTag(), "Operation in wrong state: " + mCurrentOperation);
+            Slog.e(getTag(), "Operation is in the wrong state: " + mCurrentOperation
+                    + ", expected STATE_WAITING_FOR_COOKIE");
             return;
         }
         if (mCurrentOperation.clientMonitor.getCookie() != cookie) {
@@ -354,7 +362,8 @@
 
         // If the current operation is cancellable, start the cancellation process.
         if (mCurrentOperation != null && mCurrentOperation.clientMonitor instanceof Interruptable
-                && mCurrentOperation.state != Operation.STATE_STARTED_CANCELING) {
+                && mCurrentOperation.state == Operation.STATE_STARTED) {
+            Slog.d(getTag(), "[Cancelling Interruptable]: " + mCurrentOperation);
             cancelInternal(mCurrentOperation);
         }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricServiceBase.java b/services/core/java/com/android/server/biometrics/sensors/BiometricServiceBase.java
deleted file mode 100644
index 9aa72a7..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricServiceBase.java
+++ /dev/null
@@ -1,681 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.biometrics.sensors;
-
-import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE;
-
-import android.app.ActivityManager;
-import android.app.ActivityTaskManager;
-import android.app.AppOpsManager;
-import android.app.IActivityTaskManager;
-import android.app.SynchronousUserSwitchObserver;
-import android.app.TaskStackListener;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
-import android.hardware.biometrics.BiometricAuthenticator;
-import android.hardware.biometrics.BiometricConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
-import android.hardware.biometrics.IBiometricService;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.IHwBinder;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.util.Slog;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.statusbar.IStatusBarService;
-import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.SystemService;
-import com.android.server.biometrics.Utils;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Abstract base class containing all of the business logic for biometric services, e.g.
- * Fingerprint, Face, Iris.
- *
- * @hide
- */
-public abstract class BiometricServiceBase<T> extends SystemService
-        implements IHwBinder.DeathRecipient {
-
-    protected static final boolean DEBUG = true;
-
-    private static final int MSG_USER_SWITCHING = 10;
-    private static final long CANCEL_TIMEOUT_LIMIT = 3000; // max wait for onCancel() from HAL,in ms
-
-    private final Context mContext;
-    protected final IActivityTaskManager mActivityTaskManager;
-    protected final BiometricTaskStackListener mTaskStackListener =
-            new BiometricTaskStackListener();
-    private final ResetClientStateRunnable mResetClientState = new ResetClientStateRunnable();
-
-    protected final IStatusBarService mStatusBarService;
-    protected final Map<Integer, Long> mAuthenticatorIds =
-            Collections.synchronizedMap(new HashMap<>());
-    protected final AppOpsManager mAppOps;
-
-    /**
-     * Handler which all subclasses should post events to.
-     */
-    protected final Handler mHandler = new Handler(Looper.getMainLooper()) {
-        @Override
-        public void handleMessage(android.os.Message msg) {
-            switch (msg.what) {
-                case MSG_USER_SWITCHING:
-                    handleUserSwitching(msg.arg1);
-                    break;
-                default:
-                    Slog.w(getTag(), "Unknown message:" + msg.what);
-            }
-        }
-    };
-
-    protected final ClientMonitor.FinishCallback mClientFinishCallback =
-            (clientMonitor, success) -> {
-        removeClient(clientMonitor);
-        // When enrollment finishes, update this group's authenticator id, as the HAL has
-        // already generated a new authenticator id when the new biometric is enrolled.
-        if (clientMonitor instanceof EnrollClient) {
-            updateActiveGroup(clientMonitor.getTargetUserId());
-        }
-    };
-
-    private IBiometricService mBiometricService;
-    private ClientMonitor<T> mCurrentClient;
-    private ClientMonitor<T> mPendingClient;
-    private PerformanceTracker mPerformanceTracker;
-    private int mSensorId;
-    protected int mCurrentUserId = UserHandle.USER_NULL;
-
-    /**
-     * @return the log tag.
-     */
-    protected abstract String getTag();
-
-    /**
-     * @return a fresh reference to the biometric HAL
-     */
-    protected abstract T getDaemon();
-
-    /**
-     * @return the biometric utilities for a specific implementation.
-     */
-    protected abstract BiometricUtils getBiometricUtils();
-
-    /**
-     * @param userId
-     * @return true if the enrollment limit has been reached.
-     */
-    protected abstract boolean hasReachedEnrollmentLimit(int userId);
-
-    /**
-     * Notifies the HAL that the user has changed.
-     * @param userId
-     */
-    protected abstract void updateActiveGroup(int userId);
-
-    /**
-     * @param userId
-     * @return Returns true if the user has any enrolled biometrics.
-     */
-    protected abstract boolean hasEnrolledBiometrics(int userId);
-
-    /**
-     * @return Returns the MANAGE_* permission string, which is required for enrollment, removal
-     * etc.
-     */
-    protected abstract String getManageBiometricPermission();
-
-    protected abstract List<? extends BiometricAuthenticator.Identifier> getEnrolledTemplates(
-            int userId);
-
-    /**
-     * Notifies clients of any change in the biometric state (active / idle). This is mainly for
-     * Fingerprint navigation gestures.
-     * @param isActive
-     */
-    protected void notifyClientActiveCallbacks(boolean isActive) {}
-
-    protected abstract int statsModality();
-
-    private final Runnable mOnTaskStackChangedRunnable = new Runnable() {
-        @Override
-        public void run() {
-            try {
-                if (!(mCurrentClient instanceof AuthenticationClient)) {
-                    return;
-                }
-                final String currentClient = mCurrentClient.getOwnerString();
-                if (isKeyguard(currentClient)) {
-                    return; // Keyguard is always allowed
-                }
-                List<ActivityManager.RunningTaskInfo> runningTasks =
-                        mActivityTaskManager.getTasks(1);
-                if (!runningTasks.isEmpty()) {
-                    final String topPackage = runningTasks.get(0).topActivity.getPackageName();
-                    if (!topPackage.contentEquals(currentClient)
-                            && !mCurrentClient.isAlreadyDone()) {
-                        Slog.e(getTag(), "Stopping background authentication, top: "
-                                + topPackage + " currentClient: " + currentClient);
-                        ((AuthenticationClient) mCurrentClient).cancel();
-                    }
-                }
-            } catch (RemoteException e) {
-                Slog.e(getTag(), "Unable to get running tasks", e);
-            }
-        }
-    };
-
-    private final class BiometricTaskStackListener extends TaskStackListener {
-        @Override
-        public void onTaskStackChanged() {
-            mHandler.post(mOnTaskStackChangedRunnable);
-        }
-    }
-
-    private final class ResetClientStateRunnable implements 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 BiometricConstants#BIOMETRIC_ERROR_CANCELED} in response to cancel()
-             * once it has successfully switched to the IDLE state in the HAL.
-             * Additionally,{@link BiometricConstants#BIOMETRIC_ERROR_CANCELED} should only be sent
-             * in response to an actual cancel() call.
-             */
-            Slog.w(getTag(), "Client "
-                    + (mCurrentClient != null ? mCurrentClient.getOwnerString() : "null")
-                    + " failed to respond to cancel, starting client "
-                    + (mPendingClient != null ? mPendingClient.getOwnerString() : "null"));
-
-            FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED,
-                    statsModality(), BiometricsProtoEnums.ISSUE_CANCEL_TIMED_OUT);
-
-            ClientMonitor<T> newClient = mPendingClient;
-            mCurrentClient = null;
-            mPendingClient = null;
-            startClient(newClient, false);
-        }
-    }
-
-    /**
-     * Initializes the system service.
-     * <p>
-     * Subclasses must define a single argument constructor that accepts the context
-     * and passes it to super.
-     * </p>
-     *
-     * @param context The system server context.
-     */
-    public BiometricServiceBase(Context context) {
-        super(context);
-        mContext = context;
-        mStatusBarService = IStatusBarService.Stub.asInterface(
-                ServiceManager.getService(Context.STATUS_BAR_SERVICE));
-        mAppOps = context.getSystemService(AppOpsManager.class);
-        mActivityTaskManager = ActivityTaskManager.getService();
-        mPerformanceTracker = PerformanceTracker.getInstanceForSensorId(getSensorId());
-    }
-
-    @Override
-    public void onStart() {
-        listenForUserSwitches();
-    }
-
-    @Override
-    public void serviceDied(long cookie) {
-        Slog.e(getTag(), "HAL died");
-        mPerformanceTracker.incrementHALDeathCount();
-        mCurrentUserId = UserHandle.USER_NULL;
-
-        // All client lifecycle must be managed on the handler.
-        mHandler.post(() -> {
-            Slog.e(getTag(), "Sending BIOMETRIC_ERROR_HW_UNAVAILABLE after HAL crash");
-            handleError(BIOMETRIC_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
-        });
-
-        FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED,
-                statsModality(), BiometricsProtoEnums.ISSUE_HAL_DEATH);
-    }
-
-    protected void initializeConfigurationInternal(int sensorId) {
-        if (DEBUG) {
-            Slog.d(getTag(), "initializeConfigurationInternal(" + sensorId + ")");
-        }
-        mSensorId = sensorId;
-    }
-
-    protected ClientMonitor<?> getCurrentClient() {
-        return mCurrentClient;
-    }
-
-    protected boolean isStrongBiometric() {
-        return Utils.isStrongBiometric(mSensorId);
-    }
-
-    protected int getSensorId() {
-        return mSensorId;
-    }
-
-    /**
-     * Callback handlers from the daemon. The caller must put this on a handler.
-     */
-
-    protected void handleAcquired(int acquiredInfo, int vendorCode) {
-        final ClientMonitor<?> client = mCurrentClient;
-        if (!(client instanceof AcquisitionClient)) {
-            final String clientName = client != null ? client.getClass().getSimpleName() : "null";
-            Slog.e(getTag(), "handleAcquired for non-acquire consumer: " + clientName);
-            return;
-        }
-
-        final AcquisitionClient<?> acquisitionClient = (AcquisitionClient<?>) client;
-        acquisitionClient.onAcquired(acquiredInfo, vendorCode);
-    }
-
-    protected void handleAuthenticated(BiometricAuthenticator.Identifier identifier,
-            ArrayList<Byte> token) {
-        final ClientMonitor<?> client = mCurrentClient;
-        if (!(client instanceof AuthenticationClient)) {
-            final String clientName = client != null ? client.getClass().getSimpleName() : "null";
-            Slog.e(getTag(), "handleAuthenticated for non-authentication client: " + clientName);
-            return;
-        }
-
-        final AuthenticationClient<?> authenticationClient = (AuthenticationClient<?>) client;
-        final boolean authenticated = identifier.getBiometricId() != 0;
-        authenticationClient.onAuthenticated(identifier, authenticated, token);
-    }
-
-    protected void handleEnrollResult(BiometricAuthenticator.Identifier identifier,
-            int remaining) {
-        final ClientMonitor<?> client = mCurrentClient;
-        if (!(client instanceof EnrollClient)) {
-            final String clientName = client != null ? client.getClass().getSimpleName() : "null";
-            Slog.e(getTag(), "handleEnrollResult for non-enroll client: " + clientName);
-            return;
-        }
-
-        final EnrollClient<?> enrollClient = (EnrollClient<?>) client;
-        enrollClient.onEnrollResult(identifier, remaining);
-    }
-
-    protected void handleError(int error, int vendorCode) {
-        final ClientMonitor<?> client = mCurrentClient;
-
-        if (DEBUG) Slog.v(getTag(), "handleError(client="
-                + (client != null ? client.getOwnerString() : "null") + ", error = " + error + ")");
-
-        if (!(client instanceof Interruptable)) {
-            Slog.e(getTag(), "error received for non-ErrorConsumer");
-            return;
-        }
-
-        final Interruptable interruptable = (Interruptable) client;
-        interruptable.onError(error, vendorCode);
-
-        if (error == BiometricConstants.BIOMETRIC_ERROR_CANCELED) {
-            mHandler.removeCallbacks(mResetClientState);
-            if (mPendingClient != null) {
-                if (DEBUG) Slog.v(getTag(), "start pending client " +
-                        mPendingClient.getOwnerString());
-                startClient(mPendingClient, false);
-                mPendingClient = null;
-            }
-        }
-    }
-
-    protected void handleRemoved(BiometricAuthenticator.Identifier identifier,
-            final int remaining) {
-        if (DEBUG) Slog.w(getTag(), "Removed: fid=" + identifier.getBiometricId()
-                + ", dev=" + identifier.getDeviceId()
-                + ", rem=" + remaining);
-
-        final ClientMonitor<?> client = mCurrentClient;
-        if (!(client instanceof RemovalConsumer)) {
-            final String clientName = client != null ? client.getClass().getSimpleName() : "null";
-            Slog.e(getTag(), "handleRemoved for non-removal consumer: " + clientName);
-            return;
-        }
-
-        final RemovalConsumer removalConsumer = (RemovalConsumer) client;
-        removalConsumer.onRemoved(identifier, remaining);
-    }
-
-    protected void handleEnumerate(BiometricAuthenticator.Identifier identifier, int remaining) {
-        final ClientMonitor<?> client = mCurrentClient;
-        if (!(client instanceof EnumerateConsumer)) {
-            final String clientName = client != null ? client.getClass().getSimpleName() : "null";
-            Slog.e(getTag(), "handleEnumerate for non-enumerate consumer: "
-                    + clientName);
-            return;
-        }
-
-        final EnumerateConsumer enumerateConsumer = (EnumerateConsumer) client;
-        enumerateConsumer.onEnumerationResult(identifier, remaining);
-    }
-
-    /**
-     * Calls from the Manager. These are still on the calling binder's thread.
-     */
-
-    protected void enrollInternal(EnrollClient<T> client, int userId) {
-        if (hasReachedEnrollmentLimit(userId)) {
-            return;
-        }
-
-        // Group ID is arbitrarily set to parent profile user ID. It just represents
-        // the default biometrics for the user.
-        if (!Utils.isCurrentUserOrProfile(mContext, userId)) {
-            return;
-        }
-
-        mHandler.post(() -> {
-            startClient(client, true /* initiatedByClient */);
-        });
-    }
-
-    protected void cancelEnrollmentInternal(IBinder token) {
-        mHandler.post(() -> {
-            ClientMonitor<?> client = mCurrentClient;
-            if (client instanceof EnrollClient && client.getToken() == token) {
-                if (DEBUG) Slog.v(getTag(), "Cancelling enrollment");
-                ((EnrollClient<?>) client).cancel();
-            }
-        });
-    }
-
-    protected void generateChallengeInternal(GenerateChallengeClient<T> client) {
-        mHandler.post(() -> {
-            startClient(client, true /* initiatedByClient */);
-        });
-    }
-
-    protected void revokeChallengeInternal(RevokeChallengeClient<T> client) {
-        mHandler.post(() -> {
-            startClient(client, true /* initiatedByClient */);
-        });
-    }
-
-    protected void authenticateInternal(AuthenticationClient<T> client, String opPackageName) {
-        mHandler.post(() -> {
-            startAuthentication(client, opPackageName);
-        });
-    }
-
-    protected void cancelAuthenticationInternal(final IBinder token, final String opPackageName,
-            boolean fromClient) {
-
-        if (DEBUG) Slog.v(getTag(), "cancelAuthentication(" + opPackageName + ")");
-
-        mHandler.post(() -> {
-            ClientMonitor<?> client = mCurrentClient;
-            if (client instanceof AuthenticationClient) {
-                if (client.getToken() == token || !fromClient) {
-                    if (DEBUG) Slog.v(getTag(), "Stopping client " + client.getOwnerString()
-                            + ", fromClient: " + fromClient);
-                    // If cancel was from BiometricService, it means the dialog was dismissed
-                    // and authentication should be canceled.
-                    ((AuthenticationClient<?>) client).cancel();
-                } else {
-                    if (DEBUG) Slog.v(getTag(), "Can't stop client " + client.getOwnerString()
-                            + " since tokens don't match. fromClient: " + fromClient);
-                }
-            } else if (client != null) {
-                if (DEBUG) Slog.v(getTag(), "Can't cancel non-authenticating client "
-                        + client.getOwnerString());
-            }
-        });
-    }
-
-    protected void removeInternal(RemovalClient<T> client) {
-        mHandler.post(() -> {
-            startClient(client, true /* initiatedByClient */);
-        });
-    }
-
-    protected void cleanupInternal(
-            InternalCleanupClient<? extends BiometricAuthenticator.Identifier, T> client) {
-        mHandler.post(() -> {
-            if (DEBUG) {
-                Slog.v(getTag(), "Cleaning up templates for user("
-                        + client.getTargetUserId() + ")");
-            }
-            startClient(client, true /* initiatedByClient */);
-        });
-    }
-
-    // Should be done on a handler thread - not on the Binder's thread.
-    private void startAuthentication(AuthenticationClient<T> client, String opPackageName) {
-        if (DEBUG) Slog.v(getTag(), "startAuthentication(" + opPackageName + ")");
-
-        startClient(client, true /* initiatedByClient */);
-    }
-
-    /**
-     * Helper methods.
-     */
-
-    /**
-     * @return true if this is keyguard package
-     */
-    public boolean isKeyguard(String clientPackage) {
-        return Utils.isKeyguard(mContext, clientPackage);
-    }
-
-    /**
-     * Calls the 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 BiometricConstants#BIOMETRIC_ERROR_CANCELED}).
-     *
-     * @param newClient the new client that wants to connect
-     * @param initiatedByClient true for authenticate, remove and enroll
-     */
-    @VisibleForTesting
-    protected void startClient(ClientMonitor<T> newClient, boolean initiatedByClient) {
-        ClientMonitor<?> currentClient = mCurrentClient;
-        if (currentClient != null) {
-            if (DEBUG) Slog.v(getTag(), "request stop current client " +
-                    currentClient.getOwnerString());
-            if (currentClient instanceof InternalCleanupClient) {
-                // This condition means we're currently running internal diagnostics to
-                // remove extra templates in the hardware and/or the software
-                // TODO: design an escape hatch in case client never finishes
-                if (newClient != null) {
-                    Slog.w(getTag(), "Internal cleanup in progress but trying to start client "
-                            + newClient.getClass().getSuperclass().getSimpleName()
-                            + "(" + newClient.getOwnerString() + ")"
-                            + ", initiatedByClient = " + initiatedByClient);
-                }
-            } else if (currentClient instanceof Interruptable) {
-                ((Interruptable) currentClient).cancel();
-
-                // Only post the reset runnable for non-cleanup clients. Cleanup clients should
-                // never be forcibly stopped since they ensure synchronization between HAL and
-                // framework. Thus, we should instead just start the pending client once cleanup
-                // finishes instead of using the reset runnable.
-                mHandler.removeCallbacks(mResetClientState);
-                mHandler.postDelayed(mResetClientState, CANCEL_TIMEOUT_LIMIT);
-            }
-            mPendingClient = newClient;
-        } else if (newClient != null) {
-            // For BiometricPrompt clients, do not start until
-            // <Biometric>Service#startPreparedClient is called. BiometricService waits until all
-            // modalities are ready before initiating authentication.
-            if (newClient instanceof AuthenticationClient) {
-                AuthenticationClient<?> client = (AuthenticationClient<?>) newClient;
-                if (client.isBiometricPrompt()) {
-                    if (DEBUG) Slog.v(getTag(), "Returning cookie: " + client.getCookie());
-                    mCurrentClient = newClient;
-                    if (mBiometricService == null) {
-                        mBiometricService = IBiometricService.Stub.asInterface(
-                                ServiceManager.getService(Context.BIOMETRIC_SERVICE));
-                    }
-                    try {
-                        mBiometricService.onReadyForAuthentication(client.getCookie());
-                    } catch (RemoteException e) {
-                        Slog.e(getTag(), "Remote exception", e);
-                    }
-                    return;
-                }
-            }
-
-            // We are not a BiometricPrompt client, start the client immediately
-            mCurrentClient = newClient;
-            startCurrentClient(mCurrentClient.getCookie());
-        }
-    }
-
-    protected void startCurrentClient(int cookie) {
-        if (mCurrentClient == null) {
-            Slog.e(getTag(), "Trying to start null client!");
-            return;
-        }
-
-        if (DEBUG) Slog.v(getTag(), "Starting client "
-                + mCurrentClient.getClass().getSimpleName()
-                + "(" + mCurrentClient.getOwnerString() + ")"
-                + " targetUserId: " + mCurrentClient.getTargetUserId()
-                + " currentUserId: " + mCurrentUserId
-                + " cookie: " + cookie + "/" + mCurrentClient.getCookie());
-
-        if (cookie != mCurrentClient.getCookie()) {
-            Slog.e(getTag(), "Mismatched cookie");
-            return;
-        }
-
-        final T daemon = mCurrentClient.getFreshDaemon();
-        if (daemon == null) {
-            Slog.e(getTag(), "Daemon null, unable to start: "
-                    + mCurrentClient.getClass().getSimpleName());
-            mCurrentClient.unableToStart();
-            mCurrentClient = null;
-            return;
-        }
-
-        mCurrentClient.start(mClientFinishCallback);
-        notifyClientActiveCallbacks(true);
-    }
-
-    protected void removeClient(ClientMonitor<?> client) {
-        if (client != null) {
-            client.destroy();
-            if (client != mCurrentClient && mCurrentClient != null) {
-                Slog.w(getTag(), "Unexpected client: " + client.getOwnerString() + "expected: "
-                        + mCurrentClient.getOwnerString());
-            }
-        }
-        if (mCurrentClient != null) {
-            if (DEBUG) Slog.v(getTag(), "Done with client: "
-                    + mCurrentClient.getClass().getSimpleName()
-                    + "(" + mCurrentClient.getOwnerString() + ")");
-            mCurrentClient = null;
-        }
-        if (mPendingClient == null) {
-            notifyClientActiveCallbacks(false);
-        }
-    }
-
-    /**
-     * Populates existing authenticator ids. To be used only during the start of the service.
-     */
-    protected 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(getContext()).getUsers(true /* excludeDying */)) {
-            int userId = user.id;
-            if (!mAuthenticatorIds.containsKey(userId)) {
-                updateActiveGroup(userId);
-            }
-        }
-
-        t = System.currentTimeMillis() - t;
-        if (t > 1000) {
-            Slog.w(getTag(), "loadAuthenticatorIds() taking too long: " + t + "ms");
-        }
-    }
-
-    protected boolean isRestricted() {
-        // Only give privileged apps (like Settings) access to biometric info
-        final boolean restricted = !hasPermission(getManageBiometricPermission());
-        return restricted;
-    }
-
-    protected boolean hasPermission(String permission) {
-        return getContext().checkCallingOrSelfPermission(permission)
-                == PackageManager.PERMISSION_GRANTED;
-    }
-
-    protected void checkPermission(String permission) {
-        getContext().enforceCallingOrSelfPermission(permission,
-                "Must have " + permission + " permission.");
-    }
-
-    /**
-     * @return authenticator id for the calling user
-     */
-    protected long getAuthenticatorId(int callingUserId) {
-        return mAuthenticatorIds.getOrDefault(callingUserId, 0L);
-    }
-
-    /**
-     * This method should be called upon connection to the daemon, and when user switches.
-     */
-    protected abstract void doTemplateCleanupForUser(int userId);
-
-    /**
-     * This method is called when the user switches. Implementations should probably notify the
-     * HAL.
-     */
-    protected void handleUserSwitching(int userId) {
-        if (getCurrentClient() instanceof InternalCleanupClient) {
-            Slog.w(getTag(), "User switched while performing cleanup");
-        }
-        updateActiveGroup(userId);
-        doTemplateCleanupForUser(userId);
-    }
-
-    private void listenForUserSwitches() {
-        try {
-            ActivityManager.getService().registerUserSwitchObserver(
-                    new SynchronousUserSwitchObserver() {
-                        @Override
-                        public void onUserSwitching(int newUserId) {
-                            mHandler.obtainMessage(MSG_USER_SWITCHING, newUserId, 0 /* unused */)
-                                    .sendToTarget();
-                        }
-                    }, getTag());
-        } catch (RemoteException e) {
-            Slog.w(getTag(), "Failed to listen for user switching event" ,e);
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/ClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/ClientMonitor.java
index 8cabdf5..8b27781 100644
--- a/services/core/java/com/android/server/biometrics/sensors/ClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/ClientMonitor.java
@@ -34,7 +34,7 @@
 public abstract class ClientMonitor<T> extends LoggableMonitor implements IBinder.DeathRecipient {
 
     private static final String TAG = "Biometrics/ClientMonitor";
-    protected static final boolean DEBUG = BiometricServiceBase.DEBUG;
+    protected static final boolean DEBUG = true;
 
     // Counter used to distinguish between ClientMonitor instances to help debugging.
     private static int sCount = 0;
diff --git a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
index a3d9677..163f29e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
@@ -35,12 +35,17 @@
 
     protected final byte[] mHardwareAuthToken;
     protected final int mTimeoutSec;
-    private final BiometricUtils mBiometricUtils;
+    protected final BiometricUtils mBiometricUtils;
     private final boolean mShouldVibrate;
 
     private long mEnrollmentStartTimeMs;
     private boolean mAlreadyCancelled;
 
+    /**
+     * @return true if the user has already enrolled the maximum number of templates.
+     */
+    protected abstract boolean hasReachedEnrollmentLimit();
+
     public EnrollClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
             @NonNull byte[] hardwareAuthToken, @NonNull String owner, @NonNull BiometricUtils utils,
@@ -82,6 +87,12 @@
     public void start(@NonNull FinishCallback finishCallback) {
         super.start(finishCallback);
 
+        if (hasReachedEnrollmentLimit()) {
+            Slog.e(TAG, "Reached enrollment limit");
+            finishCallback.onClientFinished(this, false /* success */);
+            return;
+        }
+
         mEnrollmentStartTimeMs = System.currentTimeMillis();
         startHalOperation();
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/LockoutResetTracker.java b/services/core/java/com/android/server/biometrics/sensors/LockoutResetDispatcher.java
similarity index 96%
rename from services/core/java/com/android/server/biometrics/sensors/LockoutResetTracker.java
rename to services/core/java/com/android/server/biometrics/sensors/LockoutResetDispatcher.java
index 22c10b3..f4997d4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/LockoutResetTracker.java
+++ b/services/core/java/com/android/server/biometrics/sensors/LockoutResetDispatcher.java
@@ -32,7 +32,7 @@
  * ends. This class keeps track of all client callbacks. Individual sensors should notify this
  * when lockout for a specific sensor has been reset.
  */
-public class LockoutResetTracker implements IBinder.DeathRecipient {
+public class LockoutResetDispatcher implements IBinder.DeathRecipient {
 
     private static final String TAG = "LockoutResetTracker";
 
@@ -79,7 +79,7 @@
         }
     }
 
-    public LockoutResetTracker(Context context) {
+    public LockoutResetDispatcher(Context context) {
         mContext = context;
         mClientCallbacks = new ArrayList<>();
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/LoggableMonitor.java b/services/core/java/com/android/server/biometrics/sensors/LoggableMonitor.java
index a7c63f7..1a4216f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/LoggableMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/LoggableMonitor.java
@@ -39,10 +39,6 @@
     private final int mStatsClient;
     private long mFirstAcquireTimeMs;
 
-    protected long getFirstAcquireTimeMs() {
-        return mFirstAcquireTimeMs;
-    }
-
     /**
      * Only valid for AuthenticationClient.
      * @return true if the client is authenticating for a crypto operation.
@@ -62,6 +58,12 @@
         mStatsClient = statsClient;
     }
 
+    private boolean isAnyFieldUnknown() {
+        return mStatsModality == BiometricsProtoEnums.MODALITY_UNKNOWN
+                || mStatsAction == BiometricsProtoEnums.ACTION_UNKNOWN
+                || mStatsClient == BiometricsProtoEnums.CLIENT_UNKNOWN;
+    }
+
     protected final void logOnAcquired(Context context, int acquiredInfo, int vendorCode,
             int targetUserId) {
 
@@ -86,6 +88,11 @@
                     + ", AcquiredInfo: " + acquiredInfo
                     + ", VendorCode: " + vendorCode);
         }
+
+        if (isAnyFieldUnknown()) {
+            return;
+        }
+
         FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ACQUIRED,
                 mStatsModality,
                 targetUserId,
@@ -114,6 +121,11 @@
         } else {
             Slog.v(TAG, "Error latency: " + latency);
         }
+
+        if (isAnyFieldUnknown()) {
+            return;
+        }
+
         FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ERROR_OCCURRED,
                 mStatsModality,
                 targetUserId,
@@ -157,6 +169,10 @@
             Slog.v(TAG, "Authentication latency: " + latency);
         }
 
+        if (isAnyFieldUnknown()) {
+            return;
+        }
+
         FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_AUTHENTICATED,
                 mStatsModality,
                 targetUserId,
@@ -179,6 +195,10 @@
             Slog.v(TAG, "Enroll latency: " + latency);
         }
 
+        if (isAnyFieldUnknown()) {
+            return;
+        }
+
         FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ENROLLED,
                 mStatsModality,
                 targetUserId,
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/Face10.java
new file mode 100644
index 0000000..c4ea0a8
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/Face10.java
@@ -0,0 +1,649 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.face;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.NotificationManager;
+import android.app.SynchronousUserSwitchObserver;
+import android.app.UserSwitchObserver;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.BiometricsProtoEnums;
+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.IFaceServiceReceiver;
+import android.os.Build;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IHwBinder;
+import android.os.Looper;
+import android.os.NativeHandle;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.util.Slog;
+
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.sensors.AcquisitionClient;
+import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.ClientMonitor;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.EnumerateConsumer;
+import com.android.server.biometrics.sensors.Interruptable;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.PerformanceTracker;
+import com.android.server.biometrics.sensors.RemovalConsumer;
+import com.android.server.biometrics.sensors.fingerprint.FingerprintUpdateActiveUserClient;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Supports a single instance of the {@link android.hardware.biometrics.face.V1_0} or
+ * its extended minor versions.
+ */
+class Face10 implements IHwBinder.DeathRecipient {
+
+    private static final String TAG = "Face10";
+    private static final int ENROLL_TIMEOUT_SEC = 75;
+    static final String NOTIFICATION_TAG = "FaceService";
+    static final int NOTIFICATION_ID = 1;
+
+    @NonNull private final Context mContext;
+    @NonNull private final BiometricScheduler mScheduler;
+    @NonNull private final Handler mHandler;
+    @NonNull private final ClientMonitor.LazyDaemon<IBiometricsFace> mLazyDaemon;
+    @NonNull private final LockoutResetDispatcher mLockoutResetDispatcher;
+    @NonNull private final LockoutHalImpl mLockoutTracker;
+    @NonNull private final UsageStats mUsageStats;
+    @NonNull private NotificationManager mNotificationManager;
+    private final int mSensorId;
+    @NonNull private final Map<Integer, Long> mAuthenticatorIds;
+
+    @Nullable private IBiometricsFace mDaemon;
+    private int mCurrentUserId = UserHandle.USER_NULL;
+
+    private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() {
+        @Override
+        public void onUserSwitching(int newUserId) {
+            scheduleInternalCleanup(newUserId);
+        }
+    };
+
+    private final IBiometricsFaceClientCallback mDaemonCallback =
+            new IBiometricsFaceClientCallback.Stub() {
+        @Override
+        public void onEnrollResult(long deviceId, int faceId, int userId, int remaining) {
+            mHandler.post(() -> {
+                final CharSequence name = FaceUtils.getInstance()
+                        .getUniqueName(mContext, userId);
+                final Face face = new Face(name, faceId, deviceId);
+
+                final ClientMonitor<?> client = mScheduler.getCurrentClient();
+                if (!(client instanceof FaceEnrollClient)) {
+                    Slog.e(TAG, "onEnrollResult for non-enroll client: "
+                            + Utils.getClientName(client));
+                    return;
+                }
+
+                final FaceEnrollClient enrollClient = (FaceEnrollClient) client;
+                enrollClient.onEnrollResult(face, remaining);
+            });
+        }
+
+        @Override
+        public void onAuthenticated(long deviceId, int faceId, int userId, ArrayList<Byte> token) {
+            mHandler.post(() -> {
+                final ClientMonitor<?> client = mScheduler.getCurrentClient();
+                if (!(client instanceof FaceAuthenticationClient)) {
+                    Slog.e(TAG, "onAuthenticated for non-authentication client: "
+                            + Utils.getClientName(client));
+                    return;
+                }
+
+                final FaceAuthenticationClient authenticationClient =
+                        (FaceAuthenticationClient) client;
+                final boolean authenticated = faceId != 0;
+                final Face face = new Face("", faceId, deviceId);
+                authenticationClient.onAuthenticated(face, authenticated, token);
+            });
+        }
+
+        @Override
+        public void onAcquired(long deviceId, int userId, int acquiredInfo, int vendorCode) {
+            mHandler.post(() -> {
+                final ClientMonitor<?> client = mScheduler.getCurrentClient();
+                if (!(client instanceof AcquisitionClient)) {
+                    Slog.e(TAG, "onAcquired for non-acquire client: "
+                            + Utils.getClientName(client));
+                    return;
+                }
+
+                final AcquisitionClient<?> acquisitionClient = (AcquisitionClient<?>) client;
+                acquisitionClient.onAcquired(acquiredInfo, vendorCode);
+            });
+        }
+
+        @Override
+        public void onError(long deviceId, int userId, int error, int vendorCode) {
+            mHandler.post(() -> {
+                final ClientMonitor<?> client = mScheduler.getCurrentClient();
+                Slog.d(TAG, "handleError"
+                        + ", client: " + (client != null ? client.getOwnerString() : null)
+                        + ", error: " + error
+                        + ", vendorCode: " + vendorCode);
+                if (!(client instanceof Interruptable)) {
+                    Slog.e(TAG, "onError for non-error consumer: " + Utils.getClientName(client));
+                    return;
+                }
+
+                final Interruptable interruptable = (Interruptable) client;
+                interruptable.onError(error, vendorCode);
+
+                if (error == BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE) {
+                    Slog.e(TAG, "Got ERROR_HW_UNAVAILABLE");
+                    mDaemon = null;
+                    mCurrentUserId = UserHandle.USER_NULL;
+                }
+            });
+        }
+
+        @Override
+        public void onRemoved(long deviceId, ArrayList<Integer> removed, int userId) {
+            mHandler.post(() -> {
+                final ClientMonitor<?> client = mScheduler.getCurrentClient();
+                if (!(client instanceof RemovalConsumer)) {
+                    Slog.e(TAG, "onRemoved for non-removal consumer: "
+                            + Utils.getClientName(client));
+                    return;
+                }
+
+                final RemovalConsumer removalConsumer = (RemovalConsumer) client;
+
+                if (!removed.isEmpty()) {
+                    // Convert to old fingerprint-like behavior, where remove() receives one removal
+                    // at a time. This way, remove can share some more common code.
+                    for (int i = 0; i < removed.size(); i++) {
+                        final int id = removed.get(i);
+                        final Face face = new Face("", id, deviceId);
+                        final int remaining = removed.size() - i - 1;
+                        Slog.d(TAG, "Removed, faceId: " + id + ", remaining: " + remaining);
+                        removalConsumer.onRemoved(face, remaining);
+                    }
+                } else {
+                    final Face face = new Face("", 0 /* identifier */, deviceId);
+                    removalConsumer.onRemoved(face, 0 /* remaining */);
+                }
+
+                Settings.Secure.putIntForUser(mContext.getContentResolver(),
+                        Settings.Secure.FACE_UNLOCK_RE_ENROLL, 0, UserHandle.USER_CURRENT);
+            });
+        }
+
+        @Override
+        public void onEnumerate(long deviceId, ArrayList<Integer> faceIds, int userId) {
+            mHandler.post(() -> {
+                final ClientMonitor<?> client = mScheduler.getCurrentClient();
+                if (!(client instanceof EnumerateConsumer)) {
+                    Slog.e(TAG, "onEnumerate for non-enumerate consumer: "
+                            + Utils.getClientName(client));
+                    return;
+                }
+
+                final EnumerateConsumer enumerateConsumer = (EnumerateConsumer) client;
+
+                if (!faceIds.isEmpty()) {
+                    // Convert to old fingerprint-like behavior, where enumerate() receives one
+                    // template at a time. This way, enumerate can share some more common code.
+                    for (int i = 0; i < faceIds.size(); i++) {
+                        final Face face = new Face("", faceIds.get(i), deviceId);
+                        enumerateConsumer.onEnumerationResult(face, faceIds.size() - i - 1);
+                    }
+                } else {
+                    // For face, the HIDL contract is to receive an empty list when there are no
+                    // templates enrolled. Send a null identifier since we don't consume them
+                    // anywhere, and send remaining == 0 so this code can be shared with
+                    // Fingerprint@2.1
+                    enumerateConsumer.onEnumerationResult(null /* identifier */, 0);
+                }
+            });
+        }
+
+        @Override
+        public void onLockoutChanged(long duration) {
+            mHandler.post(() -> {
+                Slog.d(TAG, "onLockoutChanged: " + duration);
+                final @LockoutTracker.LockoutMode int lockoutMode;
+                if (duration == 0) {
+                    lockoutMode = LockoutTracker.LOCKOUT_NONE;
+                } else if (duration == -1 || duration == Long.MAX_VALUE) {
+                    lockoutMode = LockoutTracker.LOCKOUT_PERMANENT;
+                } else {
+                    lockoutMode = LockoutTracker.LOCKOUT_TIMED;
+                }
+
+                mLockoutTracker.setCurrentUserLockoutMode(lockoutMode);
+
+                if (duration == 0) {
+                    mLockoutResetDispatcher.notifyLockoutResetCallbacks(mSensorId);
+                }
+            });
+        }
+    };
+
+    Face10(@NonNull Context context, int sensorId,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
+        mContext = context;
+        mSensorId = sensorId;
+        mScheduler = new BiometricScheduler(TAG, null /* gestureAvailabilityTracker */);
+        mHandler = new Handler(Looper.getMainLooper());
+        mUsageStats = new UsageStats(context);
+        mAuthenticatorIds = new HashMap<>();
+        mLazyDaemon = Face10.this::getDaemon;
+        mNotificationManager = mContext.getSystemService(NotificationManager.class);
+        mLockoutTracker = new LockoutHalImpl();
+        mLockoutResetDispatcher = lockoutResetDispatcher;
+
+        try {
+            ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Unable to register user switch observer");
+        }
+    }
+
+    @Override
+    public void serviceDied(long cookie) {
+        Slog.e(TAG, "HAL died");
+        mHandler.post(() -> {
+            PerformanceTracker.getInstanceForSensorId(mSensorId)
+                    .incrementHALDeathCount();
+            mDaemon = null;
+            mCurrentUserId = UserHandle.USER_NULL;
+
+            final ClientMonitor<?> client = mScheduler.getCurrentClient();
+            if (client instanceof Interruptable) {
+                Slog.e(TAG, "Sending ERROR_HW_UNAVAILABLE for client: " + client);
+                final Interruptable interruptable = (Interruptable) client;
+                interruptable.onError(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE,
+                        0 /* vendorCode */);
+
+                mScheduler.recordCrashState();
+
+                FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED,
+                        BiometricsProtoEnums.MODALITY_FACE,
+                        BiometricsProtoEnums.ISSUE_HAL_DEATH);
+            }
+        });
+    }
+
+    private synchronized IBiometricsFace getDaemon() {
+        if (mDaemon != null) {
+            return mDaemon;
+        }
+
+        Slog.d(TAG, "Daemon was null, reconnecting, current operation: "
+                + mScheduler.getCurrentClient());
+
+        try {
+            mDaemon = IBiometricsFace.getService();
+        } catch (java.util.NoSuchElementException e) {
+            // Service doesn't exist or cannot be opened.
+            Slog.w(TAG, "NoSuchElementException", e);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to get face HAL", e);
+        }
+
+        if (mDaemon == null) {
+            Slog.w(TAG, "Face HAL not available");
+            return null;
+        }
+
+        mDaemon.asBinder().linkToDeath(this, 0 /* flags */);
+
+        // HAL ID for these HIDL versions are only used to determine if callbacks have been
+        // successfully set.
+        long halId = 0;
+        try {
+            halId = mDaemon.setCallback(mDaemonCallback).value;
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to set callback for face HAL", e);
+            mDaemon = null;
+        }
+
+        Slog.d(TAG, "Face HAL ready, HAL ID: " + halId);
+        if (halId != 0) {
+            scheduleLoadAuthenticatorIds();
+            scheduleInternalCleanup(ActivityManager.getCurrentUser());
+        } else {
+            Slog.e(TAG, "Unable to set callback");
+            mDaemon = null;
+        }
+
+        return mDaemon;
+    }
+
+    @LockoutTracker.LockoutMode int getLockoutModeForUser(int userId) {
+        return mLockoutTracker.getLockoutModeForUser(userId);
+    }
+
+    private void scheduleLoadAuthenticatorIds() {
+        // Note that this can be performed on the scheduler (as opposed to being done immediately
+        // when the HAL is (re)loaded, since
+        // 1) If this is truly the first time it's being performed (e.g. system has just started),
+        //    this will be run very early and way before any applications need to generate keys.
+        // 2) If this is being performed to refresh the authenticatorIds (e.g. HAL crashed and has
+        //    just been reloaded), the framework already has a cache of the authenticatorIds. This
+        //    is safe because authenticatorIds only change when A) new template has been enrolled,
+        //    or B) all templates are removed.
+        mHandler.post(() -> {
+            for (UserInfo user : UserManager.get(mContext).getUsers(true /* excludeDying */)) {
+                final int targetUserId = user.id;
+                if (!mAuthenticatorIds.containsKey(targetUserId)) {
+                    scheduleUpdateActiveUserWithoutHandler(targetUserId);
+                }
+            }
+        });
+    }
+
+    /**
+     * Schedules the {@link FingerprintUpdateActiveUserClient} without posting the work onto the
+     * handler. Many/most APIs are user-specific. However, the HAL requires explicit "setActiveUser"
+     * invocation prior to authenticate/enroll/etc. Thus, internally we usually want to schedule
+     * this operation on the same lambda/runnable as those operations so that the ordering is
+     * correct.
+     */
+    private void scheduleUpdateActiveUserWithoutHandler(int targetUserId) {
+        final boolean hasEnrolled = !getEnrolledFaces(targetUserId).isEmpty();
+        final FaceUpdateActiveUserClient client = new FaceUpdateActiveUserClient(mContext,
+                mLazyDaemon, targetUserId, mContext.getOpPackageName(), mSensorId, mCurrentUserId,
+                hasEnrolled, mAuthenticatorIds);
+        mScheduler.scheduleClientMonitor(client, (clientMonitor, success) -> {
+            if (success) {
+                mCurrentUserId = targetUserId;
+            }
+        });
+    }
+
+    void scheduleResetLockout(int userId, @NonNull byte[] hardwareAuthToken) {
+        mHandler.post(() -> {
+            if (getEnrolledFaces(userId).isEmpty()) {
+                Slog.w(TAG, "Ignoring lockout reset, no templates enrolled for user: " + userId);
+                return;
+            }
+
+            scheduleUpdateActiveUserWithoutHandler(userId);
+
+            final FaceResetLockoutClient client = new FaceResetLockoutClient(mContext,
+                    mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId,
+                    hardwareAuthToken);
+            mScheduler.scheduleClientMonitor(client);
+        });
+    }
+
+    void scheduleSetFeature(@NonNull IBinder token, int userId, int feature, boolean enabled,
+            @NonNull byte[] hardwareAuthToken, @NonNull IFaceServiceReceiver receiver,
+            @NonNull String opPackageName) {
+        mHandler.post(() -> {
+            final List<Face> faces = getEnrolledFaces(userId);
+            if (faces.isEmpty()) {
+                Slog.w(TAG, "Ignoring setFeature, no templates enrolled for user: " + userId);
+                return;
+            }
+
+            scheduleUpdateActiveUserWithoutHandler(userId);
+
+            final int faceId = faces.get(0).getBiometricId();
+            final FaceSetFeatureClient client = new FaceSetFeatureClient(mContext,
+                    mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
+                    opPackageName, mSensorId, feature, enabled, hardwareAuthToken, faceId);
+            mScheduler.scheduleClientMonitor(client);
+        });
+    }
+
+    void scheduleGetFeature(@NonNull IBinder token, int userId, int feature,
+            @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
+        mHandler.post(() -> {
+            final List<Face> faces = getEnrolledFaces(userId);
+            if (faces.isEmpty()) {
+                Slog.w(TAG, "Ignoring getFeature, no templates enrolled for user: " + userId);
+                return;
+            }
+
+            scheduleUpdateActiveUserWithoutHandler(userId);
+
+            final int faceId = faces.get(0).getBiometricId();
+            final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext,
+                    mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
+                    opPackageName, mSensorId, feature, faceId);
+            mScheduler.scheduleClientMonitor(client);
+        });
+    }
+
+    void scheduleGenerateChallenge(@NonNull IBinder token, @NonNull IFaceServiceReceiver receiver,
+            @NonNull String opPackageName) {
+        mHandler.post(() -> {
+            final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext,
+                    mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), opPackageName,
+                    mSensorId);
+            mScheduler.scheduleClientMonitor(client);
+        });
+    }
+
+    void scheduleRevokeChallenge(@NonNull IBinder token, @NonNull String owner) {
+        mHandler.post(() -> {
+            final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext,
+                    mLazyDaemon, token, owner, mSensorId);
+            mScheduler.scheduleClientMonitor(client);
+        });
+    }
+
+    void scheduleEnroll(@NonNull IBinder token, @NonNull byte[] hardwareAuthToken, int userId,
+            @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName,
+            @NonNull int[] disabledFeatures, @Nullable NativeHandle surfaceHandle) {
+        mHandler.post(() -> {
+            scheduleUpdateActiveUserWithoutHandler(userId);
+
+            mNotificationManager.cancelAsUser(NOTIFICATION_TAG, NOTIFICATION_ID,
+                    UserHandle.CURRENT);
+
+            final FaceEnrollClient client = new FaceEnrollClient(mContext, mLazyDaemon, token,
+                    new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
+                    opPackageName, FaceUtils.getInstance(), disabledFeatures, ENROLL_TIMEOUT_SEC,
+                    surfaceHandle, mSensorId);
+
+            mScheduler.scheduleClientMonitor(client, ((clientMonitor, success) -> {
+                if (success) {
+                    // Update authenticatorIds
+                    scheduleUpdateActiveUserWithoutHandler(client.getTargetUserId());
+                }
+            }));
+        });
+    }
+
+    void cancelEnrollment(@NonNull IBinder token) {
+        mHandler.post(() -> {
+            mScheduler.cancelEnrollment(token);
+        });
+    }
+
+    void scheduleAuthenticate(@NonNull IBinder token, long operationId, int userId, int cookie,
+            @NonNull ClientMonitorCallbackConverter receiver, @NonNull String opPackageName,
+            boolean restricted, int statsClient) {
+        mHandler.post(() -> {
+            scheduleUpdateActiveUserWithoutHandler(userId);
+
+            final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorId);
+            final FaceAuthenticationClient client = new FaceAuthenticationClient(mContext,
+                    mLazyDaemon, token, receiver, userId, operationId, restricted, opPackageName,
+                    cookie, false /* requireConfirmation */, mSensorId, isStrongBiometric,
+                    statsClient, mLockoutTracker, mUsageStats);
+            mScheduler.scheduleClientMonitor(client);
+        });
+    }
+
+    void startPreparedClient(int cookie) {
+        mHandler.post(() -> {
+            mScheduler.startPreparedClient(cookie);
+        });
+    }
+
+    void cancelAuthentication(@NonNull IBinder token) {
+        mHandler.post(() -> {
+            mScheduler.cancelAuthentication(token);
+        });
+    }
+
+    void scheduleRemove(@NonNull IBinder token, int faceId, int userId,
+            @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
+        mHandler.post(() -> {
+            scheduleUpdateActiveUserWithoutHandler(userId);
+
+            final FaceRemovalClient client = new FaceRemovalClient(mContext, mLazyDaemon, token,
+                    new ClientMonitorCallbackConverter(receiver), faceId, userId, opPackageName,
+                    FaceUtils.getInstance(), mSensorId, mAuthenticatorIds);
+            mScheduler.scheduleClientMonitor(client);
+        });
+    }
+
+    private void scheduleInternalCleanup(int userId) {
+        mHandler.post(() -> {
+            scheduleUpdateActiveUserWithoutHandler(userId);
+
+            final List<Face> enrolledList = getEnrolledFaces(userId);
+            final FaceInternalCleanupClient client = new FaceInternalCleanupClient(mContext,
+                    mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId, enrolledList,
+                    FaceUtils.getInstance(), mAuthenticatorIds);
+            mScheduler.scheduleClientMonitor(client);
+        });
+    }
+
+    boolean isHardwareDetected() {
+        final IBiometricsFace daemon = getDaemon();
+        return daemon != null;
+    }
+
+    List<Face> getEnrolledFaces(int userId) {
+        return FaceUtils.getInstance().getBiometricsForUser(mContext, userId);
+    }
+
+    long getAuthenticatorId(int userId) {
+        return mAuthenticatorIds.get(userId);
+    }
+
+    public void dump(@NonNull PrintWriter pw) {
+        PerformanceTracker performanceTracker =
+                PerformanceTracker.getInstanceForSensorId(mSensorId);
+
+        JSONObject dump = new JSONObject();
+        try {
+            dump.put("service", "Face Manager");
+
+            JSONArray sets = new JSONArray();
+            for (UserInfo user : UserManager.get(mContext).getUsers()) {
+                final int userId = user.getUserHandle().getIdentifier();
+                final int N = FaceUtils.getInstance().getBiometricsForUser(mContext, userId).size();
+                JSONObject set = new JSONObject();
+                set.put("id", userId);
+                set.put("count", N);
+                set.put("accept", performanceTracker.getAcceptForUser(userId));
+                set.put("reject", performanceTracker.getRejectForUser(userId));
+                set.put("acquire", performanceTracker.getAcquireForUser(userId));
+                set.put("lockout", performanceTracker.getTimedLockoutForUser(userId));
+                set.put("permanentLockout", performanceTracker.getPermanentLockoutForUser(userId));
+                // cryptoStats measures statistics about secure face transactions
+                // (e.g. to unlock password storage, make secure purchases, etc.)
+                set.put("acceptCrypto", performanceTracker.getAcceptCryptoForUser(userId));
+                set.put("rejectCrypto", performanceTracker.getRejectCryptoForUser(userId));
+                set.put("acquireCrypto", performanceTracker.getAcquireCryptoForUser(userId));
+                sets.put(set);
+            }
+
+            dump.put("prints", sets);
+        } catch (JSONException e) {
+            Slog.e(TAG, "dump formatting failure", e);
+        }
+        pw.println(dump);
+        pw.println("HAL deaths since last reboot: " + performanceTracker.getHALDeathCount());
+
+        mUsageStats.print(pw);
+    }
+
+    public void dumpHal(@NonNull FileDescriptor fd, @NonNull String[] args) {
+        // WARNING: CDD restricts image data from leaving TEE unencrypted on
+        //          production devices:
+        // [C-1-10] MUST not allow unencrypted access to identifiable biometric
+        //          data or any data derived from it (such as embeddings) to the
+        //         Application Processor outside the context of the TEE.
+        //  As such, this API should only be enabled for testing purposes on
+        //  engineering and userdebug builds.  All modules in the software stack
+        //  MUST enforce final build products do NOT have this functionality.
+        //  Additionally, the following check MUST NOT be removed.
+        if (!(Build.IS_ENG || Build.IS_USERDEBUG)) {
+            return;
+        }
+
+        // Additionally, this flag allows turning off face for a device
+        // (either permanently through the build or on an individual device).
+        if (SystemProperties.getBoolean("ro.face.disable_debug_data", false)
+                || SystemProperties.getBoolean("persist.face.disable_debug_data", false)) {
+            return;
+        }
+
+        // The debug method takes two file descriptors. The first is for text
+        // output, which we will drop.  The second is for binary data, which
+        // will be the protobuf data.
+        final IBiometricsFace daemon = getDaemon();
+        if (daemon != null) {
+            FileOutputStream devnull = null;
+            try {
+                devnull = new FileOutputStream("/dev/null");
+                final NativeHandle handle = new NativeHandle(
+                        new FileDescriptor[] { devnull.getFD(), fd },
+                        new int[0], false);
+                daemon.debug(handle, new ArrayList<String>(Arrays.asList(args)));
+            } catch (IOException | RemoteException ex) {
+                Slog.d(TAG, "error while reading face debugging data", ex);
+            } finally {
+                if (devnull != null) {
+                    try {
+                        devnull.close();
+                    } catch (IOException ex) {
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticationClient.java
index 118cadc..21bda74 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticationClient.java
@@ -21,7 +21,6 @@
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
-import android.app.TaskStackListener;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
@@ -67,11 +66,10 @@
             @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
             boolean restricted, String owner, int cookie, boolean requireConfirmation, int sensorId,
             boolean isStrongBiometric, int statsClient,
-            @NonNull TaskStackListener taskStackListener,
             @NonNull LockoutTracker lockoutTracker, @NonNull UsageStats usageStats) {
         super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
                 owner, cookie, requireConfirmation, sensorId, isStrongBiometric,
-                BiometricsProtoEnums.MODALITY_FACE, statsClient, taskStackListener,
+                BiometricsProtoEnums.MODALITY_FACE, statsClient, null /* taskStackListener */,
                 lockoutTracker);
         mNotificationManager = context.getSystemService(NotificationManager.class);
         mUsageStats = usageStats;
@@ -221,8 +219,8 @@
                     .build();
 
             mNotificationManager.createNotificationChannel(channel);
-            mNotificationManager.notifyAsUser(FaceService.NOTIFICATION_TAG,
-                    FaceService.NOTIFICATION_ID, notification,
+            mNotificationManager.notifyAsUser(Face10.NOTIFICATION_TAG,
+                    Face10.NOTIFICATION_ID, notification,
                     UserHandle.CURRENT);
         }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceEnrollClient.java
index 4f9f46a..52a8226 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceEnrollClient.java
@@ -68,6 +68,19 @@
     }
 
     @Override
+    protected boolean hasReachedEnrollmentLimit() {
+        final int limit = getContext().getResources().getInteger(
+                com.android.internal.R.integer.config_faceMaxTemplatesPerUser);
+        final int enrolled = mBiometricUtils.getBiometricsForUser(getContext(), getTargetUserId())
+                .size();
+        if (enrolled >= limit) {
+            Slog.w(TAG, "Too many faces registered, user: " + getTargetUserId());
+            return true;
+        }
+        return false;
+    }
+
+    @Override
     public void onAcquired(int acquireInfo, int vendorCode) {
         final boolean shouldSend;
         if (acquireInfo == FaceManager.FACE_ACQUIRED_VENDOR) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 7a05192..3c597a9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -16,64 +16,31 @@
 
 package com.android.server.biometrics.sensors.face;
 
-import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 import static android.Manifest.permission.MANAGE_BIOMETRIC;
 import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
 
-import android.app.ActivityManager;
-import android.app.NotificationManager;
+import android.annotation.NonNull;
 import android.content.Context;
-import android.content.pm.UserInfo;
-import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.IBiometricSensorReceiver;
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
-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.IFaceService;
 import android.hardware.face.IFaceServiceReceiver;
 import android.os.Binder;
-import android.os.Build;
-import android.os.Environment;
 import android.os.IBinder;
 import android.os.NativeHandle;
-import android.os.RemoteException;
-import android.os.SELinux;
-import android.os.SystemProperties;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.util.Slog;
 import android.view.Surface;
 
-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.biometrics.sensors.AuthenticationClient;
-import com.android.server.biometrics.sensors.BiometricServiceBase;
-import com.android.server.biometrics.sensors.BiometricUtils;
-import com.android.server.biometrics.sensors.ClientMonitor;
+import com.android.server.SystemService;
+import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.EnrollClient;
-import com.android.server.biometrics.sensors.GenerateChallengeClient;
-import com.android.server.biometrics.sensors.LockoutResetTracker;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.LockoutTracker;
-import com.android.server.biometrics.sensors.PerformanceTracker;
-import com.android.server.biometrics.sensors.RemovalClient;
-import com.android.server.biometrics.sensors.RevokeChallengeClient;
 
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.io.File;
 import java.io.FileDescriptor;
-import java.io.FileOutputStream;
-import java.io.IOException;
 import java.io.PrintWriter;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
@@ -82,177 +49,115 @@
  * The service is responsible for maintaining a list of clients and dispatching all
  * face-related events.
  */
-public class FaceService extends BiometricServiceBase<IBiometricsFace> {
+public class FaceService extends SystemService {
 
     protected static final String TAG = "FaceService";
-    private static final boolean DEBUG = true;
-    private static final String FACE_DATA_DIR = "facedata";
-    private static final String ACTION_LOCKOUT_RESET =
-            "com.android.server.biometrics.face.ACTION_LOCKOUT_RESET";
 
-
-    static final String NOTIFICATION_TAG = "FaceService";
-    static final int NOTIFICATION_ID = 1;
-
-    private final LockoutResetTracker mLockoutResetTracker;
-    private final ClientMonitor.LazyDaemon<IBiometricsFace> mLazyDaemon;
+    private Face10 mFace10;
+    private final LockoutResetDispatcher mLockoutResetDispatcher;
 
     /**
      * Receives the incoming binder calls from FaceManager.
      */
     private final class FaceServiceWrapper extends IFaceService.Stub {
-        private static final int ENROLL_TIMEOUT_SEC = 75;
-
-        /**
-         * The following methods contain common code which is shared in biometrics/common.
-         */
-
         @Override // Binder call
         public void generateChallenge(IBinder token, IFaceServiceReceiver receiver,
                 String opPackageName) {
-            checkPermission(MANAGE_BIOMETRIC);
-
-            final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(getContext(),
-                    mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), opPackageName,
-                    getSensorId());
-            generateChallengeInternal(client);
+            Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
+            mFace10.scheduleGenerateChallenge(token, receiver, opPackageName);
         }
 
         @Override // Binder call
         public void revokeChallenge(IBinder token, String owner) {
-            checkPermission(MANAGE_BIOMETRIC);
-
-            final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(getContext(),
-                    mLazyDaemon, token, owner, getSensorId());
-
-            // TODO(b/137106905): Schedule binder calls in FaceService to avoid deadlocks.
-            if (getCurrentClient() == null) {
-                // if we aren't handling any other HIDL calls (mCurrentClient == null), revoke
-                // the challenge right away.
-                revokeChallengeInternal(client);
-            } else {
-                // postpone revoking the challenge until we finish processing the current HIDL
-                // call.
-                mPendingRevokeChallenge = client;
-            }
+            Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
+            mFace10.scheduleRevokeChallenge(token, owner);
         }
 
         @Override // Binder call
         public void enroll(int userId, final IBinder token, final byte[] hardwareAuthToken,
                 final IFaceServiceReceiver receiver, final String opPackageName,
                 final int[] disabledFeatures, Surface surface) {
-            checkPermission(MANAGE_BIOMETRIC);
-            updateActiveGroup(userId);
-
-            mHandler.post(() -> {
-                mNotificationManager.cancelAsUser(NOTIFICATION_TAG, NOTIFICATION_ID,
-                        UserHandle.CURRENT);
-            });
-
-            final FaceEnrollClient client = new FaceEnrollClient(getContext(), mLazyDaemon, token,
-                    new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
-                    opPackageName, getBiometricUtils(), disabledFeatures, ENROLL_TIMEOUT_SEC,
-                    convertSurfaceToNativeHandle(surface), getSensorId());
-
-            enrollInternal(client, userId);
+            Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
+            mFace10.scheduleEnroll(token, hardwareAuthToken, userId, receiver, opPackageName,
+                    disabledFeatures, convertSurfaceToNativeHandle(surface));
         }
 
         @Override // Binder call
         public void enrollRemotely(int userId, final IBinder token, final byte[] hardwareAuthToken,
                 final IFaceServiceReceiver receiver, final String opPackageName,
                 final int[] disabledFeatures) {
-            checkPermission(MANAGE_BIOMETRIC);
+            Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
             // TODO(b/145027036): Implement this.
         }
 
         @Override // Binder call
         public void cancelEnrollment(final IBinder token) {
-            checkPermission(MANAGE_BIOMETRIC);
-            cancelEnrollmentInternal(token);
+            Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
+            mFace10.cancelEnrollment(token);
         }
 
         @Override // Binder call
-        public void authenticate(final IBinder token, final long opId, int userId,
+        public void authenticate(final IBinder token, final long operationId, int userId,
                 final IFaceServiceReceiver receiver, final String opPackageName) {
-            checkPermission(USE_BIOMETRIC_INTERNAL);
-            updateActiveGroup(userId);
+            Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
 
-            final boolean restricted = isRestricted();
-            final int statsClient = isKeyguard(opPackageName) ? BiometricsProtoEnums.CLIENT_KEYGUARD
+            final boolean restricted = false; // Face APIs are private
+            final int statsClient = Utils.isKeyguard(getContext(), opPackageName)
+                    ? BiometricsProtoEnums.CLIENT_KEYGUARD
                     : BiometricsProtoEnums.CLIENT_UNKNOWN;
-            final FaceAuthenticationClient client = new FaceAuthenticationClient(getContext(),
-                    mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId, opId,
-                    restricted, opPackageName, 0 /* cookie */, false /* requireConfirmation */,
-                    getSensorId(), isStrongBiometric(), statsClient, mTaskStackListener,
-                    mLockoutTracker, mUsageStats);
-            authenticateInternal(client, opPackageName);
+            mFace10.scheduleAuthenticate(token, operationId, userId, 0 /* cookie */,
+                    new ClientMonitorCallbackConverter(receiver), opPackageName, restricted,
+                    statsClient);
         }
 
         @Override // Binder call
-        public void prepareForAuthentication(boolean requireConfirmation, IBinder token, long opId,
-                int userId, IBiometricSensorReceiver sensorReceiver,
+        public void prepareForAuthentication(boolean requireConfirmation, IBinder token,
+                long operationId, int userId, IBiometricSensorReceiver sensorReceiver,
                 String opPackageName, int cookie, int callingUid, int callingPid,
                 int callingUserId) {
-            checkPermission(USE_BIOMETRIC_INTERNAL);
-            updateActiveGroup(userId);
+            Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
 
             final boolean restricted = true; // BiometricPrompt is always restricted
-            final FaceAuthenticationClient client = new FaceAuthenticationClient(getContext(),
-                    mLazyDaemon, token, new ClientMonitorCallbackConverter(sensorReceiver), userId,
-                    opId, restricted, opPackageName, cookie, requireConfirmation, getSensorId(),
-                    isStrongBiometric(), BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
-                    mTaskStackListener, mLockoutTracker, mUsageStats);
-            authenticateInternal(client, opPackageName);
+            mFace10.scheduleAuthenticate(token, operationId, userId, cookie,
+                    new ClientMonitorCallbackConverter(sensorReceiver), opPackageName,
+                    restricted, BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT);
         }
 
         @Override // Binder call
         public void startPreparedClient(int cookie) {
-            checkPermission(MANAGE_BIOMETRIC);
-            startCurrentClient(cookie);
+            Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+            mFace10.startPreparedClient(cookie);
         }
 
         @Override // Binder call
         public void cancelAuthentication(final IBinder token, final String opPackageName) {
-            checkPermission(USE_BIOMETRIC_INTERNAL);
-            cancelAuthenticationInternal(token, opPackageName, true /* fromClient */);
+            Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+            mFace10.cancelAuthentication(token);
         }
 
         @Override // Binder call
         public void cancelAuthenticationFromService(final IBinder token, final String opPackageName,
                 int callingUid, int callingPid, int callingUserId) {
-            checkPermission(USE_BIOMETRIC_INTERNAL);
-            // Cancellation is from system server in this case.
-            cancelAuthenticationInternal(token, opPackageName, false /* fromClient */);
+            Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+            mFace10.cancelAuthentication(token);
         }
 
         @Override // Binder call
         public void remove(final IBinder token, final int faceId, final int userId,
                 final IFaceServiceReceiver receiver, final String opPackageName) {
-            checkPermission(MANAGE_BIOMETRIC);
-            updateActiveGroup(userId);
-
-            if (token == null) {
-                Slog.w(TAG, "remove(): token is null");
-                return;
-            }
-
-            final FaceRemovalClient client = new FaceRemovalClient(getContext(), mLazyDaemon, token,
-                    new ClientMonitorCallbackConverter(receiver), faceId, userId, opPackageName,
-                    getBiometricUtils(), getSensorId(), mAuthenticatorIds);
-            removeInternal(client);
+            Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+            mFace10.scheduleRemove(token, faceId, userId, receiver, opPackageName);
         }
 
         @Override
         public void addLockoutResetCallback(final IBiometricServiceLockoutResetCallback callback,
                 final String opPackageName) {
-            checkPermission(USE_BIOMETRIC_INTERNAL);
-            mHandler.post(() -> {
-                mLockoutResetTracker.addCallback(callback, opPackageName);
-            });
+            Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+            mLockoutResetDispatcher.addCallback(callback, opPackageName);
         }
 
         @Override // Binder call
-        protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, String[] args) {
             if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) {
                 return;
             }
@@ -260,28 +165,21 @@
             final long ident = Binder.clearCallingIdentity();
             try {
                 if (args.length > 1 && "--hal".equals(args[0])) {
-                    dumpHal(fd, Arrays.copyOfRange(args, 1, args.length, args.getClass()));
+                    mFace10.dumpHal(fd, Arrays.copyOfRange(args, 1, args.length, args.getClass()));
                 } else {
-                    dumpInternal(pw);
+                    mFace10.dump(pw);
                 }
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
         }
 
-        /**
-         * The following methods don't use any common code from BiometricService
-         */
-
-        // TODO: refactor out common code here
         @Override // Binder call
         public boolean isHardwareDetected(String opPackageName) {
-            checkPermission(USE_BIOMETRIC_INTERNAL);
-
+            Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
             final long token = Binder.clearCallingIdentity();
             try {
-                IBiometricsFace daemon = getFaceDaemon();
-                return daemon != null;
+                return mFace10.isHardwareDetected();
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -289,512 +187,66 @@
 
         @Override // Binder call
         public List<Face> getEnrolledFaces(int userId, String opPackageName) {
-            checkPermission(MANAGE_BIOMETRIC);
-            return FaceService.this.getEnrolledTemplates(userId);
+            Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+            return mFace10.getEnrolledFaces(userId);
         }
 
         @Override // Binder call
         public boolean hasEnrolledFaces(int userId, String opPackageName) {
-            checkPermission(USE_BIOMETRIC_INTERNAL);
-            return FaceService.this.hasEnrolledBiometrics(userId);
+            Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+            return !mFace10.getEnrolledFaces(userId).isEmpty();
         }
 
         @Override
         public @LockoutTracker.LockoutMode int getLockoutModeForUser(int userId) {
-            checkPermission(USE_BIOMETRIC_INTERNAL);
-            return mLockoutTracker.getLockoutModeForUser(userId);
+            Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+            return mFace10.getLockoutModeForUser(userId);
         }
 
         @Override // Binder call
-        public long getAuthenticatorId(int callingUserId) {
-            checkPermission(USE_BIOMETRIC_INTERNAL);
-            return FaceService.this.getAuthenticatorId(callingUserId);
+        public long getAuthenticatorId(int userId) {
+            Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+            return mFace10.getAuthenticatorId(userId);
         }
 
         @Override // Binder call
         public void resetLockout(int userId, byte[] hardwareAuthToken) {
-            checkPermission(MANAGE_BIOMETRIC);
-            mHandler.post(() -> {
-                if (!FaceService.this.hasEnrolledBiometrics(userId)) {
-                    Slog.w(TAG, "Ignoring lockout reset, no templates enrolled");
-                    return;
-                }
-
-                Slog.d(TAG, "Resetting lockout for user: " + userId);
-
-                updateActiveGroup(userId);
-                final FaceResetLockoutClient client = new FaceResetLockoutClient(getContext(),
-                        mLazyDaemon, userId, getContext().getOpPackageName(), getSensorId(),
-                        hardwareAuthToken);
-                startClient(client, true /* initiatedByClient */);
-            });
+            Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+            mFace10.scheduleResetLockout(userId, hardwareAuthToken);
         }
 
         @Override
         public void setFeature(final IBinder token, int userId, int feature, boolean enabled,
                 final byte[] hardwareAuthToken, IFaceServiceReceiver receiver,
                 final String opPackageName) {
-            checkPermission(MANAGE_BIOMETRIC);
-
-            mHandler.post(() -> {
-                if (DEBUG) {
-                    Slog.d(TAG, "setFeature for user(" + userId + ")");
-                }
-                updateActiveGroup(userId);
-                if (!FaceService.this.hasEnrolledBiometrics(mCurrentUserId)) {
-                    Slog.e(TAG, "No enrolled biometrics while setting feature: " + feature);
-                    return;
-                }
-
-                final int faceId = getFirstTemplateForUser(mCurrentUserId);
-
-                final FaceSetFeatureClient client = new FaceSetFeatureClient(getContext(),
-                        mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
-                        opPackageName, getSensorId(), feature, enabled, hardwareAuthToken, faceId);
-                startClient(client, true /* initiatedByClient */);
-            });
-
+            Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+            mFace10.scheduleSetFeature(token, userId, feature, enabled, hardwareAuthToken, receiver,
+                    opPackageName);
         }
 
         @Override
         public void getFeature(final IBinder token, int userId, int feature,
                 IFaceServiceReceiver receiver, final String opPackageName) {
-            checkPermission(MANAGE_BIOMETRIC);
-
-            mHandler.post(() -> {
-                if (DEBUG) {
-                    Slog.d(TAG, "getFeature for user(" + userId + ")");
-                }
-                updateActiveGroup(userId);
-                // This should ideally return tri-state, but the user isn't shown settings unless
-                // they are enrolled so it's fine for now.
-                if (!FaceService.this.hasEnrolledBiometrics(mCurrentUserId)) {
-                    Slog.e(TAG, "No enrolled biometrics while getting feature: " + feature);
-                    return;
-                }
-
-                // TODO: Support multiple faces
-                final int faceId = getFirstTemplateForUser(mCurrentUserId);
-
-                final FaceGetFeatureClient client = new FaceGetFeatureClient(getContext(),
-                        mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
-                        opPackageName, getSensorId(), feature, faceId);
-                startClient(client, true /* initiatedByClient */);
-            });
-
-        }
-
-        // TODO: Support multiple faces
-        private int getFirstTemplateForUser(int user) {
-            final List<Face> faces = FaceService.this.getEnrolledTemplates(user);
-            if (!faces.isEmpty()) {
-                return faces.get(0).getBiometricId();
-            }
-            return 0;
+            Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
+            mFace10.scheduleGetFeature(token, userId, feature, receiver, opPackageName);
         }
 
         @Override // Binder call
         public void initializeConfiguration(int sensorId) {
-            checkPermission(USE_BIOMETRIC_INTERNAL);
-            initializeConfigurationInternal(sensorId);
+            Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+            mFace10 = new Face10(getContext(), sensorId, mLockoutResetDispatcher);
         }
     }
 
-    private final LockoutHalImpl mLockoutTracker;
-
-    @GuardedBy("this")
-    private IBiometricsFace mDaemon;
-    private UsageStats mUsageStats;
-    private FaceRevokeChallengeClient mPendingRevokeChallenge;
-
-    private NotificationManager mNotificationManager;
-
-    /**
-     * Receives callbacks from the HAL.
-     */
-    private IBiometricsFaceClientCallback mDaemonCallback =
-            new IBiometricsFaceClientCallback.Stub() {
-        @Override
-        public void onEnrollResult(final long deviceId, int faceId, int userId,
-                int remaining) {
-            mHandler.post(() -> {
-                final Face face = new Face(getBiometricUtils()
-                        .getUniqueName(getContext(), userId), faceId, deviceId);
-                FaceService.super.handleEnrollResult(face, remaining);
-
-                // Enrollment changes the authenticatorId, so update it here.
-                IBiometricsFace daemon = getFaceDaemon();
-                if (remaining == 0 && daemon != null) {
-                    try {
-                        mAuthenticatorIds.put(userId,
-                                hasEnrolledBiometrics(userId) ? daemon.getAuthenticatorId().value
-                                        : 0L);
-                    } catch (RemoteException e) {
-                        Slog.e(TAG, "Unable to get authenticatorId", e);
-                    }
-                }
-            });
-        }
-
-        @Override
-        public void onAcquired(final long deviceId, final int userId, final int acquiredInfo,
-                final int vendorCode) {
-            mHandler.post(() -> {
-                FaceService.super.handleAcquired(acquiredInfo, vendorCode);
-            });
-        }
-
-        @Override
-        public void onAuthenticated(final long deviceId, final int faceId, final int userId,
-                ArrayList<Byte> token) {
-            mHandler.post(() -> {
-                Face face = new Face("", faceId, deviceId);
-                FaceService.super.handleAuthenticated(face, token);
-            });
-        }
-
-        @Override
-        public void onError(final long deviceId, final int userId, final int error,
-                final int vendorCode) {
-            mHandler.post(() -> {
-                FaceService.super.handleError(error, vendorCode);
-
-                // TODO: this chunk of code should be common to all biometric services
-                if (error == BiometricConstants.BIOMETRIC_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;
-                        mCurrentUserId = UserHandle.USER_NULL;
-                    }
-                }
-            });
-        }
-
-        @Override
-        public void onRemoved(final long deviceId, ArrayList<Integer> faceIds, final int userId) {
-            mHandler.post(() -> {
-                if (!faceIds.isEmpty()) {
-                    for (int i = 0; i < faceIds.size(); i++) {
-                        final Face face = new Face("", faceIds.get(i), deviceId);
-                        // Convert to old behavior
-                        FaceService.super.handleRemoved(face, faceIds.size() - i - 1);
-                    }
-                } else {
-                    final Face face = new Face("", 0 /* identifier */, deviceId);
-                    FaceService.super.handleRemoved(face, 0 /* remaining */);
-                }
-                Settings.Secure.putIntForUser(getContext().getContentResolver(),
-                        Settings.Secure.FACE_UNLOCK_RE_ENROLL, 0, UserHandle.USER_CURRENT);
-            });
-        }
-
-        @Override
-        public void onEnumerate(long deviceId, ArrayList<Integer> faceIds, int userId)
-                throws RemoteException {
-            mHandler.post(() -> {
-                if (!faceIds.isEmpty()) {
-                    for (int i = 0; i < faceIds.size(); i++) {
-                        final Face face = new Face("", faceIds.get(i), deviceId);
-                        // Convert to old old behavior
-                        FaceService.super.handleEnumerate(face, faceIds.size() - i - 1);
-                    }
-                } else {
-                    // For face, the HIDL contract is to receive an empty list when there are no
-                    // templates enrolled. Send a null identifier since we don't consume them
-                    // anywhere, and send remaining == 0 to plumb this with existing common code.
-                    FaceService.super.handleEnumerate(null /* identifier */, 0);
-                }
-            });
-        }
-
-        @Override
-        public void onLockoutChanged(long duration) {
-            Slog.d(TAG, "onLockoutChanged: " + duration);
-
-            final @LockoutTracker.LockoutMode int lockoutMode;
-            if (duration == 0) {
-                lockoutMode = LockoutTracker.LOCKOUT_NONE;
-            } else if (duration == -1 || duration == Long.MAX_VALUE) {
-                lockoutMode = LockoutTracker.LOCKOUT_PERMANENT;
-            } else {
-                lockoutMode = LockoutTracker.LOCKOUT_TIMED;
-            }
-            mLockoutTracker.setCurrentUserLockoutMode(lockoutMode);
-
-            mHandler.post(() -> {
-                if (duration == 0) {
-                    mLockoutResetTracker.notifyLockoutResetCallbacks(getSensorId());
-                }
-            });
-        }
-    };
-
     public FaceService(Context context) {
         super(context);
-        mLazyDaemon = FaceService.this::getFaceDaemon;
-        mLockoutResetTracker = new LockoutResetTracker(context);
-        mLockoutTracker = new LockoutHalImpl();
-        mUsageStats = new UsageStats(context);
-        mNotificationManager = getContext().getSystemService(NotificationManager.class);
-    }
-
-    @Override
-    protected void removeClient(ClientMonitor<?> client) {
-        super.removeClient(client);
-        if (mPendingRevokeChallenge != null) {
-            revokeChallengeInternal(mPendingRevokeChallenge);
-            mPendingRevokeChallenge = null;
-        }
+        mLockoutResetDispatcher = new LockoutResetDispatcher(context);
     }
 
     @Override
     public void onStart() {
-        super.onStart();
         publishBinderService(Context.FACE_SERVICE, new FaceServiceWrapper());
-        // Get the face daemon on FaceService's on thread so SystemServerInitThreadPool isn't
-        // blocked
-        SystemServerInitThreadPool.submit(() -> mHandler.post(this::getFaceDaemon),
-                TAG + ".onStart");
-    }
-
-    @Override
-    public String getTag() {
-        return TAG;
-    }
-
-    @Override
-    protected IBiometricsFace getDaemon() {
-        return getFaceDaemon();
-    }
-
-    @Override
-    protected BiometricUtils getBiometricUtils() {
-        return FaceUtils.getInstance();
-    }
-
-    @Override
-    protected boolean hasReachedEnrollmentLimit(int userId) {
-        final int limit = getContext().getResources().getInteger(
-                com.android.internal.R.integer.config_faceMaxTemplatesPerUser);
-        final int enrolled = FaceService.this.getEnrolledTemplates(userId).size();
-        if (enrolled >= limit) {
-            Slog.w(TAG, "Too many faces registered, user: " + userId);
-            return true;
-        }
-        return false;
-    }
-
-    @Override
-    public void serviceDied(long cookie) {
-        super.serviceDied(cookie);
-        mDaemon = null;
-
-        mCurrentUserId = UserHandle.USER_NULL; // Force updateActiveGroup() to re-evaluate
-    }
-
-    @Override
-    protected void updateActiveGroup(int userId) {
-        IBiometricsFace daemon = getFaceDaemon();
-
-        if (daemon != null) {
-            try {
-                if (userId != mCurrentUserId) {
-                    final File baseDir = Environment.getDataVendorDeDirectory(userId);
-                    final File faceDir = new File(baseDir, 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,
-                            hasEnrolledBiometrics(userId) ? daemon.getAuthenticatorId().value : 0L);
-                }
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to setActiveUser():", e);
-            }
-        }
-    }
-
-    @Override
-    protected void handleUserSwitching(int userId) {
-        super.handleUserSwitching(userId);
-        // Will be updated when we get the callback from HAL
-        mLockoutTracker.setCurrentUserLockoutMode(LockoutTracker.LOCKOUT_NONE);
-    }
-
-    @Override
-    protected boolean hasEnrolledBiometrics(int userId) {
-        if (userId != UserHandle.getCallingUserId()) {
-            checkPermission(INTERACT_ACROSS_USERS);
-        }
-        return getBiometricUtils().getBiometricsForUser(getContext(), userId).size() > 0;
-    }
-
-    @Override
-    protected String getManageBiometricPermission() {
-        return MANAGE_BIOMETRIC;
-    }
-
-    @Override
-    protected List<Face> getEnrolledTemplates(int userId) {
-        return FaceUtils.getInstance().getBiometricsForUser(getContext(), userId);
-    }
-
-    @Override
-    protected void notifyClientActiveCallbacks(boolean isActive) {
-        // noop for Face.
-    }
-
-    @Override
-    protected int statsModality() {
-        return BiometricsProtoEnums.MODALITY_FACE;
-    }
-
-    @Override
-    protected void doTemplateCleanupForUser(int userId) {
-        final List<Face> enrolledList = getEnrolledTemplates(userId);
-        final FaceInternalCleanupClient client = new FaceInternalCleanupClient(getContext(),
-                mLazyDaemon, userId, getContext().getOpPackageName(), getSensorId(), enrolledList,
-                getBiometricUtils(), mAuthenticatorIds);
-        cleanupInternal(client);
-    }
-
-
-    /** Gets the face daemon */
-    private 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);
-
-            long halId = 0;
-            try {
-                halId = 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: " + halId);
-            if (halId != 0) {
-                loadAuthenticatorIds();
-                updateActiveGroup(ActivityManager.getCurrentUser());
-                doTemplateCleanupForUser(ActivityManager.getCurrentUser());
-            } else {
-                Slog.w(TAG, "Failed to open Face HAL!");
-                MetricsLogger.count(getContext(), "faced_openhal_error", 1);
-                mDaemon = null;
-            }
-        }
-        return mDaemon;
     }
 
     private native NativeHandle convertSurfaceToNativeHandle(Surface surface);
-
-    private void dumpInternal(PrintWriter pw) {
-        PerformanceTracker performanceTracker =
-                PerformanceTracker.getInstanceForSensorId(getSensorId());
-
-        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();
-                final int N = getBiometricUtils().getBiometricsForUser(getContext(), userId).size();
-                JSONObject set = new JSONObject();
-                set.put("id", userId);
-                set.put("count", N);
-                set.put("accept", performanceTracker.getAcceptForUser(userId));
-                set.put("reject", performanceTracker.getRejectForUser(userId));
-                set.put("acquire", performanceTracker.getAcquireForUser(userId));
-                set.put("lockout", performanceTracker.getTimedLockoutForUser(userId));
-                set.put("permanentLockout", performanceTracker.getPermanentLockoutForUser(userId));
-                // cryptoStats measures statistics about secure face transactions
-                // (e.g. to unlock password storage, make secure purchases, etc.)
-                set.put("acceptCrypto", performanceTracker.getAcceptCryptoForUser(userId));
-                set.put("rejectCrypto", performanceTracker.getRejectCryptoForUser(userId));
-                set.put("acquireCrypto", performanceTracker.getAcquireCryptoForUser(userId));
-                sets.put(set);
-            }
-
-            dump.put("prints", sets);
-        } catch (JSONException e) {
-            Slog.e(TAG, "dump formatting failure", e);
-        }
-        pw.println(dump);
-        pw.println("HAL deaths since last reboot: " + performanceTracker.getHALDeathCount());
-
-        mUsageStats.print(pw);
-    }
-
-    private void dumpHal(FileDescriptor fd, String[] args) {
-        // WARNING: CDD restricts image data from leaving TEE unencrypted on
-        //          production devices:
-        // [C-1-10] MUST not allow unencrypted access to identifiable biometric
-        //          data or any data derived from it (such as embeddings) to the
-        //         Application Processor outside the context of the TEE.
-        //  As such, this API should only be enabled for testing purposes on
-        //  engineering and userdebug builds.  All modules in the software stack
-        //  MUST enforce final build products do NOT have this functionality.
-        //  Additionally, the following check MUST NOT be removed.
-        if (!(Build.IS_ENG || Build.IS_USERDEBUG)) {
-            return;
-        }
-
-        // Additionally, this flag allows turning off face for a device
-        // (either permanently through the build or on an individual device).
-        if (SystemProperties.getBoolean("ro.face.disable_debug_data", false)
-                || SystemProperties.getBoolean("persist.face.disable_debug_data", false)) {
-            return;
-        }
-
-        // The debug method takes two file descriptors. The first is for text
-        // output, which we will drop.  The second is for binary data, which
-        // will be the protobuf data.
-        final IBiometricsFace daemon = getFaceDaemon();
-        if (daemon != null) {
-            FileOutputStream devnull = null;
-            try {
-                devnull = new FileOutputStream("/dev/null");
-                final NativeHandle handle = new NativeHandle(
-                        new FileDescriptor[] { devnull.getFD(), fd },
-                        new int[0], false);
-                daemon.debug(handle, new ArrayList<String>(Arrays.asList(args)));
-            } catch (IOException | RemoteException ex) {
-                Slog.d(TAG, "error while reading face debugging data", ex);
-            } finally {
-                if (devnull != null) {
-                    try {
-                        devnull.close();
-                    } catch (IOException ex) {
-                    }
-                }
-            }
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceUpdateActiveUserClient.java
new file mode 100644
index 0000000..bcf304e
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceUpdateActiveUserClient.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.face;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.face.V1_0.IBiometricsFace;
+import android.os.Environment;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.biometrics.sensors.ClientMonitor;
+
+import java.io.File;
+import java.util.Map;
+
+public class FaceUpdateActiveUserClient extends ClientMonitor<IBiometricsFace> {
+    private static final String TAG = "FaceUpdateActiveUserClient";
+    private static final String FACE_DATA_DIR = "facedata";
+
+    private final int mCurrentUserId;
+    private final boolean mHasEnrolledBiometrics;
+    @NonNull private final Map<Integer, Long> mAuthenticatorIds;
+
+    FaceUpdateActiveUserClient(@NonNull Context context,
+            @NonNull LazyDaemon<IBiometricsFace> lazyDaemon,  int userId, @NonNull String owner,
+            int sensorId, int currentUserId, boolean hasEnrolledBIometrics,
+            @NonNull Map<Integer, Long> authenticatorIds) {
+        super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
+                0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
+                BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+        mCurrentUserId = currentUserId;
+        mHasEnrolledBiometrics = hasEnrolledBIometrics;
+        mAuthenticatorIds = authenticatorIds;
+    }
+
+    @Override
+    public void start(@NonNull FinishCallback finishCallback) {
+        super.start(finishCallback);
+
+        if (mCurrentUserId == getTargetUserId()) {
+            Slog.d(TAG, "Already user: " + mCurrentUserId + ", refreshing authenticatorId");
+            try {
+                mAuthenticatorIds.put(getTargetUserId(), mHasEnrolledBiometrics
+                        ? getFreshDaemon().getAuthenticatorId().value : 0L);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Unable to refresh authenticatorId", e);
+            }
+            finishCallback.onClientFinished(this, true /* success */);
+            return;
+        }
+
+        startHalOperation();
+    }
+
+    @Override
+    public void unableToStart() {
+        // Nothing to do here
+    }
+
+    @Override
+    protected void startHalOperation() {
+        final File storePath = new File(Environment.getDataVendorDeDirectory(getTargetUserId()),
+                FACE_DATA_DIR);
+        if (!storePath.exists()) {
+            Slog.e(TAG, "vold has not created the directory?");
+            mFinishCallback.onClientFinished(this, false /* success */);
+            return;
+        }
+
+        try {
+            getFreshDaemon().setActiveUser(getTargetUserId(), storePath.getAbsolutePath());
+            mFinishCallback.onClientFinished(this, true /* success */);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to setActiveUser: " + e);
+            mFinishCallback.onClientFinished(this, false /* success */);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21.java
index 0a94972..9f94c88 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21.java
@@ -56,7 +56,7 @@
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.EnumerateConsumer;
 import com.android.server.biometrics.sensors.Interruptable;
-import com.android.server.biometrics.sensors.LockoutResetTracker;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.LockoutTracker;
 import com.android.server.biometrics.sensors.PerformanceTracker;
 import com.android.server.biometrics.sensors.RemovalConsumer;
@@ -83,17 +83,17 @@
     private static final int ENROLL_TIMEOUT_SEC = 60;
 
     private final Context mContext;
-    private final IActivityTaskManager mActivityTaskManager;;
+    private final IActivityTaskManager mActivityTaskManager;
     private final SensorProperties mSensorProperties;
     private final BiometricScheduler mScheduler;
     private final Handler mHandler;
-    private final LockoutResetTracker mLockoutResetTracker;
+    private final LockoutResetDispatcher mLockoutResetDispatcher;
     private final LockoutFrameworkImpl mLockoutTracker;
     private final BiometricTaskStackListener mTaskStackListener;
     private final ClientMonitor.LazyDaemon<IBiometricsFingerprint> mLazyDaemon;
     private final Map<Integer, Long> mAuthenticatorIds;
 
-    private IBiometricsFingerprint mDaemon;
+    @Nullable private IBiometricsFingerprint mDaemon;
     @Nullable private IUdfpsOverlayController mUdfpsOverlayController;
     private int mCurrentUserId = UserHandle.USER_NULL;
 
@@ -102,9 +102,9 @@
      */
     private static final class SensorProperties {
         final int sensorId;
-        final Boolean isUdfps;
+        final boolean isUdfps;
 
-        SensorProperties(int sensorId, Boolean isUdfps) {
+        SensorProperties(int sensorId, boolean isUdfps) {
             this.sensorId = sensorId;
             this.isUdfps = isUdfps;
         }
@@ -146,7 +146,7 @@
             new LockoutFrameworkImpl.LockoutResetCallback() {
         @Override
         public void onLockoutReset(int userId) {
-            mLockoutResetTracker.notifyLockoutResetCallbacks(mSensorProperties.sensorId);
+            mLockoutResetDispatcher.notifyLockoutResetCallbacks(mSensorProperties.sensorId);
         }
     };
 
@@ -157,9 +157,8 @@
         }
     };
 
-    private IBiometricsFingerprintClientCallback mDaemonCallback =
+    private final IBiometricsFingerprintClientCallback mDaemonCallback =
             new IBiometricsFingerprintClientCallback.Stub() {
-
         @Override
         public void onEnrollResult(long deviceId, int fingerId, int groupId, int remaining) {
             mHandler.post(() -> {
@@ -169,19 +168,13 @@
 
                 final ClientMonitor<?> client = mScheduler.getCurrentClient();
                 if (!(client instanceof FingerprintEnrollClient)) {
-                    final String clientName = client != null
-                            ? client.getClass().getSimpleName(): "null";
-                    Slog.e(TAG, "onEnrollResult for non-enroll client: " + clientName);
+                    Slog.e(TAG, "onEnrollResult for non-enroll client: "
+                            + Utils.getClientName(client));
                     return;
                 }
 
                 final FingerprintEnrollClient enrollClient = (FingerprintEnrollClient) client;
                 enrollClient.onEnrollResult(fingerprint, remaining);
-
-                if (remaining == 0) {
-                    // Update the framework's authenticatorId cache for this user
-                    scheduleUpdateActiveUserWithoutHandler(mCurrentUserId);
-                }
             });
         }
 
@@ -195,9 +188,8 @@
             mHandler.post(() -> {
                 final ClientMonitor<?> client = mScheduler.getCurrentClient();
                 if (!(client instanceof AcquisitionClient)) {
-                    final String clientName = client != null
-                            ? client.getClass().getSimpleName(): "null";
-                    Slog.e(TAG, "onAcquired for non-acquisition client: " + clientName);
+                    Slog.e(TAG, "onAcquired for non-acquisition client: "
+                            + Utils.getClientName(client));
                     return;
                 }
 
@@ -212,9 +204,8 @@
             mHandler.post(() -> {
                 final ClientMonitor<?> client = mScheduler.getCurrentClient();
                 if (!(client instanceof FingerprintAuthenticationClient)) {
-                    final String clientName = client != null
-                            ? client.getClass().getSimpleName(): "null";
-                    Slog.e(TAG, "onAuthenticated for non-authentication client: " + clientName);
+                    Slog.e(TAG, "onAuthenticated for non-authentication client: "
+                            + Utils.getClientName(client));
                     return;
                 }
 
@@ -235,9 +226,7 @@
                         + ", error: " + error
                         + ", vendorCode: " + vendorCode);
                 if (!(client instanceof Interruptable)) {
-                    final String clientName = client != null
-                            ? client.getClass().getSimpleName(): "null";
-                    Slog.e(TAG, "onError for non-error consumer: " + clientName);
+                    Slog.e(TAG, "onError for non-error consumer: " + Utils.getClientName(client));
                     return;
                 }
 
@@ -258,9 +247,8 @@
                 Slog.d(TAG, "Removed, fingerId: " + fingerId + ", remaining: " + remaining);
                 final ClientMonitor<?> client = mScheduler.getCurrentClient();
                 if (!(client instanceof RemovalConsumer)) {
-                    final String clientName = client != null
-                            ? client.getClass().getSimpleName(): "null";
-                    Slog.e(TAG, "onRemoved for non-removal consumer: " + clientName);
+                    Slog.e(TAG, "onRemoved for non-removal consumer: "
+                            + Utils.getClientName(client));
                     return;
                 }
 
@@ -275,9 +263,8 @@
             mHandler.post(() -> {
                 final ClientMonitor<?> client = mScheduler.getCurrentClient();
                 if (!(client instanceof EnumerateConsumer)) {
-                    final String clientName = client != null
-                            ? client.getClass().getSimpleName(): "null";
-                    Slog.e(TAG, "onEnumerate for non-enumerate consumer: " + clientName);
+                    Slog.e(TAG, "onEnumerate for non-enumerate consumer: "
+                            + Utils.getClientName(client));
                     return;
                 }
 
@@ -289,17 +276,17 @@
     };
 
     Fingerprint21(@NonNull Context context, int sensorId,
-            @NonNull LockoutResetTracker lockoutResetTracker,
-            @NonNull GestureAvailabilityTracker gestureAvailabilityTracker) {
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
         mContext = context;
         mActivityTaskManager = ActivityTaskManager.getService();
         mHandler = new Handler(Looper.getMainLooper());
         mTaskStackListener = new BiometricTaskStackListener();
         mAuthenticatorIds = Collections.synchronizedMap(new HashMap<>());
         mLazyDaemon = Fingerprint21.this::getDaemon;
-        mLockoutResetTracker = lockoutResetTracker;
+        mLockoutResetDispatcher = lockoutResetDispatcher;
         mLockoutTracker = new LockoutFrameworkImpl(context, mLockoutResetCallback);
-        mScheduler = new BiometricScheduler(TAG, gestureAvailabilityTracker);
+        mScheduler = new BiometricScheduler(TAG, gestureAvailabilityDispatcher);
 
         try {
             ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG);
@@ -308,7 +295,7 @@
         }
 
         final IBiometricsFingerprint daemon = getDaemon();
-        Boolean isUdfps = false;
+        boolean isUdfps = false;
         android.hardware.biometrics.fingerprint.V2_3.IBiometricsFingerprint extension =
                 android.hardware.biometrics.fingerprint.V2_3.IBiometricsFingerprint.castFrom(
                         daemon);
@@ -317,7 +304,7 @@
                 isUdfps = extension.isUdfps(sensorId);
             } catch (RemoteException e) {
                 Slog.e(TAG, "Remote exception while quering udfps", e);
-                isUdfps = null;
+                isUdfps = false;
             }
         }
         mSensorProperties = new SensorProperties(sensorId, isUdfps);
@@ -414,9 +401,15 @@
                 }
             }
         });
-
     }
 
+    /**
+     * Schedules the {@link FingerprintUpdateActiveUserClient} without posting the work onto the
+     * handler. Many/most APIs are user-specific. However, the HAL requires explicit "setActiveUser"
+     * invocation prior to authenticate/enroll/etc. Thus, internally we usually want to schedule
+     * this operation on the same lambda/runnable as those operations so that the ordering is
+     * correct.
+     */
     private void scheduleUpdateActiveUserWithoutHandler(int targetUserId) {
         final boolean hasEnrolled = !getEnrolledFingerprints(targetUserId).isEmpty();
         final FingerprintUpdateActiveUserClient client =
@@ -467,9 +460,13 @@
                     mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
                     hardwareAuthToken, opPackageName, FingerprintUtils.getInstance(),
                     ENROLL_TIMEOUT_SEC, mSensorProperties.sensorId, mUdfpsOverlayController);
-            mScheduler.scheduleClientMonitor(client);
+            mScheduler.scheduleClientMonitor(client, ((clientMonitor, success) -> {
+                if (success) {
+                    // Update authenticatorIds
+                    scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId());
+                }
+            }));
         });
-
     }
 
     void cancelEnrollment(@NonNull IBinder token) {
@@ -533,7 +530,7 @@
     }
 
     boolean isHardwareDetected() {
-        IBiometricsFingerprint daemon = getDaemon();
+        final IBiometricsFingerprint daemon = getDaemon();
         return daemon != null;
     }
 
@@ -619,7 +616,7 @@
         tracker.clear();
     }
 
-    void dumpInternal(PrintWriter pw) {
+    void dumpInternal(@NonNull PrintWriter pw) {
         PerformanceTracker performanceTracker =
                 PerformanceTracker.getInstanceForSensorId(mSensorProperties.sensorId);
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintEnrollClient.java
index c26dbfd..25e949a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintEnrollClient.java
@@ -76,6 +76,19 @@
     }
 
     @Override
+    protected boolean hasReachedEnrollmentLimit() {
+        final int limit = getContext().getResources().getInteger(
+                com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser);
+        final int enrolled = mBiometricUtils.getBiometricsForUser(getContext(), getTargetUserId())
+                .size();
+        if (enrolled >= limit) {
+            Slog.w(TAG, "Too many faces registered, user: " + getTargetUserId());
+            return true;
+        }
+        return false;
+    }
+
+    @Override
     protected void startHalOperation() {
         showUdfpsOverlay();
         try {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index d092bfd..e0cb7ec9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -25,6 +25,7 @@
 import static android.Manifest.permission.USE_FINGERPRINT;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
 
+import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.content.Context;
@@ -50,7 +51,7 @@
 import com.android.server.SystemService;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.LockoutResetTracker;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.LockoutTracker;
 
 import java.io.FileDescriptor;
@@ -68,8 +69,8 @@
     protected static final String TAG = "FingerprintService";
 
     private final AppOpsManager mAppOps;
-    private final LockoutResetTracker mLockoutResetTracker;
-    private final GestureAvailabilityTracker mGestureAvailabilityTracker;
+    private final LockoutResetDispatcher mLockoutResetDispatcher;
+    private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
     private Fingerprint21 mFingerprint21;
 
     /**
@@ -181,11 +182,11 @@
         public void addLockoutResetCallback(final IBiometricServiceLockoutResetCallback callback,
                 final String opPackageName) {
             Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
-            mLockoutResetTracker.addCallback(callback, opPackageName);
+            mLockoutResetDispatcher.addCallback(callback, opPackageName);
         }
 
         @Override // Binder call
-        protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, String[] args) {
             if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) {
                 return;
             }
@@ -277,26 +278,26 @@
         @Override
         public boolean isClientActive() {
             Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);
-            return mGestureAvailabilityTracker.isAnySensorActive();
+            return mGestureAvailabilityDispatcher.isAnySensorActive();
         }
 
         @Override
         public void addClientActiveCallback(IFingerprintClientActiveCallback callback) {
             Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);
-            mGestureAvailabilityTracker.registerCallback(callback);
+            mGestureAvailabilityDispatcher.registerCallback(callback);
         }
 
         @Override
         public void removeClientActiveCallback(IFingerprintClientActiveCallback callback) {
             Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);
-            mGestureAvailabilityTracker.removeCallback(callback);
+            mGestureAvailabilityDispatcher.removeCallback(callback);
         }
 
         @Override // Binder call
         public void initializeConfiguration(int sensorId) {
             Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
-            mFingerprint21 = new Fingerprint21(getContext(), sensorId, mLockoutResetTracker,
-                    mGestureAvailabilityTracker);
+            mFingerprint21 = new Fingerprint21(getContext(), sensorId, mLockoutResetDispatcher,
+                    mGestureAvailabilityDispatcher);
         }
 
         @Override
@@ -327,8 +328,8 @@
     public FingerprintService(Context context) {
         super(context);
         mAppOps = context.getSystemService(AppOpsManager.class);
-        mGestureAvailabilityTracker = new GestureAvailabilityTracker();
-        mLockoutResetTracker = new LockoutResetTracker(context);
+        mGestureAvailabilityDispatcher = new GestureAvailabilityDispatcher();
+        mLockoutResetDispatcher = new LockoutResetDispatcher(context);
     }
 
     @Override
@@ -336,6 +337,9 @@
         publishBinderService(Context.FINGERPRINT_SERVICE, new FingerprintServiceWrapper());
     }
 
+    /**
+     * Checks for public API invocations to ensure that permissions, etc are granted/correct.
+     */
     @SuppressWarnings("BooleanMethodIsAlwaysInverted")
     private boolean canUseFingerprint(String opPackageName, boolean requireForeground, int uid,
             int pid, int userId) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/GestureAvailabilityTracker.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/GestureAvailabilityDispatcher.java
similarity index 96%
rename from services/core/java/com/android/server/biometrics/sensors/fingerprint/GestureAvailabilityTracker.java
rename to services/core/java/com/android/server/biometrics/sensors/fingerprint/GestureAvailabilityDispatcher.java
index 5023c83..7bda33d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/GestureAvailabilityTracker.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/GestureAvailabilityDispatcher.java
@@ -28,7 +28,7 @@
  * Keeps track of sensor gesture availability (e.g. swipe), and notifies clients when its
  * availability changes
  */
-public class GestureAvailabilityTracker {
+public class GestureAvailabilityDispatcher {
     private static final String TAG = "GestureAvailabilityTracker";
 
     private final CopyOnWriteArrayList<IFingerprintClientActiveCallback> mClientActiveCallbacks;
@@ -36,7 +36,7 @@
 
     private boolean mIsActive;
 
-    GestureAvailabilityTracker() {
+    GestureAvailabilityDispatcher() {
         mClientActiveCallbacks = new CopyOnWriteArrayList<>();
         mActiveSensors = new HashMap<>();
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java b/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java
index 58ab20d..bcf63dc 100644
--- a/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java
@@ -18,17 +18,12 @@
 
 import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
 
+import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.biometrics.BiometricAuthenticator;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.iris.IIrisService;
 
-import com.android.server.biometrics.sensors.BiometricServiceBase;
-import com.android.server.biometrics.sensors.BiometricUtils;
-import com.android.server.biometrics.sensors.LockoutTracker;
-import com.android.server.biometrics.sensors.fingerprint.FingerprintService;
-
-import java.util.List;
+import com.android.server.SystemService;
+import com.android.server.biometrics.Utils;
 
 /**
  * A service to manage multiple clients that want to access the Iris HAL API.
@@ -36,11 +31,9 @@
  * iris-related events.
  *
  * TODO: The vendor is expected to fill in the service. See
- * {@link FingerprintService}
- *
- * @hide
+ * {@link com.android.server.biometrics.sensors.face.FaceService}
  */
-public class IrisService extends BiometricServiceBase {
+public class IrisService extends SystemService {
 
     private static final String TAG = "IrisService";
 
@@ -50,77 +43,16 @@
     private final class IrisServiceWrapper extends IIrisService.Stub {
         @Override // Binder call
         public void initializeConfiguration(int sensorId) {
-            checkPermission(USE_BIOMETRIC_INTERNAL);
-            initializeConfigurationInternal(sensorId);
+            Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
         }
     }
 
-    /**
-     * Initializes the system service.
-     * <p>
-     * Subclasses must define a single argument constructor that accepts the context
-     * and passes it to super.
-     * </p>
-     *
-     * @param context The system server context.
-     */
-    public IrisService(Context context) {
+    public IrisService(@NonNull Context context) {
         super(context);
     }
 
     @Override
     public void onStart() {
-        super.onStart();
         publishBinderService(Context.IRIS_SERVICE, new IrisServiceWrapper());
     }
-
-    @Override
-    protected void doTemplateCleanupForUser(int userId) {
-
-    }
-
-    @Override
-    protected String getTag() {
-        return TAG;
-    }
-
-    @Override
-    protected Object getDaemon() {
-        return null;
-    }
-
-    @Override
-    protected BiometricUtils getBiometricUtils() {
-        return null;
-    }
-
-    @Override
-    protected boolean hasReachedEnrollmentLimit(int userId) {
-        return false;
-    }
-
-    @Override
-    protected void updateActiveGroup(int userId) {
-
-    }
-
-    @Override
-    protected boolean hasEnrolledBiometrics(int userId) {
-        return false;
-    }
-
-    @Override
-    protected String getManageBiometricPermission() {
-        return null;
-    }
-
-    @Override
-    protected List<? extends BiometricAuthenticator.Identifier> getEnrolledTemplates(int userId) {
-        return null;
-    }
-
-    @Override
-    protected int statsModality() {
-        return BiometricsProtoEnums.MODALITY_IRIS;
-    }
 }
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index b279b37..ed3a223 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -400,7 +400,7 @@
                 final int intendingUid = getIntendingUid(callingPackage, userId);
                 final int intendingUserId = UserHandle.getUserId(intendingUid);
                 if (!clipboardAccessAllowed(AppOpsManager.OP_READ_CLIPBOARD, callingPackage,
-                        intendingUid, intendingUserId)
+                        intendingUid, intendingUserId, false)
                         || isDeviceLocked(intendingUserId)) {
                     return null;
                 }
@@ -416,7 +416,7 @@
                 final int intendingUid = getIntendingUid(callingPackage, userId);
                 final int intendingUserId = UserHandle.getUserId(intendingUid);
                 if (!clipboardAccessAllowed(AppOpsManager.OP_READ_CLIPBOARD, callingPackage,
-                        intendingUid, intendingUserId)
+                        intendingUid, intendingUserId, false)
                         || isDeviceLocked(intendingUserId)) {
                     return false;
                 }
@@ -450,7 +450,7 @@
                 final int intendingUid = getIntendingUid(callingPackage, userId);
                 final int intendingUserId = UserHandle.getUserId(intendingUid);
                 if (!clipboardAccessAllowed(AppOpsManager.OP_READ_CLIPBOARD, callingPackage,
-                        intendingUid, intendingUserId)
+                        intendingUid, intendingUserId, false)
                         || isDeviceLocked(intendingUserId)) {
                     return false;
                 }
@@ -740,14 +740,21 @@
 
     private boolean clipboardAccessAllowed(int op, String callingPackage, int uid,
             @UserIdInt int userId) {
-        // Check the AppOp.
-        if (mAppOps.noteOp(op, uid, callingPackage) != AppOpsManager.MODE_ALLOWED) {
-            return false;
-        }
+        return clipboardAccessAllowed(op, callingPackage, uid, userId, true);
+    }
+
+    private boolean clipboardAccessAllowed(int op, String callingPackage, int uid,
+            @UserIdInt int userId, boolean shouldNoteOp) {
+
+        boolean allowed = false;
+
+        // First, verify package ownership to ensure use below is safe.
+        mAppOps.checkPackage(uid, callingPackage);
+
         // Shell can access the clipboard for testing purposes.
         if (mPm.checkPermission(android.Manifest.permission.READ_CLIPBOARD_IN_BACKGROUND,
                     callingPackage) == PackageManager.PERMISSION_GRANTED) {
-            return true;
+            allowed = true;
         }
         // The default IME is always allowed to access the clipboard.
         String defaultIme = Settings.Secure.getStringForUser(getContext().getContentResolver(),
@@ -755,7 +762,7 @@
         if (!TextUtils.isEmpty(defaultIme)) {
             final String imePkg = ComponentName.unflattenFromString(defaultIme).getPackageName();
             if (imePkg.equals(callingPackage)) {
-                return true;
+                allowed = true;
             }
         }
 
@@ -766,8 +773,10 @@
                 // at the same time. e.x. SystemUI. It needs to check the window focus of
                 // Binder.getCallingUid(). Without checking, the user X can't copy any thing from
                 // INTERNAL_SYSTEM_WINDOW to the other applications.
-                boolean allowed = mWm.isUidFocused(uid)
-                        || isInternalSysWindowAppWithWindowFocus(callingPackage);
+                if (!allowed) {
+                    allowed = mWm.isUidFocused(uid)
+                            || isInternalSysWindowAppWithWindowFocus(callingPackage);
+                }
                 if (!allowed && mContentCaptureInternal != null) {
                     // ...or the Content Capture Service
                     // The uid parameter of mContentCaptureInternal.isContentCaptureServiceForUser
@@ -786,17 +795,28 @@
                     // userId must pass intending userId. i.e. user#10.
                     allowed = mAutofillInternal.isAugmentedAutofillServiceForUser(uid, userId);
                 }
-                if (!allowed) {
-                    Slog.e(TAG, "Denying clipboard access to " + callingPackage
-                            + ", application is not in focus neither is a system service for "
-                            + "user " + userId);
-                }
-                return allowed;
+                break;
             case AppOpsManager.OP_WRITE_CLIPBOARD:
                 // Writing is allowed without focus.
-                return true;
+                allowed = true;
+                break;
             default:
                 throw new IllegalArgumentException("Unknown clipboard appop " + op);
         }
+        if (!allowed) {
+            Slog.e(TAG, "Denying clipboard access to " + callingPackage
+                    + ", application is not in focus nor is it a system service for "
+                    + "user " + userId);
+            return false;
+        }
+        // Finally, check the app op.
+        int appOpsResult;
+        if (shouldNoteOp) {
+            appOpsResult = mAppOps.noteOp(op, uid, callingPackage);
+        } else {
+            appOpsResult = mAppOps.checkOp(op, uid, callingPackage);
+        }
+
+        return appOpsResult == AppOpsManager.MODE_ALLOWED;
     }
 }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index ca16d57..a864aa6 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -403,6 +403,7 @@
     @GuardedBy("mLock")
     private boolean mVerityFound;
 
+    @GuardedBy("mLock")
     private boolean mDataLoaderFinished = false;
 
     @GuardedBy("mLock")
@@ -1118,7 +1119,7 @@
         if (hasParentSessionId()) {
             throw new IllegalStateException(
                     "Session " + sessionId + " is a child of multi-package session "
-                            + mParentSessionId +  " and may not be committed directly.");
+                            + getParentSessionId() +  " and may not be committed directly.");
         }
 
         if (!markAsSealed(statusReceiver, forTransfer)) {
@@ -1589,12 +1590,12 @@
     }
 
     private void onStorageUnhealthy() {
-        if (TextUtils.isEmpty(mPackageName)) {
+        final String packageName = getPackageName();
+        if (TextUtils.isEmpty(packageName)) {
             // The package has not been installed.
             return;
         }
         final PackageManagerService packageManagerService = mPm;
-        final String packageName = mPackageName;
         mHandler.post(() -> {
             if (packageManagerService.deletePackageX(packageName,
                     PackageManager.VERSION_CODE_HIGHEST, UserHandle.USER_SYSTEM,
@@ -1686,7 +1687,11 @@
     }
 
     private void handleInstall() {
-        if (isInstallerDeviceOwnerOrAffiliatedProfileOwnerLocked()) {
+        final boolean needsLogging;
+        synchronized (mLock) {
+            needsLogging = isInstallerDeviceOwnerOrAffiliatedProfileOwnerLocked();
+        }
+        if (needsLogging) {
             DevicePolicyEventLogger
                     .createEvent(DevicePolicyEnums.INSTALL_PACKAGE)
                     .setAdmin(mInstallSource.installerPackageName)
@@ -1929,19 +1934,20 @@
         // Skip logging the side-loaded app installations, as those are private and aren't reported
         // anywhere; app stores already have a record of the installation and that's why reporting
         // it here is fine
+        final String packageName = getPackageName();
         final String packageNameToLog =
-                (params.installFlags & PackageManager.INSTALL_FROM_ADB) == 0 ? mPackageName : "";
+                (params.installFlags & PackageManager.INSTALL_FROM_ADB) == 0 ? packageName : "";
         final long currentTimestamp = System.currentTimeMillis();
         FrameworkStatsLog.write(FrameworkStatsLog.PACKAGE_INSTALLER_V2_REPORTED,
                 isIncrementalInstallation(),
                 packageNameToLog,
                 currentTimestamp - createdMillis,
                 returnCode,
-                getApksSize());
+                getApksSize(packageName));
     }
 
-    private long getApksSize() {
-        final PackageSetting ps = mPm.getPackageSetting(mPackageName);
+    private long getApksSize(String packageName) {
+        final PackageSetting ps = mPm.getPackageSetting(packageName);
         if (ps == null) {
             return 0;
         }
@@ -2577,7 +2583,7 @@
     }
 
     void setPermissionsResult(boolean accepted) {
-        if (!mSealed) {
+        if (!isSealed()) {
             throw new SecurityException("Must be sealed to accept permissions");
         }
 
@@ -2653,7 +2659,7 @@
         if (hasParentSessionId()) {
             throw new IllegalStateException(
                     "Session " + sessionId + " is a child of multi-package session "
-                            + mParentSessionId +  " and may not be abandoned directly.");
+                            + getParentSessionId() +  " and may not be abandoned directly.");
         }
 
         List<PackageInstallerSession> childSessions = getChildSessionsNotLocked();
@@ -2823,7 +2829,11 @@
                         return;
                 }
 
-                if (mDestroyed || mDataLoaderFinished) {
+                final boolean isDestroyedOrDataLoaderFinished;
+                synchronized (mLock) {
+                    isDestroyedOrDataLoaderFinished = mDestroyed || mDataLoaderFinished;
+                }
+                if (isDestroyedOrDataLoaderFinished) {
                     switch (status) {
                         case IDataLoaderStatusListener.DATA_LOADER_UNRECOVERABLE:
                             onStorageUnhealthy();
@@ -2835,7 +2845,9 @@
                 try {
                     IDataLoader dataLoader = dataLoaderManager.getDataLoader(dataLoaderId);
                     if (dataLoader == null) {
-                        mDataLoaderFinished = true;
+                        synchronized (mLock) {
+                            mDataLoaderFinished = true;
+                        }
                         dispatchSessionVerificationFailure(INSTALL_FAILED_MEDIA_UNAVAILABLE,
                                 "Failure to obtain data loader");
                         return;
@@ -2868,10 +2880,12 @@
                             break;
                         }
                         case IDataLoaderStatusListener.DATA_LOADER_IMAGE_READY: {
-                            mDataLoaderFinished = true;
+                            synchronized (mLock) {
+                                mDataLoaderFinished = true;
+                            }
                             if (hasParentSessionId()) {
                                 mSessionProvider.getSession(
-                                        mParentSessionId).dispatchStreamValidateAndCommit();
+                                        getParentSessionId()).dispatchStreamValidateAndCommit();
                             } else {
                                 dispatchStreamValidateAndCommit();
                             }
@@ -2881,7 +2895,9 @@
                             break;
                         }
                         case IDataLoaderStatusListener.DATA_LOADER_IMAGE_NOT_READY: {
-                            mDataLoaderFinished = true;
+                            synchronized (mLock) {
+                                mDataLoaderFinished = true;
+                            }
                             dispatchSessionVerificationFailure(INSTALL_FAILED_MEDIA_UNAVAILABLE,
                                     "Failed to prepare image.");
                             if (manualStartAndDestroy) {
@@ -2895,7 +2911,9 @@
                             break;
                         }
                         case IDataLoaderStatusListener.DATA_LOADER_UNRECOVERABLE:
-                            mDataLoaderFinished = true;
+                            synchronized (mLock) {
+                                mDataLoaderFinished = true;
+                            }
                             dispatchSessionVerificationFailure(INSTALL_FAILED_MEDIA_UNAVAILABLE,
                                     "DataLoader reported unrecoverable failure.");
                             break;
@@ -2919,7 +2937,11 @@
             final IStorageHealthListener healthListener = new IStorageHealthListener.Stub() {
                 @Override
                 public void onHealthStatus(int storageId, int status) {
-                    if (mDestroyed || mDataLoaderFinished) {
+                    final boolean isDestroyedOrDataLoaderFinished;
+                    synchronized (mLock) {
+                        isDestroyedOrDataLoaderFinished = mDestroyed || mDataLoaderFinished;
+                    }
+                    if (isDestroyedOrDataLoaderFinished) {
                         // App's installed.
                         switch (status) {
                             case IStorageHealthListener.HEALTH_STATUS_UNHEALTHY:
@@ -2941,7 +2963,9 @@
                             // fallthrough
                         case IStorageHealthListener.HEALTH_STATUS_UNHEALTHY:
                             // Even ADB installation can't wait for missing pages for too long.
-                            mDataLoaderFinished = true;
+                            synchronized (mLock) {
+                                mDataLoaderFinished = true;
+                            }
                             dispatchSessionVerificationFailure(INSTALL_FAILED_MEDIA_UNAVAILABLE,
                                     "Image is missing pages required for installation.");
                             break;
@@ -2984,13 +3008,17 @@
         return EMPTY_CHILD_SESSION_ARRAY;
     }
 
+    private boolean canBeAddedAsChild(int parentCandidate) {
+        synchronized (mLock) {
+            return (!hasParentSessionId() || mParentSessionId == parentCandidate)
+                    && !mCommitted && !mDestroyed;
+        }
+    }
+
     @Override
     public void addChildSessionId(int childSessionId) {
         final PackageInstallerSession childSession = mSessionProvider.getSession(childSessionId);
-        if (childSession == null
-                || (childSession.hasParentSessionId() && childSession.mParentSessionId != sessionId)
-                || childSession.mCommitted
-                || childSession.mDestroyed) {
+        if (childSession == null || !childSession.canBeAddedAsChild(sessionId)) {
             throw new IllegalStateException("Unable to add child session " + childSessionId
                             + " as it does not exist or is in an invalid state.");
         }
@@ -3039,12 +3067,16 @@
     }
 
     boolean hasParentSessionId() {
-        return mParentSessionId != SessionInfo.INVALID_ID;
+        synchronized (mLock) {
+            return mParentSessionId != SessionInfo.INVALID_ID;
+        }
     }
 
     @Override
     public int getParentSessionId() {
-        return mParentSessionId;
+        synchronized (mLock) {
+            return mParentSessionId;
+        }
     }
 
     private void dispatchSessionFinished(int returnCode, String msg, Bundle extras) {
@@ -3134,27 +3166,37 @@
 
     /** {@hide} */
     boolean isStagedSessionReady() {
-        return mStagedSessionReady;
+        synchronized (mLock) {
+            return mStagedSessionReady;
+        }
     }
 
     /** {@hide} */
     boolean isStagedSessionApplied() {
-        return mStagedSessionApplied;
+        synchronized (mLock) {
+            return mStagedSessionApplied;
+        }
     }
 
     /** {@hide} */
     boolean isStagedSessionFailed() {
-        return mStagedSessionFailed;
+        synchronized (mLock) {
+            return mStagedSessionFailed;
+        }
     }
 
     /** {@hide} */
     @StagedSessionErrorCode int getStagedSessionErrorCode() {
-        return mStagedSessionErrorCode;
+        synchronized (mLock) {
+            return mStagedSessionErrorCode;
+        }
     }
 
     /** {@hide} */
     String getStagedSessionErrorMessage() {
-        return mStagedSessionErrorMessage;
+        synchronized (mLock) {
+            return mStagedSessionErrorMessage;
+        }
     }
 
     private void destroyInternal() {
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Enforcer.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Enforcer.java
index 72f8027..761858c 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Enforcer.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Enforcer.java
@@ -192,10 +192,7 @@
     }
 
     private static RuntimeException handleException(RuntimeException e) {
-        // TODO(b/160169016): There is currently no other way to distinguish dead object from other
-        //   exceptions.
-        if (e.getCause() instanceof RemoteException &&
-                e.getCause().getMessage().equals("HwBinder Error: (-32)")) {
+        if (e.getCause() instanceof DeadObjectException) {
             // Server is dead, no need to reboot.
             Log.e(TAG, "HAL died");
         } else {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 1d1c31b..de91af7 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3459,9 +3459,9 @@
     InsetsControlTarget getImeFallback() {
         // host is in non-default display that doesn't support system decor, default to
         // default display's StatusBar to control IME (when available), else let system control it.
-        WindowState statusBar = 
-                mWmService.getDefaultDisplayContentLocked().getDisplayPolicy().getStatusBar();
-        return statusBar != null ? statusBar : mRemoteInsetsControlTarget;
+        final DisplayContent defaultDc = mWmService.getDefaultDisplayContentLocked();
+        WindowState statusBar = defaultDc.getDisplayPolicy().getStatusBar();
+        return statusBar != null ? statusBar : defaultDc.mRemoteInsetsControlTarget;
     }
 
     boolean canShowIme() {
@@ -3521,7 +3521,10 @@
      */
     @VisibleForTesting
     InsetsControlTarget computeImeControlTarget() {
-        if (!isImeControlledByApp() && mRemoteInsetsControlTarget != null) {
+        if (!isImeControlledByApp() && mRemoteInsetsControlTarget != null
+                || (mInputMethodInputTarget != null
+                        && getImeHostOrFallback(mInputMethodInputTarget.getWindow())
+                                == mRemoteInsetsControlTarget)) {
             return mRemoteInsetsControlTarget;
         } else {
             // Now, a special case -- if the last target's window is in the process of exiting, but
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 99ee5e1..e7fbc334 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -32,7 +32,7 @@
  */
 class ImeInsetsSourceProvider extends InsetsSourceProvider {
 
-    private WindowState mImeTargetFromIme;
+    private InsetsControlTarget mImeTargetFromIme;
     private Runnable mShowImeRunner;
     private boolean mIsImeLayoutDrawn;
 
@@ -47,10 +47,12 @@
      *
      * @param imeTarget imeTarget on which IME request is coming from.
      */
-    void scheduleShowImePostLayout(WindowState imeTarget) {
+    void scheduleShowImePostLayout(InsetsControlTarget imeTarget) {
         boolean targetChanged = mImeTargetFromIme != imeTarget
                 && mImeTargetFromIme != null && imeTarget != null && mShowImeRunner != null
-                && mImeTargetFromIme.mActivityRecord == imeTarget.mActivityRecord;
+                && imeTarget.getWindow() != null && mImeTargetFromIme.getWindow() != null
+                && mImeTargetFromIme.getWindow().mActivityRecord
+                        == imeTarget.getWindow().mActivityRecord;
         mImeTargetFromIme = imeTarget;
         if (targetChanged) {
             // target changed, check if new target can show IME.
@@ -62,7 +64,8 @@
             return;
         }
 
-        ProtoLog.d(WM_DEBUG_IME, "Schedule IME show for %s", mImeTargetFromIme.getName());
+        ProtoLog.d(WM_DEBUG_IME, "Schedule IME show for %s", mImeTargetFromIme.getWindow() == null
+                ? mImeTargetFromIme : mImeTargetFromIme.getWindow().getName());
         mShowImeRunner = () -> {
             ProtoLog.d(WM_DEBUG_IME, "Run showImeRunner");
             // Target should still be the same.
@@ -127,13 +130,17 @@
             return false;
         }
         ProtoLog.d(WM_DEBUG_IME, "dcTarget: %s mImeTargetFromIme: %s",
-                dcTarget.getName(), mImeTargetFromIme.getName());
+                dcTarget.getName(), mImeTargetFromIme.getWindow() == null
+                        ? mImeTargetFromIme : mImeTargetFromIme.getWindow().getName());
 
         return (!dcTarget.isClosing() && mImeTargetFromIme == dcTarget)
-                || (mImeTargetFromIme != null && dcTarget.getParentWindow() == mImeTargetFromIme
-                        && dcTarget.mSubLayer > mImeTargetFromIme.mSubLayer)
+                || (mImeTargetFromIme != null && mImeTargetFromIme.getWindow() != null
+                        && dcTarget.getParentWindow() == mImeTargetFromIme
+                        && dcTarget.mSubLayer > mImeTargetFromIme.getWindow().mSubLayer)
                 || mImeTargetFromIme == mDisplayContent.getImeFallback()
-                || (!mImeTargetFromIme.isClosing() && controlTarget == mImeTargetFromIme);
+                || controlTarget == mImeTargetFromIme
+                        && (mImeTargetFromIme.getWindow() == null 
+                                || !mImeTargetFromIme.getWindow().isClosing());
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 85972bf..4df48dc 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -7599,13 +7599,14 @@
                 if (imeTarget == null) {
                     return;
                 }
-                imeTarget = imeTarget.getImeControlTarget().getWindow();
+                final InsetsControlTarget controlTarget = imeTarget.getImeControlTarget();
+                imeTarget = controlTarget.getWindow();
                 // If InsetsControlTarget doesn't have a window, its using remoteControlTarget which
                 // is controlled by default display
                 final DisplayContent dc = imeTarget != null
                         ? imeTarget.getDisplayContent() : getDefaultDisplayContentLocked();
                 dc.getInsetsStateController().getImeSourceProvider()
-                        .scheduleShowImePostLayout(imeTarget);
+                        .scheduleShowImePostLayout(controlTarget);
             }
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
index c9e6284..70e6a34 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
@@ -32,7 +32,10 @@
 
 import static java.lang.Float.NaN;
 
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.IBinder;
@@ -300,6 +303,26 @@
         assertFalse(mWindowMagnificationManager.isConnected());
     }
 
+    @Test
+    public void requestConnection_registerAndUnregisterBroadcastReceiver() {
+        assertTrue(mWindowMagnificationManager.requestConnection(true));
+        verify(mContext).registerReceiver(any(BroadcastReceiver.class),  any(IntentFilter.class));
+
+        assertTrue(mWindowMagnificationManager.requestConnection(false));
+        verify(mContext).unregisterReceiver(any(BroadcastReceiver.class));
+    }
+
+    @Test
+    public void onReceiveScreenOff_removeMagnificationButtonAndDisableWindowMagnification()
+            throws RemoteException {
+        mWindowMagnificationManager.requestConnection(true);
+        mWindowMagnificationManager.mScreenStateReceiver.onReceive(mContext,
+                new Intent(Intent.ACTION_SCREEN_OFF));
+
+        verify(mMockConnection.getConnection()).removeMagnificationButton(TEST_DISPLAY);
+        verify(mMockConnection.getConnection()).disableWindowMagnification(TEST_DISPLAY);
+    }
+
     private MotionEvent generatePointersDownEvent(PointF[] pointersLocation) {
         final int len = pointersLocation.length;
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricServiceBaseTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricServiceBaseTest.java
deleted file mode 100644
index 0c66ee7..0000000
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricServiceBaseTest.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.biometrics.sensors;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.hardware.biometrics.BiometricAuthenticator;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.List;
-
-@Presubmit
-@SmallTest
-public class BiometricServiceBaseTest {
-    private static class TestableBiometricServiceBase extends BiometricServiceBase {
-        TestableBiometricServiceBase(Context context) {
-            super(context);
-        }
-
-        @Override
-        protected void doTemplateCleanupForUser(int userId) {
-        }
-
-        @Override
-        protected String getTag() {
-            return null;
-        }
-
-        @Override
-        protected Object getDaemon() {
-            return null;
-        }
-
-        @Override
-        protected BiometricUtils getBiometricUtils() {
-            return null;
-        }
-
-        @Override
-        protected boolean hasReachedEnrollmentLimit(int userId) {
-            return false;
-        }
-
-        @Override
-        protected void updateActiveGroup(int userId) {
-        }
-
-        @Override
-        protected boolean hasEnrolledBiometrics(int userId) {
-            return false;
-        }
-
-        @Override
-        protected String getManageBiometricPermission() {
-            return null;
-        }
-
-        @Override
-        protected List<? extends BiometricAuthenticator.Identifier> getEnrolledTemplates(
-                int userId) {
-            return null;
-        }
-
-        @Override
-        protected int statsModality() {
-            return 0;
-        }
-    }
-
-    private static final int CLIENT_COOKIE = 0xc00c1e;
-
-    private BiometricServiceBase mBiometricServiceBase;
-
-    @Mock
-    private Context mContext;
-    @Mock
-    private Resources mResources;
-    @Mock
-    private BiometricAuthenticator.Identifier mIdentifier;
-    @Mock
-    private ClientMonitor mClient;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-
-        when(mContext.getResources()).thenReturn(mResources);
-        when(mResources.getString(anyInt())).thenReturn("");
-        when(mClient.getCookie()).thenReturn(CLIENT_COOKIE);
-
-        mBiometricServiceBase = new TestableBiometricServiceBase(mContext);
-    }
-
-    @Test
-    public void testHandleEnumerate_doesNotCrash_withNullClient() {
-        mBiometricServiceBase.handleEnumerate(mIdentifier, 0 /* remaining */);
-    }
-}
diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
index bc987a6..71a1964 100644
--- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
@@ -623,6 +623,10 @@
     }
 
     private static int getCarrierPrivilegeStatus(Context context, int subId, int uid) {
+        if (uid == Process.SYSTEM_UID || uid == Process.PHONE_UID) {
+            // Skip the check if it's one of these special uids
+            return TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
+        }
         final long identity = Binder.clearCallingIdentity();
         try {
             TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(
diff --git a/tools/validatekeymaps/Android.bp b/tools/validatekeymaps/Android.bp
index 819e75b..61ce44c 100644
--- a/tools/validatekeymaps/Android.bp
+++ b/tools/validatekeymaps/Android.bp
@@ -20,6 +20,7 @@
         "libutils",
         "libcutils",
         "liblog",
+        "libui-types",
     ],
 
     // This tool is prebuilt if we're doing an app-only build.