Merge "Don't collapse the panel post launch if we went back to sleep." into tm-dev
diff --git a/ApiDocs.bp b/ApiDocs.bp
index 5b7c125..7f5d4a3 100644
--- a/ApiDocs.bp
+++ b/ApiDocs.bp
@@ -94,7 +94,7 @@
         ":framework-scheduling-sources",
         ":framework-sdkextensions-sources",
         ":framework-statsd-sources",
-        ":framework-supplementalprocess-sources",
+        ":framework-sdksandbox-sources",
         ":framework-tethering-srcs",
         ":framework-uwb-updatable-sources",
         ":framework-wifi-updatable-sources",
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
index ffa534e..c83ca8c 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
@@ -1377,7 +1377,7 @@
     }
 
     private boolean isAllowedBlobAccess(int uid, String packageName) {
-        return (!Process.isSupplemental(uid) && !Process.isIsolated(uid)
+        return (!Process.isSdkSandboxUid(uid) && !Process.isIsolated(uid)
                 && !mPackageManagerInternal.isInstantApp(packageName, UserHandle.getUserId(uid)));
     }
 
diff --git a/api/Android.bp b/api/Android.bp
index 9e377a0..464c163 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -123,7 +123,7 @@
         "framework-scheduling",
         "framework-sdkextensions",
         "framework-statsd",
-        "framework-supplementalprocess",
+        "framework-sdksandbox",
         "framework-tethering",
         "framework-uwb",
         "framework-wifi",
@@ -136,7 +136,7 @@
     system_server_classpath: [
         "service-media-s",
         "service-permission",
-        "service-supplementalprocess",
+        "service-sdksandbox",
     ],
 }
 
diff --git a/cmds/svc/src/com/android/commands/svc/BluetoothCommand.java b/cmds/svc/src/com/android/commands/svc/BluetoothCommand.java
deleted file mode 100644
index b572ce2..0000000
--- a/cmds/svc/src/com/android/commands/svc/BluetoothCommand.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2017 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.commands.svc;
-
-import android.bluetooth.BluetoothAdapter;
-import android.os.RemoteException;
-
-public class BluetoothCommand extends Svc.Command {
-
-    public BluetoothCommand() {
-        super("bluetooth");
-    }
-
-    @Override
-    public String shortHelp() {
-        return "Control Bluetooth service";
-    }
-
-    @Override
-    public String longHelp() {
-        return shortHelp() + "\n"
-                + "\n"
-                + "usage: svc bluetooth [enable|disable]\n"
-                + "         Turn Bluetooth on or off.\n\n";
-    }
-
-    @Override
-    public void run(String[] args) {
-        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
-
-        if (adapter == null) {
-            System.err.println("Got a null BluetoothAdapter, is the system running?");
-            return;
-        }
-
-        if (args.length == 2 && "enable".equals(args[1])) {
-            adapter.enable();
-        } else if (args.length == 2 && "disable".equals(args[1])) {
-            adapter.disable();
-        } else {
-            System.err.println(longHelp());
-        }
-    }
-}
diff --git a/cmds/svc/src/com/android/commands/svc/Svc.java b/cmds/svc/src/com/android/commands/svc/Svc.java
index 2ed2678..bbad984 100644
--- a/cmds/svc/src/com/android/commands/svc/Svc.java
+++ b/cmds/svc/src/com/android/commands/svc/Svc.java
@@ -96,7 +96,7 @@
             // `svc wifi` has been migrated to WifiShellCommand
             new UsbCommand(),
             new NfcCommand(),
-            new BluetoothCommand(),
+            // `svc bluetooth` has been migrated to BluetoothShellCommand
             new SystemServerCommand(),
     };
 }
diff --git a/cmds/svc/svc b/cmds/svc/svc
index 95265e8..a2c9de3 100755
--- a/cmds/svc/svc
+++ b/cmds/svc/svc
@@ -33,6 +33,25 @@
     exit 1
 fi
 
+# `svc bluetooth` has been migrated to BluetoothShellCommand,
+# simply perform translation to `cmd bluetooth set-bluetooth-enabled` here.
+if [ "x$1" == "xbluetooth" ]; then
+    # `cmd wifi` by convention uses enabled/disabled
+    # instead of enable/disable
+    if [ "x$2" == "xenable" ]; then
+        exec cmd bluetooth_manager enable
+    elif [ "x$2" == "xdisable" ]; then
+        exec cmd bluetooth_manager disable
+    else
+        echo "Control the Bluetooth manager"
+        echo ""
+        echo "usage: svc bluetooth [enable|disable]"
+        echo "         Turn Bluetooth on or off."
+        echo ""
+    fi
+    exit 1
+fi
+
 export CLASSPATH=/system/framework/svc.jar
 exec app_process /system/bin com.android.commands.svc.Svc "$@"
 
diff --git a/core/api/current.txt b/core/api/current.txt
index 27a45ac..d8ea767 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -7388,8 +7388,8 @@
     method public int getCurrentFailedPasswordAttempts();
     method @Nullable public java.util.List<java.lang.String> getDelegatePackages(@NonNull android.content.ComponentName, @NonNull String);
     method @NonNull public java.util.List<java.lang.String> getDelegatedScopes(@Nullable android.content.ComponentName, @NonNull String);
-    method @Nullable public String getDeviceManagerRoleHolderPackageName();
     method public CharSequence getDeviceOwnerLockScreenInfo();
+    method @Nullable public String getDevicePolicyManagementRoleHolderPackage();
     method @Nullable public android.graphics.drawable.Drawable getDrawable(@NonNull String, @NonNull String, @NonNull java.util.function.Supplier<android.graphics.drawable.Drawable>);
     method @Nullable public android.graphics.drawable.Drawable getDrawable(@NonNull String, @NonNull String, @NonNull String, @NonNull java.util.function.Supplier<android.graphics.drawable.Drawable>);
     method @Nullable public android.graphics.drawable.Icon getDrawableAsIcon(@NonNull String, @NonNull String, @NonNull String, @Nullable android.graphics.drawable.Icon);
@@ -30867,6 +30867,7 @@
     field public static final int PREVIEW_SDK_INT;
     field public static final String RELEASE;
     field @NonNull public static final String RELEASE_OR_CODENAME;
+    field @NonNull public static final String RELEASE_OR_PREVIEW_DISPLAY;
     field @Deprecated public static final String SDK;
     field public static final int SDK_INT;
     field public static final String SECURITY_PATCH;
@@ -31858,7 +31859,7 @@
     method public static final boolean is64Bit();
     method public static boolean isApplicationUid(int);
     method public static final boolean isIsolated();
-    method public static final boolean isSupplemental();
+    method public static final boolean isSdkSandbox();
     method public static final void killProcess(int);
     method public static final int myPid();
     method @NonNull public static String myProcessName();
@@ -40754,8 +40755,9 @@
     method public String getDefaultDialerPackage();
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.telecom.PhoneAccountHandle getDefaultOutgoingPhoneAccount(String);
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_SMS, android.Manifest.permission.READ_PHONE_NUMBERS}, conditional=true) public String getLine1Number(android.telecom.PhoneAccountHandle);
+    method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_OWN_CALLS) public java.util.List<android.telecom.PhoneAccountHandle> getOwnSelfManagedPhoneAccounts();
     method public android.telecom.PhoneAccount getPhoneAccount(android.telecom.PhoneAccountHandle);
-    method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.MANAGE_OWN_CALLS}) public java.util.List<android.telecom.PhoneAccountHandle> getSelfManagedPhoneAccounts();
+    method @NonNull @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telecom.PhoneAccountHandle> getSelfManagedPhoneAccounts();
     method public android.telecom.PhoneAccountHandle getSimCallManager();
     method @Nullable public android.telecom.PhoneAccountHandle getSimCallManagerForSubscription(int);
     method @Nullable public String getSystemDialerPackage();
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 7ec239d..a268f85 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -120,7 +120,7 @@
 
   public abstract class PackageManager {
     method @NonNull public String getPermissionControllerPackageName();
-    method @NonNull public String getSupplementalProcessPackageName();
+    method @NonNull public String getSdkSandboxPackageName();
     field public static final int MATCH_STATIC_SHARED_AND_SDK_LIBRARIES = 67108864; // 0x4000000
   }
 
@@ -362,9 +362,9 @@
   }
 
   public class Process {
-    method public static final boolean isSupplemental(int);
+    method public static final boolean isSdkSandboxUid(int);
     method public static final int toAppUid(int);
-    method public static final int toSupplementalUid(int);
+    method public static final int toSdkSandboxUid(int);
     field public static final int NFC_UID = 1027; // 0x403
     field public static final int VPN_UID = 1016; // 0x3f8
   }
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index e611d6b..1c373b2 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -416,7 +416,7 @@
     field public static final int config_defaultCallScreening = 17039398; // 0x1040026
     field public static final int config_defaultDialer = 17039395; // 0x1040023
     field public static final int config_defaultSms = 17039396; // 0x1040024
-    field public static final int config_deviceManager;
+    field public static final int config_devicePolicyManagement;
     field public static final int config_feedbackIntentExtraKey = 17039391; // 0x104001f
     field public static final int config_feedbackIntentNameKey = 17039392; // 0x1040020
     field public static final int config_helpIntentExtraKey = 17039389; // 0x104001d
@@ -6329,7 +6329,6 @@
     method public int describeContents();
     method @NonNull public String getMediaRoute2Id();
     method public int getRangeZone();
-    method @NonNull public static String rangeZoneToString(int);
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.media.NearbyDevice> CREATOR;
     field public static final int RANGE_CLOSE = 3; // 0x3
@@ -10524,6 +10523,7 @@
     field public static final String ACTION_SHOW_ADMIN_SUPPORT_DETAILS = "android.settings.SHOW_ADMIN_SUPPORT_DETAILS";
     field public static final String ACTION_TETHER_PROVISIONING_UI = "android.settings.TETHER_PROVISIONING_UI";
     field public static final String ACTION_TETHER_SETTINGS = "android.settings.TETHER_SETTINGS";
+    field public static final String ACTION_TETHER_UNSUPPORTED_CARRIER_UI = "android.settings.TETHER_UNSUPPORTED_CARRIER_UI";
   }
 
   public static final class Settings.Global extends android.provider.Settings.NameValueTable {
@@ -15701,6 +15701,11 @@
 
 package android.view {
 
+  @UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback {
+    method @NonNull public final java.util.List<android.graphics.Rect> getUnrestrictedPreferKeepClearRects();
+    method @RequiresPermission(android.Manifest.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS) public final void setUnrestrictedPreferKeepClearRects(@NonNull java.util.List<android.graphics.Rect>);
+  }
+
   public abstract class Window {
     method public void addSystemFlags(@android.view.WindowManager.LayoutParams.SystemFlags int);
   }
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 22637ca..c36b94b 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -155,8 +155,10 @@
 
   public class ActivityOptions {
     method @NonNull public static android.app.ActivityOptions fromBundle(@NonNull android.os.Bundle);
+    method public boolean isEligibleForLegacyPermissionPrompt();
     method @NonNull public static android.app.ActivityOptions makeCustomAnimation(@NonNull android.content.Context, int, int, int, @Nullable android.os.Handler, @Nullable android.app.ActivityOptions.OnAnimationStartedListener, @Nullable android.app.ActivityOptions.OnAnimationFinishedListener);
     method @NonNull @RequiresPermission(android.Manifest.permission.START_TASKS_FROM_RECENTS) public static android.app.ActivityOptions makeCustomTaskAnimation(@NonNull android.content.Context, int, int, @Nullable android.os.Handler, @Nullable android.app.ActivityOptions.OnAnimationStartedListener, @Nullable android.app.ActivityOptions.OnAnimationFinishedListener);
+    method public void setEligibleForLegacyPermissionPrompt(boolean);
     method public static void setExitTransitionTimeout(long);
     method public void setLaunchActivityType(int);
     method public void setLaunchWindowingMode(int);
@@ -514,6 +516,7 @@
     method public boolean isCurrentInputMethodSetByOwner();
     method public boolean isFactoryResetProtectionPolicySupported();
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public boolean isNewUserDisclaimerAcknowledged();
+    method public boolean isRemovingAdmin(@NonNull android.content.ComponentName, int);
     method @RequiresPermission(anyOf={android.Manifest.permission.MARK_DEVICE_ORGANIZATION_OWNED, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}, conditional=true) public void markProfileOwnerOnOrganizationOwnedDevice(@NonNull android.content.ComponentName);
     method @NonNull public static String operationSafetyReasonToString(int);
     method @NonNull public static String operationToString(int);
@@ -826,9 +829,9 @@
     method @NonNull public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplicationsAsUser(@NonNull android.content.pm.PackageManager.ApplicationInfoFlags, int);
     method @Nullable public abstract String[] getNamesForUids(int[]);
     method @NonNull public String getPermissionControllerPackageName();
+    method @NonNull public String getSdkSandboxPackageName();
     method @NonNull public abstract String getServicesSystemSharedLibraryPackageName();
     method @NonNull public abstract String getSharedSystemSharedLibraryPackageName();
-    method @NonNull public String getSupplementalProcessPackageName();
     method @Nullable public String getSystemTextClassifierPackageName();
     method @Nullable public String getWellbeingPackageName();
     method public void holdLock(android.os.IBinder, int);
@@ -1774,7 +1777,7 @@
 
   public class Process {
     method public static final int getThreadScheduler(int) throws java.lang.IllegalArgumentException;
-    method public static final int toSupplementalUid(int);
+    method public static final int toSdkSandboxUid(int);
     field public static final int FIRST_APP_ZYGOTE_ISOLATED_UID = 90000; // 0x15f90
     field public static final int FIRST_ISOLATED_UID = 99000; // 0x182b8
     field public static final int LAST_APP_ZYGOTE_ISOLATED_UID = 98999; // 0x182b7
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 5ddaa80..1d14307 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -174,6 +174,13 @@
     public static final String KEY_SPLASH_SCREEN_THEME = "android.activity.splashScreenTheme";
 
     /**
+     * Indicates that this activity launch is eligible to show a legacy permission prompt
+     * @hide
+     */
+    public static final String KEY_LEGACY_PERMISSION_PROMPT_ELIGIBLE =
+            "android:activity.legacyPermissionPromptEligible";
+
+    /**
      * Callback for when the last frame of the animation is played.
      * @hide
      */
@@ -445,6 +452,7 @@
     private String mSplashScreenThemeResName;
     @SplashScreen.SplashScreenStyle
     private int mSplashScreenStyle = SplashScreen.SPLASH_SCREEN_STYLE_UNDEFINED;
+    private boolean mIsEligibleForLegacyPermissionPrompt;
     private boolean mRemoveWithTaskOrganizer;
     private boolean mLaunchedFromBubble;
     private boolean mTransientLaunch;
@@ -1243,6 +1251,8 @@
         mTransientLaunch = opts.getBoolean(KEY_TRANSIENT_LAUNCH);
         mSplashScreenStyle = opts.getInt(KEY_SPLASH_SCREEN_STYLE);
         mLaunchIntoPipParams = opts.getParcelable(KEY_LAUNCH_INTO_PIP_PARAMS);
+        mIsEligibleForLegacyPermissionPrompt =
+                opts.getBoolean(KEY_LEGACY_PERMISSION_PROMPT_ELIGIBLE);
     }
 
     /**
@@ -1474,6 +1484,24 @@
     }
 
     /**
+     * Whether the activity is eligible to show a legacy permission prompt
+     * @hide
+     */
+    @TestApi
+    public boolean isEligibleForLegacyPermissionPrompt() {
+        return mIsEligibleForLegacyPermissionPrompt;
+    }
+
+    /**
+     * Sets whether the activity is eligible to show a legacy permission prompt
+     * @hide
+     */
+    @TestApi
+    public void setEligibleForLegacyPermissionPrompt(boolean eligible) {
+        mIsEligibleForLegacyPermissionPrompt = eligible;
+    }
+
+    /**
      * Sets whether the activity is to be launched into LockTask mode.
      *
      * Use this option to start an activity in LockTask mode. Note that only apps permitted by
@@ -1909,6 +1937,7 @@
         mSpecsFuture = otherOptions.mSpecsFuture;
         mRemoteAnimationAdapter = otherOptions.mRemoteAnimationAdapter;
         mLaunchIntoPipParams = otherOptions.mLaunchIntoPipParams;
+        mIsEligibleForLegacyPermissionPrompt = otherOptions.mIsEligibleForLegacyPermissionPrompt;
     }
 
     /**
@@ -2084,6 +2113,10 @@
         if (mLaunchIntoPipParams != null) {
             b.putParcelable(KEY_LAUNCH_INTO_PIP_PARAMS, mLaunchIntoPipParams);
         }
+        if (mIsEligibleForLegacyPermissionPrompt) {
+            b.putBoolean(KEY_LEGACY_PERMISSION_PROMPT_ELIGIBLE,
+                    mIsEligibleForLegacyPermissionPrompt);
+        }
         return b;
     }
 
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 20ffa25..dca5c54 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -165,50 +165,35 @@
     public static final String PERMISSION_CONTROLLER_RESOURCE_PACKAGE =
             "com.android.permissioncontroller";
 
-    private final Object mLock = new Object();
-
-    @GuardedBy("mLock")
-    private UserManager mUserManager;
-    @GuardedBy("mLock")
-    private PermissionManager mPermissionManager;
-    @GuardedBy("mLock")
-    private PackageInstaller mInstaller;
-    @GuardedBy("mLock")
-    private ArtManager mArtManager;
-    @GuardedBy("mLock")
-    private DevicePolicyManager mDevicePolicyManager;
+    private volatile UserManager mUserManager;
+    private volatile PermissionManager mPermissionManager;
+    private volatile PackageInstaller mInstaller;
+    private volatile ArtManager mArtManager;
+    private volatile DevicePolicyManager mDevicePolicyManager;
+    private volatile String mPermissionsControllerPackageName;
 
     @GuardedBy("mDelegates")
     private final ArrayList<MoveCallbackDelegate> mDelegates = new ArrayList<>();
 
-    @GuardedBy("mLock")
-    private String mPermissionsControllerPackageName;
-
     UserManager getUserManager() {
-        synchronized (mLock) {
-            if (mUserManager == null) {
-                mUserManager = UserManager.get(mContext);
-            }
-            return mUserManager;
+        if (mUserManager == null) {
+            mUserManager = UserManager.get(mContext);
         }
+        return mUserManager;
     }
 
     DevicePolicyManager getDevicePolicyManager() {
-        synchronized (mLock) {
-            if (mDevicePolicyManager == null) {
-                mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
-            }
-            return mDevicePolicyManager;
+        if (mDevicePolicyManager == null) {
+            mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
         }
+        return mDevicePolicyManager;
     }
 
     private PermissionManager getPermissionManager() {
-        synchronized (mLock) {
-            if (mPermissionManager == null) {
-                mPermissionManager = mContext.getSystemService(PermissionManager.class);
-            }
-            return mPermissionManager;
+        if (mPermissionManager == null) {
+            mPermissionManager = mContext.getSystemService(PermissionManager.class);
         }
+        return mPermissionManager;
     }
 
     @Override
@@ -851,25 +836,23 @@
      */
     @Override
     public String getPermissionControllerPackageName() {
-        synchronized (mLock) {
-            if (mPermissionsControllerPackageName == null) {
-                try {
-                    mPermissionsControllerPackageName = mPM.getPermissionControllerPackageName();
-                } catch (RemoteException e) {
-                    throw e.rethrowFromSystemServer();
-                }
+        if (mPermissionsControllerPackageName == null) {
+            try {
+                mPermissionsControllerPackageName = mPM.getPermissionControllerPackageName();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
             }
-            return mPermissionsControllerPackageName;
         }
+        return mPermissionsControllerPackageName;
     }
 
     /**
      * @hide
      */
     @Override
-    public String getSupplementalProcessPackageName() {
+    public String getSdkSandboxPackageName() {
         try {
-            return mPM.getSupplementalProcessPackageName();
+            return mPM.getSdkSandboxPackageName();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -3235,17 +3218,15 @@
 
     @Override
     public PackageInstaller getPackageInstaller() {
-        synchronized (mLock) {
-            if (mInstaller == null) {
-                try {
-                    mInstaller = new PackageInstaller(mPM.getPackageInstaller(),
-                            mContext.getPackageName(), mContext.getAttributionTag(), getUserId());
-                } catch (RemoteException e) {
-                    throw e.rethrowFromSystemServer();
-                }
+        if (mInstaller == null) {
+            try {
+                mInstaller = new PackageInstaller(mPM.getPackageInstaller(),
+                        mContext.getPackageName(), mContext.getAttributionTag(), getUserId());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
             }
-            return mInstaller;
         }
+        return mInstaller;
     }
 
     @Override
@@ -3583,16 +3564,14 @@
 
     @Override
     public ArtManager getArtManager() {
-        synchronized (mLock) {
-            if (mArtManager == null) {
-                try {
-                    mArtManager = new ArtManager(mContext, mPM.getArtManager());
-                } catch (RemoteException e) {
-                    throw e.rethrowFromSystemServer();
-                }
+        if (mArtManager == null) {
+            try {
+                mArtManager = new ArtManager(mContext, mPM.getArtManager());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
             }
-            return mArtManager;
         }
+        return mArtManager;
     }
 
     @Override
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index 87ba197..cedf483e 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -184,8 +184,17 @@
     })
     @interface LockTypes {}
 
-    // TODO(b/220379118): register only one binder listener and keep a map of listener to executor.
-    private final ArrayMap<KeyguardLockedStateListener, IKeyguardLockedStateListener>
+    private final IKeyguardLockedStateListener mIKeyguardLockedStateListener =
+            new IKeyguardLockedStateListener.Stub() {
+                @Override
+                public void onKeyguardLockedStateChanged(boolean isKeyguardLocked) {
+                    mKeyguardLockedStateListeners.forEach((listener, executor) -> {
+                        executor.execute(
+                                () -> listener.onKeyguardLockedStateChanged(isKeyguardLocked));
+                    });
+                }
+            };
+    private final ArrayMap<KeyguardLockedStateListener, Executor>
             mKeyguardLockedStateListeners = new ArrayMap<>();
 
     /**
@@ -1102,17 +1111,12 @@
     public void addKeyguardLockedStateListener(@NonNull @CallbackExecutor Executor executor,
             @NonNull KeyguardLockedStateListener listener) {
         synchronized (mKeyguardLockedStateListeners) {
+            mKeyguardLockedStateListeners.put(listener, executor);
+            if (mKeyguardLockedStateListeners.size() > 1) {
+                return;
+            }
             try {
-                final IKeyguardLockedStateListener innerListener =
-                        new IKeyguardLockedStateListener.Stub() {
-                    @Override
-                    public void onKeyguardLockedStateChanged(boolean isKeyguardLocked) {
-                        executor.execute(
-                                () -> listener.onKeyguardLockedStateChanged(isKeyguardLocked));
-                    }
-                };
-                mWM.addKeyguardLockedStateListener(innerListener);
-                mKeyguardLockedStateListeners.put(listener, innerListener);
+                mWM.addKeyguardLockedStateListener(mIKeyguardLockedStateListener);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -1125,17 +1129,15 @@
     @RequiresPermission(Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE)
     public void removeKeyguardLockedStateListener(@NonNull KeyguardLockedStateListener listener) {
         synchronized (mKeyguardLockedStateListeners) {
-            IKeyguardLockedStateListener innerListener = mKeyguardLockedStateListeners.get(
-                    listener);
-            if (innerListener == null) {
+            mKeyguardLockedStateListeners.remove(listener);
+            if (!mKeyguardLockedStateListeners.isEmpty()) {
                 return;
             }
             try {
-                mWM.removeKeyguardLockedStateListener(innerListener);
+                mWM.removeKeyguardLockedStateListener(mIKeyguardLockedStateListener);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
-            mKeyguardLockedStateListeners.remove(listener);
         }
     }
 }
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index e460638..c6e36a3 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -335,7 +335,7 @@
      * Constant for {@link #setNavBarMode(int)} indicating kids navbar mode.
      *
      * <p>When used, back and home icons will change drawables and layout, recents will be hidden,
-     * and the navbar will remain visible when apps are in immersive mode.
+     * and enables the setting to force navbar visible, even when apps are in immersive mode.
      *
      * @hide
      */
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index eeb4705..58db93c 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -18,6 +18,7 @@
 
 import android.accounts.AccountManager;
 import android.accounts.IAccountManager;
+import android.adservices.AdServicesFrameworkInitializer;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
@@ -36,6 +37,7 @@
 import android.app.people.PeopleManager;
 import android.app.prediction.AppPredictionManager;
 import android.app.role.RoleFrameworkInitializer;
+import android.app.sdksandbox.SdkSandboxManagerFrameworkInitializer;
 import android.app.search.SearchUiManager;
 import android.app.slice.SliceManager;
 import android.app.smartspace.SmartspaceManager;
@@ -208,7 +210,6 @@
 import android.service.persistentdata.IPersistentDataBlockService;
 import android.service.persistentdata.PersistentDataBlockManager;
 import android.service.vr.IVrManager;
-import android.supplementalprocess.SupplementalProcessFrameworkInitializer;
 import android.telecom.TelecomManager;
 import android.telephony.MmsManager;
 import android.telephony.TelephonyFrameworkInitializer;
@@ -1564,7 +1565,8 @@
             MediaFrameworkInitializer.registerServiceWrappers();
             RoleFrameworkInitializer.registerServiceWrappers();
             SchedulingFrameworkInitializer.registerServiceWrappers();
-            SupplementalProcessFrameworkInitializer.registerServiceWrappers();
+            SdkSandboxManagerFrameworkInitializer.registerServiceWrappers();
+            AdServicesFrameworkInitializer.registerServiceWrappers();
             UwbFrameworkInitializer.registerServiceWrappers();
             SafetyCenterFrameworkInitializer.registerServiceWrappers();
             ConnectivityFrameworkInitializerTiramisu.registerServiceWrappers();
diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java
index b791f05..5c1ab38 100644
--- a/core/java/android/app/WindowConfiguration.java
+++ b/core/java/android/app/WindowConfiguration.java
@@ -841,15 +841,6 @@
     }
 
     /**
-     * Returns true if the windowingMode represents a split window.
-     * @hide
-     */
-    public static boolean isSplitScreenWindowingMode(int windowingMode) {
-        return windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
-                || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-    }
-
-    /**
      * Returns true if the windows associated with this window configuration can receive input keys.
      * @hide
      */
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index b944468..a2df2d0 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -545,15 +545,16 @@
             = "android.app.action.PROVISION_FINALIZATION";
 
     /**
-     * Activity action: starts the managed profile provisioning flow inside the device management
-     * role holder.
+     * Activity action: starts the managed profile provisioning flow inside the device policy
+     * management role holder.
      *
      * <p>During the managed profile provisioning flow, the platform-provided provisioning handler
-     * will delegate provisioning to the device management role holder, by firing this intent.
-     * Third-party mobile device management applications attempting to fire this intent will
+     * will delegate provisioning to the device policy management role holder, by firing this
+     * intent. Third-party mobile device management applications attempting to fire this intent will
      * receive a {@link SecurityException}.
      *
-     * <p>Device management role holders are required to have a handler for this intent action.
+     * <p>Device policy management role holders are required to have a handler for this intent
+     * action.
      *
      * <p>If {@link #EXTRA_ROLE_HOLDER_STATE} is supplied to this intent, it is the responsibility
      * of the role holder to restore its state from this extra. This is the same {@link Bundle}
@@ -596,15 +597,16 @@
     public static final int RESULT_DEVICE_OWNER_SET = 123;
 
     /**
-     * Activity action: starts the trusted source provisioning flow inside the device management
-     * role holder.
+     * Activity action: starts the trusted source provisioning flow inside the device policy
+     * management role holder.
      *
      * <p>During the trusted source provisioning flow, the platform-provided provisioning handler
-     * will delegate provisioning to the device management role holder, by firing this intent.
-     * Third-party mobile device management applications attempting to fire this intent will
+     * will delegate provisioning to the device policy management role holder, by firing this
+     * intent. Third-party mobile device management applications attempting to fire this intent will
      * receive a {@link SecurityException}.
      *
-     * <p>Device management role holders are required to have a handler for this intent action.
+     * <p>Device policy management role holders are required to have a handler for this intent
+     * action.
      *
      * <p>If {@link #EXTRA_ROLE_HOLDER_STATE} is supplied to this intent, it is the responsibility
      * of the role holder to restore its state from this extra. This is the same {@link Bundle}
@@ -624,15 +626,16 @@
             "android.app.action.ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
 
     /**
-     * Activity action: starts the provisioning finalization flow inside the device management
-     * role holder.
+     * Activity action: starts the provisioning finalization flow inside the device policy
+     * management role holder.
      *
      * <p>During the provisioning finalization flow, the platform-provided provisioning handler
-     * will delegate provisioning to the device management role holder, by firing this intent.
-     * Third-party mobile device management applications attempting to fire this intent will
+     * will delegate provisioning to the device policy management role holder, by firing this
+     * intent. Third-party mobile device management applications attempting to fire this intent will
      * receive a {@link SecurityException}.
      *
-     * <p>Device management role holders are required to have a handler for this intent action.
+     * <p>Device policy management role holders are required to have a handler for this intent
+     * action.
      *
      * <p>This handler forwards the result from the admin app's {@link
      * #ACTION_ADMIN_POLICY_COMPLIANCE} handler. Result code {@link Activity#RESULT_CANCELED}
@@ -697,9 +700,9 @@
      * A boolean extra indicating whether offline provisioning is allowed.
      *
      * <p>For the online provisioning flow, there will be an attempt to download and install
-     * the latest version of the device management role holder. The platform will then delegate
-     * provisioning to the device management role holder via role holder-specific provisioning
-     * actions.
+     * the latest version of the device policy management role holder. The platform will then
+     * delegate provisioning to the device policy management role holder via role holder-specific
+     * provisioning actions.
      *
      * <p>For the offline provisioning flow, the provisioning flow will always be handled by
      * the platform.
@@ -720,8 +723,8 @@
             "android.app.extra.PROVISIONING_ALLOW_OFFLINE";
 
     /**
-     * A String extra holding a url that specifies the download location of the device manager
-     * role holder package.
+     * A String extra holding a url that specifies the download location of the device policy
+     * management role holder package.
      *
      * <p>This is only meant to be used in cases when a specific variant of the role holder package
      * is needed (such as a debug variant). If not provided, the default variant of the device
@@ -777,13 +780,13 @@
 
     /**
      * An extra of type {@link android.os.PersistableBundle} that allows the provisioning initiator
-     * to pass data to the device manager role holder.
+     * to pass data to the device policy management role holder.
      *
-     * <p>The device manager role holder will receive this extra via the {@link
+     * <p>The device policy management role holder will receive this extra via the {@link
      * #ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} intent.
      *
      * <p>The contents of this extra are up to the contract between the provisioning initiator
-     * and the device manager role holder.
+     * and the device policy management role holder.
      *
      * <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}
      * or in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner
@@ -814,18 +817,19 @@
      * <p>If {@link #EXTRA_PROVISIONING_SHOULD_LAUNCH_RESULT_INTENT} is set to {@code false},
      * this result will be supplied as part of the result {@link Intent} for provisioning actions
      * such as {@link #ACTION_PROVISION_MANAGED_PROFILE}. This result will also be supplied as
-     * part of the result {@link Intent} for the device manager role holder provisioning actions.
+     * part of the result {@link Intent} for the device policy management role holder provisioning
+     * actions.
      */
     public static final String EXTRA_RESULT_LAUNCH_INTENT =
             "android.app.extra.RESULT_LAUNCH_INTENT";
 
     /**
      * A boolean extra that determines whether the provisioning flow should launch the resulting
-     * launch intent, if one is supplied by the device manager role holder via {@link
+     * launch intent, if one is supplied by the device policy management role holder via {@link
      * #EXTRA_RESULT_LAUNCH_INTENT}. Default value is {@code false}.
      *
      * <p>If {@code true}, the resulting intent will be launched by the provisioning flow, if one
-     * is supplied by the device manager role holder.
+     * is supplied by the device policy management role holder.
      *
      * <p>If {@code false}, the resulting intent will be returned as {@link
      * #EXTRA_RESULT_LAUNCH_INTENT} to the provisioning initiator, if one is supplied by the device
@@ -3220,10 +3224,10 @@
             "android.app.action.ADMIN_POLICY_COMPLIANCE";
 
     /**
-     * Activity action: Starts the device management role holder updater.
+     * Activity action: Starts the device policy management role holder updater.
      *
-     * <p>The activity must handle the device management role holder update and set the intent
-     * result to either {@link Activity#RESULT_OK} if the update was successful, {@link
+     * <p>The activity must handle the device policy management role holder update and set the
+     * intent result to either {@link Activity#RESULT_OK} if the update was successful, {@link
      * #RESULT_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER_RECOVERABLE_ERROR} if it encounters a problem
      * that may be solved by relaunching it again, or {@link
      * #RESULT_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER_UNRECOVERABLE_ERROR} if it encounters a problem
@@ -3261,9 +3265,9 @@
     /**
      * An {@link Intent} extra which resolves to a custom user consent screen.
      *
-     * <p>If this extra is provided to the device management role holder via either {@link
+     * <p>If this extra is provided to the device policy management role holder via either {@link
      * #ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} or {@link
-     * #ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE}, the device management role holder must
+     * #ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE}, the device policy management role holder must
      * launch this intent which shows the custom user consent screen, replacing its own standard
      * consent screen.
      *
@@ -3279,9 +3283,9 @@
      * </ul>
      *
      * <p>If the custom consent screens are granted by the user {@link Activity#RESULT_OK} is
-     * returned, otherwise {@link Activity#RESULT_CANCELED} is returned. The device management
-     * role holder should ensure that the provisioning flow terminates immediately if consent
-     * is not granted by the user.
+     * returned, otherwise {@link Activity#RESULT_CANCELED} is returned. The device policy
+     * management role holder should ensure that the provisioning flow terminates immediately if
+     * consent is not granted by the user.
      *
      * @hide
      */
@@ -3762,6 +3766,7 @@
      * for the user.
      * @hide
      */
+    @TestApi
     public boolean isRemovingAdmin(@NonNull ComponentName admin, int userId) {
         if (mService != null) {
             try {
@@ -15647,14 +15652,15 @@
     }
 
     /**
-     * Returns the package name of the device manager role holder.
+     * Returns the package name of the device policy management role holder.
      *
-     * <p>If the device manager role holder is not configured for this device, returns {@code null}.
+     * <p>If the device policy management role holder is not configured for this device, returns
+     * {@code null}.
      */
     @Nullable
-    public String getDeviceManagerRoleHolderPackageName() {
-        String deviceManagerConfig =
-                mContext.getString(com.android.internal.R.string.config_deviceManager);
+    public String getDevicePolicyManagementRoleHolderPackage() {
+        String deviceManagerConfig = mContext.getString(
+                com.android.internal.R.string.config_devicePolicyManagement);
         return extractPackageNameFromDeviceManagerConfig(deviceManagerConfig);
     }
 
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 30aed8b..e9e6cd3 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -653,7 +653,7 @@
 
     @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     String getPermissionControllerPackageName();
-    String getSupplementalProcessPackageName();
+    String getSdkSandboxPackageName();
 
     ParceledListSlice getInstantApps(int userId);
     byte[] getInstantAppCookie(String packageName, int userId);
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index a162c41..f4bc161 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -5772,16 +5772,16 @@
     }
 
     /**
-     * Returns the package name of the component implementing supplemental process service.
+     * Returns the package name of the component implementing sdk sandbox service.
      *
-     * @return the package name of the component implementing supplemental process service
+     * @return the package name of the component implementing sdk sandbox service
      *
      * @hide
      */
     @NonNull
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     @TestApi
-    public String getSupplementalProcessPackageName() {
+    public String getSdkSandboxPackageName() {
         throw new RuntimeException("Not implemented. Must override in a subclass.");
     }
 
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index ebef053..a03286d3 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -2674,9 +2674,8 @@
         // Putting into a map keyed on the apk assets to deduplicate resources that are different
         // objects but ultimately represent the same assets
         Map<List<ApkAssets>, Resources> history = new ArrayMap<>();
-        for (Resources r : sResourcesHistory) {
-            history.put(Arrays.asList(r.mResourcesImpl.mAssets.getApkAssets()), r);
-        }
+        sResourcesHistory.forEach(
+                r -> history.put(Arrays.asList(r.mResourcesImpl.mAssets.getApkAssets()), r));
         int i = 0;
         for (Resources r : history.values()) {
             if (r != null) {
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index ada5155..3f139f0 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -104,16 +104,16 @@
     public static final int BIOMETRIC_MULTI_SENSOR_DEFAULT = 0;
 
     /**
-     * Prefer the face sensor and fall back to fingerprint when needed.
+     * Use face and fingerprint sensors together.
      * @hide
      */
-    public static final int BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT = 1;
+    public static final int BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE = 1;
 
     /**
      * @hide
      */
     @IntDef({BIOMETRIC_MULTI_SENSOR_DEFAULT,
-            BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT})
+            BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE})
     @Retention(RetentionPolicy.SOURCE)
     public @interface BiometricMultiSensorMode {}
 
diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl
index 2c3c8c3..42aad36 100644
--- a/core/java/android/hardware/biometrics/IBiometricService.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricService.aidl
@@ -63,7 +63,7 @@
 
     // Notify BiometricService when <Biometric>Service is ready to start the prepared client.
     // Client lifecycle is still managed in <Biometric>Service.
-    void onReadyForAuthentication(int cookie);
+    void onReadyForAuthentication(long requestId, int cookie);
 
     // Requests all BIOMETRIC_STRONG sensors to have their authenticatorId invalidated for the
     // specified user. This happens when enrollments have been added on devices with multiple
diff --git a/core/java/android/hardware/biometrics/IBiometricSysuiReceiver.aidl b/core/java/android/hardware/biometrics/IBiometricSysuiReceiver.aidl
index 5d9b5f3..450c5ce 100644
--- a/core/java/android/hardware/biometrics/IBiometricSysuiReceiver.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricSysuiReceiver.aidl
@@ -30,6 +30,4 @@
     void onSystemEvent(int event);
     // Notifies that the dialog has finished animating.
     void onDialogAnimatedIn();
-    // For multi-sensor devices, notifies that the fingerprint should start now.
-    void onStartFingerprintNow();
 }
diff --git a/core/java/android/os/AppZygote.java b/core/java/android/os/AppZygote.java
index c8b4226e..07fbe4a 100644
--- a/core/java/android/os/AppZygote.java
+++ b/core/java/android/os/AppZygote.java
@@ -17,9 +17,11 @@
 package android.os;
 
 import android.content.pm.ApplicationInfo;
+import android.content.pm.ProcessInfo;
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.Zygote;
 
 import dalvik.system.VMRuntime;
 
@@ -45,8 +47,6 @@
     // Last UID/GID of the range the AppZygote can setuid()/setgid() to
     private final int mZygoteUidGidMax;
 
-    private final int mZygoteRuntimeFlags;
-
     private final Object mLock = new Object();
 
     /**
@@ -57,14 +57,15 @@
     private ChildZygoteProcess mZygote;
 
     private final ApplicationInfo mAppInfo;
+    private final ProcessInfo mProcessInfo;
 
-    public AppZygote(ApplicationInfo appInfo, int zygoteUid, int uidGidMin, int uidGidMax,
-            int runtimeFlags) {
+    public AppZygote(ApplicationInfo appInfo, ProcessInfo processInfo, int zygoteUid, int uidGidMin,
+            int uidGidMax) {
         mAppInfo = appInfo;
+        mProcessInfo = processInfo;
         mZygoteUid = zygoteUid;
         mZygoteUidGidMin = uidGidMin;
         mZygoteUidGidMax = uidGidMax;
-        mZygoteRuntimeFlags = runtimeFlags;
     }
 
     /**
@@ -108,13 +109,15 @@
         String abi = mAppInfo.primaryCpuAbi != null ? mAppInfo.primaryCpuAbi :
                 Build.SUPPORTED_ABIS[0];
         try {
+            int runtimeFlags = Zygote.getMemorySafetyRuntimeFlagsForSecondaryZygote(
+                    mAppInfo, mProcessInfo);
             mZygote = Process.ZYGOTE_PROCESS.startChildZygote(
                     "com.android.internal.os.AppZygoteInit",
                     mAppInfo.processName + "_zygote",
                     mZygoteUid,
                     mZygoteUid,
                     null,  // gids
-                    mZygoteRuntimeFlags,  // runtimeFlags
+                    runtimeFlags,
                     "app_zygote",  // seInfo
                     abi,  // abi
                     abi, // acceptedAbiList
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 1d1f17d..1c85f69 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -285,13 +285,20 @@
         public static final String RELEASE = getString("ro.build.version.release");
 
         /**
-         * The version string we show to the user; may be {@link #RELEASE} or
-         * {@link #CODENAME} if not a final release build.
+         * The version string.  May be {@link #RELEASE} or {@link #CODENAME} if
+         * not a final release build.
          */
         @NonNull public static final String RELEASE_OR_CODENAME = getString(
                 "ro.build.version.release_or_codename");
 
         /**
+         * The version string we show to the user; may be {@link #RELEASE} or
+         * a descriptive string if not a final release build.
+         */
+        @NonNull public static final String RELEASE_OR_PREVIEW_DISPLAY = getString(
+                "ro.build.version.release_or_preview_display");
+
+        /**
          * The base OS build the product is based on.
          */
         public static final String BASE_OS = SystemProperties.get("ro.build.version.base_os", "");
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 3d12941..79fa4fb 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -29,7 +29,6 @@
 import android.compat.annotation.Disabled;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
-import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.os.storage.StorageManager;
@@ -1333,7 +1332,7 @@
         final Context context = AppGlobals.getInitialApplication();
         final int uid = context.getApplicationInfo().uid;
         // Isolated processes and Instant apps are never allowed to be in scoped storage
-        if (Process.isIsolated(uid) || Process.isSupplemental(uid)) {
+        if (Process.isIsolated(uid) || Process.isSdkSandboxUid(uid)) {
             return false;
         }
 
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index 5d9f2189..8e5ed8f 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -52,6 +52,9 @@
 per-file *Telephony* = file:/telephony/OWNERS
 per-file *Zygote* = file:/ZYGOTE_OWNERS
 
+# Time
+per-file *Clock* = file:/services/core/java/com/android/server/timezonedetector/OWNERS
+
 # RecoverySystem
 per-file *Recovery* = file:/services/core/java/com/android/server/recoverysystem/OWNERS
 
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 17b5ec5..f069158c 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -281,23 +281,23 @@
 
     /**
      * Defines the start of a range of UIDs going from this number to
-     * {@link #LAST_SUPPLEMENTAL_UID} that are reserved for assigning to
-     * supplemental processes. There is a 1-1 mapping between a supplemental
+     * {@link #LAST_SDK_SANDBOX_UID} that are reserved for assigning to
+     * sdk sandbox processes. There is a 1-1 mapping between a sdk sandbox
      * process UID and the app that it belongs to, which can be computed by
-     * subtracting (FIRST_SUPPLEMENTAL_UID - FIRST_APPLICATION_UID) from the
-     * uid of a supplemental process.
+     * subtracting (FIRST_SDK_SANDBOX_UID - FIRST_APPLICATION_UID) from the
+     * uid of a sdk sandbox process.
      *
      * Note that there are no GIDs associated with these processes; storage
      * attribution for them will be done using project IDs.
      * @hide
      */
-    public static final int FIRST_SUPPLEMENTAL_UID = 20000;
+    public static final int FIRST_SDK_SANDBOX_UID = 20000;
 
     /**
-     * Last UID that is used for supplemental processes.
+     * Last UID that is used for sdk sandbox processes.
      * @hide
      */
-    public static final int LAST_SUPPLEMENTAL_UID = 29999;
+    public static final int LAST_SDK_SANDBOX_UID = 29999;
 
     /**
      * First uid used for fully isolated sandboxed processes spawned from an app zygote
@@ -901,44 +901,44 @@
     }
 
     /**
-     * Returns whether the provided UID belongs to a supplemental process.
+     * Returns whether the provided UID belongs to a SDK sandbox process.
      *
      * @hide
      */
     @SystemApi(client = MODULE_LIBRARIES)
-    public static final boolean isSupplemental(int uid) {
+    public static final boolean isSdkSandboxUid(int uid) {
         uid = UserHandle.getAppId(uid);
-        return (uid >= FIRST_SUPPLEMENTAL_UID && uid <= LAST_SUPPLEMENTAL_UID);
+        return (uid >= FIRST_SDK_SANDBOX_UID && uid <= LAST_SDK_SANDBOX_UID);
     }
 
     /**
      *
-     * Returns the app process corresponding to a supplemental process.
+     * Returns the app process corresponding to a sdk sandbox process.
      *
      * @hide
      */
     @SystemApi(client = MODULE_LIBRARIES)
     public static final int toAppUid(int uid) {
-        return uid - (FIRST_SUPPLEMENTAL_UID - FIRST_APPLICATION_UID);
+        return uid - (FIRST_SDK_SANDBOX_UID - FIRST_APPLICATION_UID);
     }
 
     /**
      *
-     * Returns the supplemental process corresponding to an app process.
+     * Returns the sdk sandbox process corresponding to an app process.
      *
      * @hide
      */
     @SystemApi(client = MODULE_LIBRARIES)
     @TestApi
-    public static final int toSupplementalUid(int uid) {
-        return uid + (FIRST_SUPPLEMENTAL_UID - FIRST_APPLICATION_UID);
+    public static final int toSdkSandboxUid(int uid) {
+        return uid + (FIRST_SDK_SANDBOX_UID - FIRST_APPLICATION_UID);
     }
 
     /**
-     * Returns whether the current process is a supplemental process.
+     * Returns whether the current process is a sdk sandbox process.
      */
-    public static final boolean isSupplemental() {
-        return isSupplemental(myUid());
+    public static final boolean isSdkSandbox() {
+        return isSdkSandboxUid(myUid());
     }
 
     /**
diff --git a/core/java/android/os/vibrator/PrimitiveSegment.java b/core/java/android/os/vibrator/PrimitiveSegment.java
index 58ca978..2d287e9 100644
--- a/core/java/android/os/vibrator/PrimitiveSegment.java
+++ b/core/java/android/os/vibrator/PrimitiveSegment.java
@@ -108,7 +108,7 @@
         Preconditions.checkArgumentInRange(mPrimitiveId, VibrationEffect.Composition.PRIMITIVE_NOOP,
                 VibrationEffect.Composition.PRIMITIVE_LOW_TICK, "primitiveId");
         Preconditions.checkArgumentInRange(mScale, 0f, 1f, "scale");
-        Preconditions.checkArgumentNonnegative(mDelay, "primitive delay should be >= 0");
+        VibrationEffectSegment.checkDurationArgument(mDelay, "delay");
     }
 
     @Override
diff --git a/core/java/android/os/vibrator/RampSegment.java b/core/java/android/os/vibrator/RampSegment.java
index 9e1f636..d7d8c49 100644
--- a/core/java/android/os/vibrator/RampSegment.java
+++ b/core/java/android/os/vibrator/RampSegment.java
@@ -108,14 +108,9 @@
     /** @hide */
     @Override
     public void validate() {
-        Preconditions.checkArgumentNonNegative(mStartFrequencyHz,
-                "Frequencies must all be >= 0, got start frequency of " + mStartFrequencyHz);
-        Preconditions.checkArgumentFinite(mStartFrequencyHz, "startFrequencyHz");
-        Preconditions.checkArgumentNonNegative(mEndFrequencyHz,
-                "Frequencies must all be >= 0, got end frequency of " + mEndFrequencyHz);
-        Preconditions.checkArgumentFinite(mEndFrequencyHz, "endFrequencyHz");
-        Preconditions.checkArgumentNonnegative(mDuration,
-                "Durations must all be >= 0, got " + mDuration);
+        VibrationEffectSegment.checkFrequencyArgument(mStartFrequencyHz, "startFrequencyHz");
+        VibrationEffectSegment.checkFrequencyArgument(mEndFrequencyHz, "endFrequencyHz");
+        VibrationEffectSegment.checkDurationArgument(mDuration, "duration");
         Preconditions.checkArgumentInRange(mStartAmplitude, 0f, 1f, "startAmplitude");
         Preconditions.checkArgumentInRange(mEndAmplitude, 0f, 1f, "endAmplitude");
     }
diff --git a/core/java/android/os/vibrator/StepSegment.java b/core/java/android/os/vibrator/StepSegment.java
index c679511..5a0bbf7 100644
--- a/core/java/android/os/vibrator/StepSegment.java
+++ b/core/java/android/os/vibrator/StepSegment.java
@@ -95,11 +95,8 @@
     /** @hide */
     @Override
     public void validate() {
-        Preconditions.checkArgumentNonNegative(mFrequencyHz,
-                "Frequencies must all be >= 0, got " + mFrequencyHz);
-        Preconditions.checkArgumentFinite(mFrequencyHz, "frequencyHz");
-        Preconditions.checkArgumentNonnegative(mDuration,
-                "Durations must all be >= 0, got " + mDuration);
+        VibrationEffectSegment.checkFrequencyArgument(mFrequencyHz, "frequencyHz");
+        VibrationEffectSegment.checkDurationArgument(mDuration, "duration");
         if (Float.compare(mAmplitude, VibrationEffect.DEFAULT_AMPLITUDE) != 0) {
             Preconditions.checkArgumentInRange(mAmplitude, 0f, 1f, "amplitude");
         }
diff --git a/core/java/android/os/vibrator/VibrationEffectSegment.java b/core/java/android/os/vibrator/VibrationEffectSegment.java
index 979c447..be10553 100644
--- a/core/java/android/os/vibrator/VibrationEffectSegment.java
+++ b/core/java/android/os/vibrator/VibrationEffectSegment.java
@@ -112,6 +112,43 @@
     @NonNull
     public abstract <T extends VibrationEffectSegment> T applyEffectStrength(int effectStrength);
 
+    /**
+     * Checks the given frequency argument is valid to represent a vibration effect frequency in
+     * hertz, i.e. a finite non-negative value.
+     *
+     * @param value the frequency argument value to be checked
+     * @param name the argument name for the error message.
+     *
+     * @hide
+     */
+    public static void checkFrequencyArgument(float value, @NonNull String name) {
+        // Similar to combining Preconditions checkArgumentFinite + checkArgumentNonnegative,
+        // but this implementation doesn't create the error message unless a check fail.
+        if (Float.isNaN(value)) {
+            throw new IllegalArgumentException(name + " must not be NaN");
+        }
+        if (Float.isInfinite(value)) {
+            throw new IllegalArgumentException(name + " must not be infinite");
+        }
+        if (value < 0) {
+            throw new IllegalArgumentException(name + " must be >= 0, got " + value);
+        }
+    }
+
+    /**
+     * Checks the given duration argument is valid, i.e. a non-negative value.
+     *
+     * @param value the duration value to be checked
+     * @param name the argument name for the error message.
+     *
+     * @hide
+     */
+    public static void checkDurationArgument(long value, @NonNull String name) {
+        if (value < 0) {
+            throw new IllegalArgumentException(name + " must be >= 0, got " + value);
+        }
+    }
+
     @NonNull
     public static final Creator<VibrationEffectSegment> CREATOR =
             new Creator<VibrationEffectSegment>() {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 8feff16..f2137b3 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -240,6 +240,20 @@
             "android.settings.TETHER_PROVISIONING_UI";
 
     /**
+     * Activity Action: Show a dialog activity to notify tethering is NOT supported by carrier.
+     *
+     * When {@link android.telephony.CarrierConfigManager#KEY_CARRIER_SUPPORTS_TETHERING_BOOL}
+     * is false, and tethering is started by Settings, this dialog activity will be started to
+     * tell the user that tethering is not supported by carrier.
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    @SystemApi
+    public static final String ACTION_TETHER_UNSUPPORTED_CARRIER_UI =
+            "android.settings.TETHER_UNSUPPORTED_CARRIER_UI";
+
+    /**
      * Activity Action: Show settings to allow entering/exiting airplane mode.
      * <p>
      * In some cases, a matching Activity may not exist, so ensure you
@@ -10274,6 +10288,14 @@
                 "theme_customization_overlay_packages";
 
         /**
+         * Indicates whether the nav bar is forced to always be visible, even in immersive mode.
+         * <p>Type: int (0 for false, 1 for true)
+         *
+         * @hide
+         */
+        public static final String NAV_BAR_FORCE_VISIBLE = "nav_bar_force_visible";
+
+        /**
          * Indicates whether the device is in kids nav mode.
          * <p>Type: int (0 for false, 1 for true)
          *
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 188bc3f..4541f3a 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -95,7 +95,7 @@
         DEFAULT_FLAGS.put(SETTINGS_USE_NEW_BACKUP_ELIGIBILITY_RULES, "true");
         DEFAULT_FLAGS.put(SETTINGS_ENABLE_SECURITY_HUB, "true");
         DEFAULT_FLAGS.put(SETTINGS_SUPPORT_LARGE_SCREEN, "true");
-        DEFAULT_FLAGS.put("settings_search_always_expand", "false");
+        DEFAULT_FLAGS.put("settings_search_always_expand", "true");
         DEFAULT_FLAGS.put(SETTINGS_APP_LANGUAGE_SELECTION, "true");
         DEFAULT_FLAGS.put(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS, "true");
         DEFAULT_FLAGS.put(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME, "false");
diff --git a/core/java/android/util/NtpTrustedTime.java b/core/java/android/util/NtpTrustedTime.java
index 8604078..40beab3 100644
--- a/core/java/android/util/NtpTrustedTime.java
+++ b/core/java/android/util/NtpTrustedTime.java
@@ -265,6 +265,13 @@
         return mTimeResult;
     }
 
+    /** Clears the last received NTP. Intended for use during tests. */
+    public void clearCachedTimeResult() {
+        synchronized (this) {
+            mTimeResult = null;
+        }
+    }
+
     private static class NtpConnectionInfo {
 
         @NonNull private final String mServer;
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index a266a28..5c7c844 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -290,7 +290,8 @@
     /**
      * Called when the keep-clear areas for this window have changed.
      */
-    oneway void reportKeepClearAreasChanged(IWindow window, in List<Rect> keepClearRects);
+    oneway void reportKeepClearAreasChanged(IWindow window, in List<Rect> restricted,
+           in List<Rect> unrestricted);
 
     /**
     * Request the server to call setInputWindowInfo on a given Surface, and return
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index b1582cf..6aab635 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -98,6 +98,13 @@
      */
     private boolean mIsAnimationPending;
 
+    /**
+     * @param type The {@link InternalInsetsType} of the consumed insets.
+     * @param state The current {@link InsetsState} of the consumed insets.
+     * @param transactionSupplier The source of new {@link Transaction} instances. The supplier
+     *         must provide *new* instances, which will be explicitly closed by this class.
+     * @param controller The {@link InsetsController} to use for insets interaction.
+     */
     public InsetsSourceConsumer(@InternalInsetsType int type, InsetsState state,
             Supplier<Transaction> transactionSupplier, InsetsController controller) {
         mType = type;
@@ -390,16 +397,17 @@
             return;
         }
 
-        final Transaction t = mTransactionSupplier.get();
-        if (DEBUG) Log.d(TAG, "applyRequestedVisibilityToControl: " + mRequestedVisible);
-        if (mRequestedVisible) {
-            t.show(mSourceControl.getLeash());
-        } else {
-            t.hide(mSourceControl.getLeash());
+        try (Transaction t = mTransactionSupplier.get()) {
+            if (DEBUG) Log.d(TAG, "applyRequestedVisibilityToControl: " + mRequestedVisible);
+            if (mRequestedVisible) {
+                t.show(mSourceControl.getLeash());
+            } else {
+                t.hide(mSourceControl.getLeash());
+            }
+            // Ensure the alpha value is aligned with the actual requested visibility.
+            t.setAlpha(mSourceControl.getLeash(), mRequestedVisible ? 1 : 0);
+            t.apply();
         }
-        // Ensure the alpha value is aligned with the actual requested visibility.
-        t.setAlpha(mSourceControl.getLeash(), mRequestedVisible ? 1 : 0);
-        t.apply();
         onPerceptible(mRequestedVisible);
     }
 
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 91d5b20..9aa243b 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -133,8 +133,9 @@
     private boolean mDisableBackgroundLayer = false;
 
     /**
-     * We use this lock to protect access to mSurfaceControl. Both are accessed on the UI
-     * thread and the render thread via RenderNode.PositionUpdateListener#positionLost.
+     * We use this lock to protect access to mSurfaceControl and
+     * SurfaceViewPositionUpdateListener#mPositionChangedTransaction. Both are accessed on the UI
+     * thread and the render thread.
      */
     final Object mSurfaceControlLock = new Object();
     final Rect mTmpRect = new Rect();
@@ -223,6 +224,12 @@
     private final SurfaceControl.Transaction mFrameCallbackTransaction =
             new SurfaceControl.Transaction();
 
+    /**
+     * A temporary transaction holder that should only be used when applying right away. There
+     * should be no assumption about thread safety for this transaction.
+     */
+    private final SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction();
+
     private int mParentSurfaceSequenceId;
 
     private RemoteAccessibilityController mRemoteAccessibilityController =
@@ -753,7 +760,7 @@
                 mBlastBufferQueue = null;
             }
 
-            final Transaction transaction = new Transaction();
+            Transaction transaction = new Transaction();
             if (mSurfaceControl != null) {
                 transaction.remove(mSurfaceControl);
                 mSurfaceControl = null;
@@ -783,18 +790,22 @@
     // synchronously otherwise we may see flickers.
     // When the listener is updated, we will get at least a single position update call so we can
     // guarantee any changes we post will be applied.
-    private void replacePositionUpdateListener(int surfaceWidth, int surfaceHeight) {
+    private void replacePositionUpdateListener(int surfaceWidth, int surfaceHeight,
+            Transaction geometryTransaction) {
         if (mPositionListener != null) {
             mRenderNode.removePositionUpdateListener(mPositionListener);
+            synchronized (mSurfaceControlLock) {
+                geometryTransaction = mPositionListener.getTransaction().merge(geometryTransaction);
+            }
         }
         mPositionListener = new SurfaceViewPositionUpdateListener(surfaceWidth, surfaceHeight,
-                mSurfaceControl);
+                geometryTransaction);
         mRenderNode.addPositionUpdateListener(mPositionListener);
     }
 
     private boolean performSurfaceTransaction(ViewRootImpl viewRoot, Translator translator,
             boolean creating, boolean sizeChanged, boolean hintChanged,
-            Transaction surfaceUpdateTransaction) {
+            Transaction geometryTransaction) {
         boolean realSizeChanged = false;
 
         mSurfaceLock.lock();
@@ -809,60 +820,59 @@
             // SurfaceChangedCallback to update the relative z. This is needed so that
             // we do not change the relative z before the server is ready to swap the
             // parent surface.
-            if (creating) {
-                updateRelativeZ(surfaceUpdateTransaction);
-                if (mSurfacePackage != null) {
-                    reparentSurfacePackage(surfaceUpdateTransaction, mSurfacePackage);
-                }
+            if (creating || (mParentSurfaceSequenceId == viewRoot.getSurfaceSequenceId())) {
+                updateRelativeZ(mTmpTransaction);
             }
             mParentSurfaceSequenceId = viewRoot.getSurfaceSequenceId();
 
             if (mViewVisibility) {
-                surfaceUpdateTransaction.show(mSurfaceControl);
+                geometryTransaction.show(mSurfaceControl);
             } else {
-                surfaceUpdateTransaction.hide(mSurfaceControl);
+                geometryTransaction.hide(mSurfaceControl);
             }
 
+            if (mSurfacePackage != null) {
+                reparentSurfacePackage(mTmpTransaction, mSurfacePackage);
+            }
 
-
-            updateBackgroundVisibility(surfaceUpdateTransaction);
-            updateBackgroundColor(surfaceUpdateTransaction);
+            updateBackgroundVisibility(mTmpTransaction);
+            updateBackgroundColor(mTmpTransaction);
             if (mUseAlpha) {
                 float alpha = getFixedAlpha();
-                surfaceUpdateTransaction.setAlpha(mSurfaceControl, alpha);
+                mTmpTransaction.setAlpha(mSurfaceControl, alpha);
                 mSurfaceAlpha = alpha;
             }
 
-            surfaceUpdateTransaction.setCornerRadius(mSurfaceControl, mCornerRadius);
+            geometryTransaction.setCornerRadius(mSurfaceControl, mCornerRadius);
             if ((sizeChanged || hintChanged) && !creating) {
-                setBufferSize(surfaceUpdateTransaction);
+                setBufferSize(geometryTransaction);
             }
             if (sizeChanged || creating || !isHardwareAccelerated()) {
+                onSetSurfacePositionAndScaleRT(geometryTransaction, mSurfaceControl,
+                        mScreenRect.left, /*positionLeft*/
+                        mScreenRect.top /*positionTop*/ ,
+                        mScreenRect.width() / (float) mSurfaceWidth /*postScaleX*/,
+                        mScreenRect.height() / (float) mSurfaceHeight /*postScaleY*/);
 
                 // Set a window crop when creating the surface or changing its size to
                 // crop the buffer to the surface size since the buffer producer may
                 // use SCALING_MODE_SCALE and submit a larger size than the surface
                 // size.
                 if (mClipSurfaceToBounds && mClipBounds != null) {
-                    surfaceUpdateTransaction.setWindowCrop(mSurfaceControl, mClipBounds);
+                    geometryTransaction.setWindowCrop(mSurfaceControl, mClipBounds);
                 } else {
-                    surfaceUpdateTransaction.setWindowCrop(mSurfaceControl, mSurfaceWidth,
+                    geometryTransaction.setWindowCrop(mSurfaceControl, mSurfaceWidth,
                             mSurfaceHeight);
                 }
 
-                surfaceUpdateTransaction.setDesintationFrame(mBlastSurfaceControl, mSurfaceWidth,
+                geometryTransaction.setDesintationFrame(mBlastSurfaceControl, mSurfaceWidth,
                             mSurfaceHeight);
 
                 if (isHardwareAccelerated()) {
                     // This will consume the passed in transaction and the transaction will be
                     // applied on a render worker thread.
-                    replacePositionUpdateListener(mSurfaceWidth, mSurfaceHeight);
-                } else {
-                    onSetSurfacePositionAndScale(surfaceUpdateTransaction, mSurfaceControl,
-                            mScreenRect.left /*positionLeft*/,
-                            mScreenRect.top /*positionTop*/,
-                            mScreenRect.width() / (float) mSurfaceWidth /*postScaleX*/,
-                            mScreenRect.height() / (float) mSurfaceHeight /*postScaleY*/);
+                    replacePositionUpdateListener(mSurfaceWidth, mSurfaceHeight,
+                            geometryTransaction);
                 }
                 if (DEBUG_POSITION) {
                     Log.d(TAG, String.format(
@@ -874,7 +884,8 @@
                             mScreenRect.bottom, mSurfaceWidth, mSurfaceHeight));
                 }
             }
-            applyTransactionOnVriDraw(surfaceUpdateTransaction);
+            mTmpTransaction.merge(geometryTransaction);
+            mTmpTransaction.apply();
             updateEmbeddedAccessibilityMatrix();
 
             mSurfaceFrame.left = 0;
@@ -982,17 +993,17 @@
                 mScreenRect.offset(surfaceInsets.left, surfaceInsets.top);
                 // Collect all geometry changes and apply these changes on the RenderThread worker
                 // via the RenderNode.PositionUpdateListener.
-                final Transaction surfaceUpdateTransaction = new Transaction();
+                final Transaction geometryTransaction = new Transaction();
                 if (creating) {
                     updateOpaqueFlag();
                     final String name = "SurfaceView[" + viewRoot.getTitle().toString() + "]";
-                    createBlastSurfaceControls(viewRoot, name, surfaceUpdateTransaction);
+                    createBlastSurfaceControls(viewRoot, name, geometryTransaction);
                 } else if (mSurfaceControl == null) {
                     return;
                 }
 
                 final boolean realSizeChanged = performSurfaceTransaction(viewRoot,
-                        translator, creating, sizeChanged, hintChanged, surfaceUpdateTransaction);
+                        translator, creating, sizeChanged, hintChanged, geometryTransaction);
                 final boolean redrawNeeded = sizeChanged || creating || hintChanged
                         || (mVisible && !mDrawFinished);
 
@@ -1128,7 +1139,7 @@
      *
      */
     private void createBlastSurfaceControls(ViewRootImpl viewRoot, String name,
-            Transaction surfaceUpdateTransaction) {
+            Transaction geometryTransaction) {
         if (mSurfaceControl == null) {
             mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession)
                     .setName(name)
@@ -1151,10 +1162,11 @@
                     .build();
         } else {
             // update blast layer
-            surfaceUpdateTransaction
+            mTmpTransaction
                     .setOpaque(mBlastSurfaceControl, (mSurfaceFlags & SurfaceControl.OPAQUE) != 0)
                     .setSecure(mBlastSurfaceControl, (mSurfaceFlags & SurfaceControl.SECURE) != 0)
-                    .show(mBlastSurfaceControl);
+                    .show(mBlastSurfaceControl)
+                    .apply();
         }
 
         if (mBackgroundControl == null) {
@@ -1201,7 +1213,7 @@
      *
      * @hide
      */
-    protected void onSetSurfacePositionAndScale(@NonNull Transaction transaction,
+    protected void onSetSurfacePositionAndScaleRT(@NonNull Transaction transaction,
             @NonNull SurfaceControl surface, int positionLeft, int positionTop,
             float postScaleX, float postScaleY) {
         transaction.setPosition(surface, positionLeft, positionTop);
@@ -1214,14 +1226,12 @@
         if (mSurfaceControl == null) {
             return;
         }
-        final Transaction transaction = new Transaction();
-        onSetSurfacePositionAndScale(transaction, mSurfaceControl,
+        onSetSurfacePositionAndScaleRT(mTmpTransaction, mSurfaceControl,
                 mScreenRect.left, /*positionLeft*/
                 mScreenRect.top/*positionTop*/ ,
                 mScreenRect.width() / (float) mSurfaceWidth /*postScaleX*/,
                 mScreenRect.height() / (float) mSurfaceHeight /*postScaleY*/);
-        applyTransactionOnVriDraw(transaction);
-        invalidate();
+        mTmpTransaction.apply();
     }
 
     /**
@@ -1243,57 +1253,66 @@
         }
     }
 
-    private final Rect mRTLastReportedPosition = new Rect();
-    private final Point mRTLastReportedSurfaceSize = new Point();
+    private Rect mRTLastReportedPosition = new Rect();
+    private Point mRTLastReportedSurfaceSize = new Point();
 
     private class SurfaceViewPositionUpdateListener implements RenderNode.PositionUpdateListener {
-        private final int mRtSurfaceWidth;
-        private final int mRtSurfaceHeight;
+        int mRtSurfaceWidth = -1;
+        int mRtSurfaceHeight = -1;
         private final SurfaceControl.Transaction mPositionChangedTransaction =
                 new SurfaceControl.Transaction();
-        private final SurfaceControl mRtSurfaceControl = new SurfaceControl();
+        boolean mPendingTransaction = false;
 
         SurfaceViewPositionUpdateListener(int surfaceWidth, int surfaceHeight,
-                SurfaceControl surfaceControl) {
+                @Nullable Transaction t) {
             mRtSurfaceWidth = surfaceWidth;
             mRtSurfaceHeight = surfaceHeight;
-            mRtSurfaceControl.copyFrom(surfaceControl, "SurfaceViewPositionUpdateListener");
+            if (t != null) {
+                mPositionChangedTransaction.merge(t);
+                mPendingTransaction = true;
+            }
         }
 
         @Override
         public void positionChanged(long frameNumber, int left, int top, int right, int bottom) {
-            if (mRTLastReportedPosition.left == left
-                    && mRTLastReportedPosition.top == top
-                    && mRTLastReportedPosition.right == right
-                    && mRTLastReportedPosition.bottom == bottom
-                    && mRTLastReportedSurfaceSize.x == mRtSurfaceWidth
-                    && mRTLastReportedSurfaceSize.y == mRtSurfaceHeight) {
-                return;
-            }
-            try {
-                if (DEBUG_POSITION) {
-                    Log.d(TAG, String.format(
-                            "%d updateSurfacePosition RenderWorker, frameNr = %d, "
-                                    + "position = [%d, %d, %d, %d] surfaceSize = %dx%d",
-                            System.identityHashCode(SurfaceView.this), frameNumber,
-                            left, top, right, bottom, mRtSurfaceWidth, mRtSurfaceHeight));
+            synchronized(mSurfaceControlLock) {
+                if (mSurfaceControl == null) {
+                    return;
                 }
-                mRTLastReportedPosition.set(left, top, right, bottom);
-                mRTLastReportedSurfaceSize.set(mRtSurfaceWidth, mRtSurfaceHeight);
-                onSetSurfacePositionAndScale(mPositionChangedTransaction, mRtSurfaceControl,
-                        mRTLastReportedPosition.left /*positionLeft*/,
-                        mRTLastReportedPosition.top /*positionTop*/,
-                        mRTLastReportedPosition.width()
-                                / (float) mRtSurfaceWidth /*postScaleX*/,
-                        mRTLastReportedPosition.height()
-                                / (float) mRtSurfaceHeight /*postScaleY*/);
-                if (mViewVisibility) {
-                    // b/131239825
-                    mPositionChangedTransaction.show(mRtSurfaceControl);
+                if (mRTLastReportedPosition.left == left
+                        && mRTLastReportedPosition.top == top
+                        && mRTLastReportedPosition.right == right
+                        && mRTLastReportedPosition.bottom == bottom
+                        && mRTLastReportedSurfaceSize.x == mRtSurfaceWidth
+                        && mRTLastReportedSurfaceSize.y == mRtSurfaceHeight
+                        && !mPendingTransaction) {
+                    return;
                 }
-                applyOrMergeTransaction(mPositionChangedTransaction, frameNumber);
-            } catch (Exception ex) {
-                Log.e(TAG, "Exception from repositionChild", ex);
+                try {
+                    if (DEBUG_POSITION) {
+                        Log.d(TAG, String.format(
+                                "%d updateSurfacePosition RenderWorker, frameNr = %d, "
+                                        + "position = [%d, %d, %d, %d] surfaceSize = %dx%d",
+                                System.identityHashCode(SurfaceView.this), frameNumber,
+                                left, top, right, bottom, mRtSurfaceWidth, mRtSurfaceHeight));
+                    }
+                    mRTLastReportedPosition.set(left, top, right, bottom);
+                    mRTLastReportedSurfaceSize.set(mRtSurfaceWidth, mRtSurfaceHeight);
+                    onSetSurfacePositionAndScaleRT(mPositionChangedTransaction, mSurfaceControl,
+                            mRTLastReportedPosition.left /*positionLeft*/,
+                            mRTLastReportedPosition.top /*positionTop*/,
+                            mRTLastReportedPosition.width()
+                                    / (float) mRtSurfaceWidth /*postScaleX*/,
+                            mRTLastReportedPosition.height()
+                                    / (float) mRtSurfaceHeight /*postScaleY*/);
+                    if (mViewVisibility) {
+                        mPositionChangedTransaction.show(mSurfaceControl);
+                    }
+                    applyOrMergeTransaction(mPositionChangedTransaction, frameNumber);
+                    mPendingTransaction = false;
+                } catch (Exception ex) {
+                    Log.e(TAG, "Exception from repositionChild", ex);
+                }
             }
         }
 
@@ -1302,7 +1321,7 @@
                 float vecX, float vecY, float maxStretchX, float maxStretchY,
                 float childRelativeLeft, float childRelativeTop, float childRelativeRight,
                 float childRelativeBottom) {
-            mRtTransaction.setStretchEffect(mRtSurfaceControl, width, height, vecX, vecY,
+            mRtTransaction.setStretchEffect(mSurfaceControl, width, height, vecX, vecY,
                     maxStretchX, maxStretchY, childRelativeLeft, childRelativeTop,
                     childRelativeRight, childRelativeBottom);
             applyOrMergeTransaction(mRtTransaction, frameNumber);
@@ -1317,14 +1336,28 @@
             mRTLastReportedPosition.setEmpty();
             mRTLastReportedSurfaceSize.set(-1, -1);
 
-            // positionLost can be called while UI thread is un-paused.
+            /**
+             * positionLost can be called while UI thread is un-paused so we
+             * need to hold the lock here.
+             */
             synchronized (mSurfaceControlLock) {
-                if (mSurfaceControl == null) return;
-                // b/131239825
+                if (mPendingTransaction) {
+                    Log.w(TAG, System.identityHashCode(SurfaceView.this)
+                            + "Pending transaction cleared.");
+                    mPositionChangedTransaction.clear();
+                    mPendingTransaction = false;
+                }
+                if (mSurfaceControl == null) {
+                    return;
+                }
                 mRtTransaction.hide(mSurfaceControl);
                 applyOrMergeTransaction(mRtTransaction, frameNumber);
             }
         }
+
+        public Transaction getTransaction() {
+            return mPositionChangedTransaction;
+        }
     }
 
     private SurfaceViewPositionUpdateListener mPositionListener = null;
@@ -1371,10 +1404,8 @@
      * @hide
      */
     public void setResizeBackgroundColor(int bgColor) {
-        final SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
-        setResizeBackgroundColor(transaction, bgColor);
-        applyTransactionOnVriDraw(transaction);
-        invalidate();
+        setResizeBackgroundColor(mTmpTransaction, bgColor);
+        mTmpTransaction.apply();
     }
 
     /**
diff --git a/core/java/android/view/SyncRtSurfaceTransactionApplier.java b/core/java/android/view/SyncRtSurfaceTransactionApplier.java
index 3e21103..e9c937cc 100644
--- a/core/java/android/view/SyncRtSurfaceTransactionApplier.java
+++ b/core/java/android/view/SyncRtSurfaceTransactionApplier.java
@@ -65,10 +65,12 @@
         applyParams(t, params);
 
         mTargetViewRootImpl.registerRtFrameCallback(frame -> {
-            if (mTargetSc == null || !mTargetSc.isValid()) {
-                return;
+            if (mTargetSc != null && mTargetSc.isValid()) {
+                applyTransaction(t, frame);
             }
-            applyTransaction(t, frame);
+            // The transaction was either dropped, successfully applied, or merged with a future
+            // transaction, so we can safely release its resources.
+            t.close();
         });
 
         // Make sure a frame gets scheduled.
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 553c537..8b3a29a 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -46,9 +46,11 @@
 import android.annotation.LayoutRes;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.Size;
 import android.annotation.StyleRes;
 import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.annotation.UiContext;
 import android.annotation.UiThread;
@@ -4744,6 +4746,7 @@
          */
         private List<Rect> mSystemGestureExclusionRects = null;
         private List<Rect> mKeepClearRects = null;
+        private List<Rect> mUnrestrictedKeepClearRects = null;
         private boolean mPreferKeepClear = false;
         private Rect mHandwritingArea = null;
 
@@ -11729,6 +11732,7 @@
         final ListenerInfo info = getListenerInfo();
         if (getSystemGestureExclusionRects().isEmpty()
                 && collectPreferKeepClearRects().isEmpty()
+                && collectUnrestrictedPreferKeepClearRects().isEmpty()
                 && (info.mHandwritingArea == null || !isAutoHandwritingEnabled())) {
             if (info.mPositionUpdateListener != null) {
                 mRenderNode.removePositionUpdateListener(info.mPositionUpdateListener);
@@ -11871,6 +11875,52 @@
         return Collections.emptyList();
     }
 
+    /**
+     * Set a preference to keep the provided rects clear from floating windows above this
+     * view's window. This informs the system that these rects are considered vital areas for the
+     * user and that ideally they should not be covered. Setting this is only appropriate for UI
+     * where the user would likely take action to uncover it.
+     * <p>
+     * Note: The difference with {@link #setPreferKeepClearRects} is that the system won't apply
+     * restrictions to the rects set here.
+     * <p>
+     * @see #setPreferKeepClear
+     * @see #getPreferKeepClearRects
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS)
+    public final void setUnrestrictedPreferKeepClearRects(@NonNull List<Rect> rects) {
+        final ListenerInfo info = getListenerInfo();
+        if (info.mUnrestrictedKeepClearRects != null) {
+            info.mUnrestrictedKeepClearRects.clear();
+            info.mUnrestrictedKeepClearRects.addAll(rects);
+        } else {
+            info.mUnrestrictedKeepClearRects = new ArrayList<>(rects);
+        }
+        updatePositionUpdateListener();
+        postUpdate(this::updateKeepClearRects);
+    }
+
+    /**
+     * @return the list of rects, set by {@link #setPreferKeepClearRects}.
+     *
+     * @see #setPreferKeepClearRects
+     *
+     * @hide
+     */
+    @SystemApi
+    @NonNull
+    public final List<Rect> getUnrestrictedPreferKeepClearRects() {
+        final ListenerInfo info = mListenerInfo;
+        if (info != null && info.mKeepClearRects != null) {
+            return new ArrayList(info.mUnrestrictedKeepClearRects);
+        }
+
+        return Collections.emptyList();
+    }
+
     void updateKeepClearRects() {
         final AttachInfo ai = mAttachInfo;
         if (ai != null) {
@@ -11899,6 +11949,20 @@
     }
 
     /**
+     * Retrieve the list of unrestricted areas within this view's post-layout coordinate space
+     * which the system will try to not cover with other floating elements, like the pip window.
+     */
+    @NonNull
+    List<Rect> collectUnrestrictedPreferKeepClearRects() {
+        final ListenerInfo info = mListenerInfo;
+        if (info != null && info.mUnrestrictedKeepClearRects != null) {
+            return info.mUnrestrictedKeepClearRects;
+        }
+
+        return Collections.emptyList();
+    }
+
+    /**
      * Set a list of handwriting areas in this view. If there is any stylus {@link MotionEvent}
      * occurs within those areas, it will trigger stylus handwriting mode. This can be disabled by
      * disabling the auto handwriting initiation by calling
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 8bb7e67..d3d3625 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -76,6 +76,7 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FIT_INSETS_CONTROLLED;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
@@ -220,6 +221,7 @@
 import java.lang.ref.WeakReference;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
@@ -777,6 +779,8 @@
             new ViewRootRectTracker(v -> v.getSystemGestureExclusionRects());
     private final ViewRootRectTracker mKeepClearRectsTracker =
             new ViewRootRectTracker(v -> v.collectPreferKeepClearRects());
+    private final ViewRootRectTracker mUnrestrictedKeepClearRectsTracker =
+            new ViewRootRectTracker(v -> v.collectUnrestrictedPreferKeepClearRects());
 
     private IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection;
 
@@ -2462,8 +2466,9 @@
             if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": baseSize=" + baseSize
                     + ", desiredWindowWidth=" + desiredWindowWidth);
             if (baseSize != 0 && desiredWindowWidth > baseSize) {
-                childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
-                childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
+                childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width, lp.privateFlags);
+                childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height,
+                        lp.privateFlags);
                 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                 if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured ("
                         + host.getMeasuredWidth() + "," + host.getMeasuredHeight()
@@ -2476,7 +2481,7 @@
                     baseSize = (baseSize+desiredWindowWidth)/2;
                     if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": next baseSize="
                             + baseSize);
-                    childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
+                    childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width, lp.privateFlags);
                     performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                     if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured ("
                             + host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")");
@@ -2489,8 +2494,10 @@
         }
 
         if (!goodMeasure) {
-            childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
-            childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
+            childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width,
+                    lp.privateFlags);
+            childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height,
+                    lp.privateFlags);
             performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
             if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
                 windowSizeMayChange = true;
@@ -3150,8 +3157,10 @@
                 if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                         || mHeight != host.getMeasuredHeight() || dispatchApplyInsets ||
                         updatedConfiguration) {
-                    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
-                    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
+                    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width,
+                            lp.privateFlags);
+                    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height,
+                            lp.privateFlags);
 
                     if (DEBUG_LAYOUT) Log.v(mTag, "Ooops, something changed!  mWidth="
                             + mWidth + " measuredWidth=" + host.getMeasuredWidth()
@@ -3951,31 +3960,28 @@
      * Figures out the measure spec for the root view in a window based on it's
      * layout params.
      *
-     * @param windowSize
-     *            The available width or height of the window
-     *
-     * @param rootDimension
-     *            The layout params for one dimension (width or height) of the
-     *            window.
-     *
+     * @param windowSize The available width or height of the window.
+     * @param measurement The layout width or height requested in the layout params.
+     * @param privateFlags The private flags in the layout params of the window.
      * @return The measure spec to use to measure the root view.
      */
-    private static int getRootMeasureSpec(int windowSize, int rootDimension) {
+    private static int getRootMeasureSpec(int windowSize, int measurement, int privateFlags) {
         int measureSpec;
+        final int rootDimension = (privateFlags & PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT) != 0
+                ? MATCH_PARENT : measurement;
         switch (rootDimension) {
-
-        case ViewGroup.LayoutParams.MATCH_PARENT:
-            // Window can't resize. Force root view to be windowSize.
-            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
-            break;
-        case ViewGroup.LayoutParams.WRAP_CONTENT:
-            // Window can resize. Set max size for root view.
-            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
-            break;
-        default:
-            // Window wants to be an exact size. Force root view to be that size.
-            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
-            break;
+            case ViewGroup.LayoutParams.MATCH_PARENT:
+                // Window can't resize. Force root view to be windowSize.
+                measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
+                break;
+            case ViewGroup.LayoutParams.WRAP_CONTENT:
+                // Window can resize. Set max size for root view.
+                measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
+                break;
+            default:
+                // Window wants to be an exact size. Force root view to be that size.
+                measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
+                break;
         }
         return measureSpec;
     }
@@ -4175,7 +4181,7 @@
                         + " didProduceBuffer=" + didProduceBuffer);
             }
 
-            Transaction tmpTransaction = new Transaction();
+            final Transaction tmpTransaction = new Transaction();
             tmpTransaction.merge(mRtBLASTSyncTransaction);
 
             // If frame wasn't drawn, clear out the next transaction so it doesn't affect the next
@@ -4206,6 +4212,7 @@
                         blastSyncConsumer.accept(mSurfaceChangedTransaction);
                     }
                 }
+                tmpTransaction.close();
 
                 if (reportNextDraw) {
                     pendingDrawFinished();
@@ -4875,14 +4882,26 @@
      */
     void updateKeepClearRectsForView(View view) {
         mKeepClearRectsTracker.updateRectsForView(view);
+        mUnrestrictedKeepClearRectsTracker.updateRectsForView(view);
         mHandler.sendEmptyMessage(MSG_KEEP_CLEAR_RECTS_CHANGED);
     }
 
     void keepClearRectsChanged() {
-        final List<Rect> rectsForWindowManager = mKeepClearRectsTracker.computeChangedRects();
-        if (rectsForWindowManager != null && mView != null) {
+        List<Rect> restrictedKeepClearRects = mKeepClearRectsTracker.computeChangedRects();
+        List<Rect> unrestrictedKeepClearRects =
+                mUnrestrictedKeepClearRectsTracker.computeChangedRects();
+        if ((restrictedKeepClearRects != null || unrestrictedKeepClearRects != null)
+                && mView != null) {
+            if (restrictedKeepClearRects == null) {
+                restrictedKeepClearRects = Collections.emptyList();
+            }
+            if (unrestrictedKeepClearRects == null) {
+                unrestrictedKeepClearRects = Collections.emptyList();
+            }
+
             try {
-                mWindowSession.reportKeepClearAreasChanged(mWindow, rectsForWindowManager);
+                mWindowSession.reportKeepClearAreasChanged(mWindow, restrictedKeepClearRects,
+                        unrestrictedKeepClearRects);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
diff --git a/core/java/android/view/WindowLayout.java b/core/java/android/view/WindowLayout.java
index 27f89ec..ad9f21b 100644
--- a/core/java/android/view/WindowLayout.java
+++ b/core/java/android/view/WindowLayout.java
@@ -16,6 +16,8 @@
 
 package android.view;
 
+import static android.view.Gravity.DISPLAY_CLIP_HORIZONTAL;
+import static android.view.Gravity.DISPLAY_CLIP_VERTICAL;
 import static android.view.InsetsState.ITYPE_IME;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
@@ -29,6 +31,7 @@
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
@@ -250,11 +253,39 @@
         Gravity.apply(attrs.gravity, w, h, outParentFrame,
                 (int) (x + attrs.horizontalMargin * pw),
                 (int) (y + attrs.verticalMargin * ph), outFrame);
+
         // Now make sure the window fits in the overall display frame.
         if (fitToDisplay) {
             Gravity.applyDisplay(attrs.gravity, outDisplayFrame, outFrame);
         }
 
+        if ((attrs.privateFlags & PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT) != 0
+                && !cutout.isEmpty()) {
+            // If the actual frame covering a display cutout, and the window is requesting to extend
+            // it's requested frame, re-do the frame calculation after getting the new requested
+            // size.
+            mTempRect.set(outFrame);
+            // Do nothing if the display cutout and window don't overlap entirely. This may happen
+            // when the cutout is not on the same side with the window.
+            boolean shouldExpand = false;
+            final Rect [] cutoutBounds = cutout.getBoundingRectsAll();
+            for (Rect cutoutBound : cutoutBounds) {
+                if (cutoutBound.isEmpty()) continue;
+                if (mTempRect.contains(cutoutBound) || cutoutBound.contains(mTempRect)) {
+                    shouldExpand = true;
+                    break;
+                }
+            }
+            if (shouldExpand) {
+                // Try to fit move the bar to avoid the display cutout first. Make sure the clip
+                // flags are not set to make the window move.
+                final int clipFlags = DISPLAY_CLIP_VERTICAL | DISPLAY_CLIP_HORIZONTAL;
+                Gravity.applyDisplay(attrs.gravity & ~clipFlags, displayCutoutSafe,
+                        mTempRect);
+                outFrame.union(mTempRect);
+            }
+        }
+
         if (DEBUG) Log.d(TAG, "computeWindowFrames " + attrs.getTitle()
                 + " outFrame=" + outFrame.toShortString()
                 + " outParentFrame=" + outParentFrame.toShortString()
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 8ee3e43..dbfae46 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -2385,6 +2385,16 @@
         public static final int PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR = 0x00001000;
 
         /**
+         * Flag to indicate that the window frame should be the requested frame adding the display
+         * cutout frame. This will only be applied if a specific size smaller than the parent frame
+         * is given, and the window is covering the display cutout. The extended frame will not be
+         * larger than the parent frame.
+         *
+         * {@hide}
+         */
+        public static final int PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT = 0x00002000;
+
+        /**
          * Flag that will make window ignore app visibility and instead depend purely on the decor
          * view visibility for determining window visibility. This is used by recents to keep
          * drawing after it launches an app.
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index c81b8cc..a270c92 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -31,6 +31,7 @@
 import android.window.IOnBackInvokedCallback;
 
 import java.util.HashMap;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -473,12 +474,12 @@
 
     @Override
     public void reportSystemGestureExclusionChanged(android.view.IWindow window,
-            java.util.List<android.graphics.Rect> exclusionRects) {
+            List<Rect> exclusionRects) {
     }
 
     @Override
-    public void reportKeepClearAreasChanged(android.view.IWindow window,
-            java.util.List<android.graphics.Rect> exclusionRects) {
+    public void reportKeepClearAreasChanged(android.view.IWindow window, List<Rect> restrictedRects,
+            List<Rect> unrestrictedRects) {
     }
 
     @Override
diff --git a/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl b/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl
index 67d9667..62d029b 100644
--- a/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl
+++ b/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl
@@ -32,7 +32,7 @@
     /**
      * Enables window magnification on specified display with given center and scale and animation.
      *
-     * @param displayId The logical display id.
+     * @param displayId the logical display id.
      * @param scale magnification scale.
      * @param centerX the screen-relative X coordinate around which to center,
      *                or {@link Float#NaN} to leave unchanged.
@@ -51,7 +51,7 @@
     /**
      * Sets the scale of the window magnifier on specified display.
      *
-     * @param displayId The logical display id.
+     * @param displayId the logical display id.
      * @param scale magnification scale.
      */
     void setScale(int displayId, float scale);
@@ -59,7 +59,7 @@
      /**
      * Disables window magnification on specified display with animation.
      *
-     * @param displayId The logical display id.
+     * @param displayId the logical display id.
      * @param callback The callback called when the animation is completed or interrupted.
      */
     void disableWindowMagnification(int displayId,
@@ -68,6 +68,7 @@
     /**
      * Moves the window magnifier on the specified display. It has no effect while animating.
      *
+     * @param displayId the logical display id.
      * @param offsetX the amount in pixels to offset the window magnifier in the X direction, in
      *                current screen pixels.
      * @param offsetY the amount in pixels to offset the window magnifier in the Y direction, in
@@ -76,9 +77,20 @@
     void moveWindowMagnifier(int displayId, float offsetX, float offsetY);
 
     /**
+     * Moves the window magnifier on the given display.
+     *
+     * @param displayId the logical display id.
+     * @param positionX the x-axis position of the center of the magnified source bounds.
+     * @param positionY the y-axis position of the center of the magnified source bounds.
+     * @param callback the callback called when the animation is completed or interrupted.
+     */
+    void moveWindowMagnifierToPosition(int displayId, float positionX, float positionY,
+        in IRemoteMagnificationAnimationCallback callback);
+
+    /**
      * Requests System UI show magnification mode button UI on the specified display.
      *
-     * @param displayId The logical display id.
+     * @param displayId the logical display id.
      * @param magnificationMode the current magnification mode.
      */
     void showMagnificationButton(int displayId, int magnificationMode);
@@ -86,7 +98,7 @@
     /**
      * Requests System UI remove magnification mode button UI on the specified display.
      *
-     * @param displayId The logical display id.
+     * @param displayId the logical display id.
      */
     void removeMagnificationButton(int displayId);
 
diff --git a/core/java/android/view/accessibility/IWindowMagnificationConnectionCallback.aidl b/core/java/android/view/accessibility/IWindowMagnificationConnectionCallback.aidl
index 722546e..adfeb6d 100644
--- a/core/java/android/view/accessibility/IWindowMagnificationConnectionCallback.aidl
+++ b/core/java/android/view/accessibility/IWindowMagnificationConnectionCallback.aidl
@@ -68,12 +68,10 @@
     void onAccessibilityActionPerformed(int displayId);
 
     /**
-     * Called when the user is performing dragging gesture. It is started after the offset
-     * between the down location and the move event location exceed
-     * {@link ViewConfiguration#getScaledTouchSlop()}.
+     * Called when the user is performing move action.
      *
      * @param displayId The logical display id.
      */
-    void onDrag(int displayId);
+    void onMove(int displayId);
 
 }
diff --git a/core/java/android/webkit/WebViewZygote.java b/core/java/android/webkit/WebViewZygote.java
index 2bfbe4b..bc7a5fd 100644
--- a/core/java/android/webkit/WebViewZygote.java
+++ b/core/java/android/webkit/WebViewZygote.java
@@ -25,6 +25,7 @@
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.Zygote;
 
 /** @hide */
 public class WebViewZygote {
@@ -127,13 +128,15 @@
 
         try {
             String abi = sPackage.applicationInfo.primaryCpuAbi;
+            int runtimeFlags = Zygote.getMemorySafetyRuntimeFlagsForSecondaryZygote(
+                    sPackage.applicationInfo, null);
             sZygote = Process.ZYGOTE_PROCESS.startChildZygote(
                     "com.android.internal.os.WebViewZygoteInit",
                     "webview_zygote",
                     Process.WEBVIEW_ZYGOTE_UID,
                     Process.WEBVIEW_ZYGOTE_UID,
                     null,  // gids
-                    0,  // runtimeFlags
+                    runtimeFlags,
                     "webview_zygote",  // seInfo
                     abi,  // abi
                     TextUtils.join(",", Build.SUPPORTED_ABIS),
diff --git a/core/java/android/widget/inline/InlineContentView.java b/core/java/android/widget/inline/InlineContentView.java
index e4f483a..9712311 100644
--- a/core/java/android/widget/inline/InlineContentView.java
+++ b/core/java/android/widget/inline/InlineContentView.java
@@ -230,9 +230,8 @@
             int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
         mSurfaceView = new SurfaceView(context, attrs, defStyleAttr, defStyleRes) {
-            // b/219807628
             @Override
-            protected void onSetSurfacePositionAndScale(
+            protected void onSetSurfacePositionAndScaleRT(
                     @NonNull SurfaceControl.Transaction transaction,
                     @NonNull SurfaceControl surface, int positionLeft, int positionTop,
                     float postScaleX, float postScaleY) {
@@ -249,7 +248,7 @@
                 postScaleX = InlineContentView.this.getScaleX();
                 postScaleY = InlineContentView.this.getScaleY();
 
-                super.onSetSurfacePositionAndScale(transaction, surface, positionLeft,
+                super.onSetSurfacePositionAndScaleRT(transaction, surface, positionLeft,
                         positionTop, postScaleX, postScaleY);
             }
         };
diff --git a/core/java/android/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java
index 34a3418..232248b 100644
--- a/core/java/android/window/SplashScreenView.java
+++ b/core/java/android/window/SplashScreenView.java
@@ -65,6 +65,7 @@
 import java.time.Duration;
 import java.time.Instant;
 import java.util.function.Consumer;
+import java.util.function.LongConsumer;
 
 /**
  * <p>The view which allows an activity to customize its splash screen exit animation.</p>
@@ -234,7 +235,7 @@
         /**
          * Set the animation duration if icon is animatable.
          */
-        public Builder setAnimationDurationMillis(int duration) {
+        public Builder setAnimationDurationMillis(long duration) {
             mIconAnimationDuration = Duration.ofMillis(duration);
             return this;
         }
@@ -521,8 +522,11 @@
         });
     }
 
-    private void animationStartCallback() {
+    private void animationStartCallback(long animDuration) {
         mIconAnimationStart = Instant.now();
+        if (animDuration > 0) {
+            mIconAnimationDuration = Duration.ofMillis(animDuration);
+        }
     }
 
     /**
@@ -693,9 +697,8 @@
          * Prepare the animation if this drawable also be animatable.
          * @param duration The animation duration.
          * @param startListener The callback listener used to receive the start of the animation.
-         * @return true if this drawable object can also be animated and it can be played now.
          */
-        boolean prepareAnimate(long duration, Runnable startListener);
+        void prepareAnimate(long duration, LongConsumer startListener);
 
         /**
          * Stop animation.
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 346aa33..35c9594 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -31,6 +31,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.ArrayMap;
+import android.view.InsetsState;
 import android.view.SurfaceControl;
 
 import java.util.ArrayList;
@@ -589,6 +590,56 @@
     }
 
     /**
+     * Adds a given {@code Rect} as a rect insets provider on the {@code receiverWindowContainer}.
+     * This will trigger a change of insets for all the children in the subtree of
+     * {@code receiverWindowContainer}.
+     *
+     * @param receiverWindowContainer the window container which the insets provider need to be
+     *                                added to
+     * @param insetsProviderFrame the frame that will be added as Insets provider
+     * @param insetsTypes types of insets the rect provides
+     * @hide
+     */
+    @NonNull
+    public WindowContainerTransaction addRectInsetsProvider(
+            @NonNull WindowContainerToken receiverWindowContainer,
+            @NonNull Rect insetsProviderFrame,
+            @InsetsState.InternalInsetsType int[] insetsTypes) {
+        final HierarchyOp hierarchyOp =
+                new HierarchyOp.Builder(
+                        HierarchyOp.HIERARCHY_OP_TYPE_ADD_RECT_INSETS_PROVIDER)
+                        .setContainer(receiverWindowContainer.asBinder())
+                        .setInsetsProviderFrame(insetsProviderFrame)
+                        .setInsetsTypes(insetsTypes)
+                        .build();
+        mHierarchyOps.add(hierarchyOp);
+        return this;
+    }
+
+    /**
+     * Removes the insets provider for the given types from the
+     * {@code receiverWindowContainer}. This will trigger a change of insets for all the children
+     * in the subtree of {@code receiverWindowContainer}.
+     *
+     * @param receiverWindowContainer the window container which the insets-override-provider has
+     *                                to be removed from
+     * @param insetsTypes types of insets that have to be removed
+     * @hide
+     */
+    @NonNull
+    public WindowContainerTransaction removeInsetsProvider(
+            @NonNull WindowContainerToken receiverWindowContainer,
+            @InsetsState.InternalInsetsType int[] insetsTypes) {
+        final HierarchyOp hierarchyOp =
+                new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_INSETS_PROVIDER)
+                        .setContainer(receiverWindowContainer.asBinder())
+                        .setInsetsTypes(insetsTypes)
+                        .build();
+        mHierarchyOps.add(hierarchyOp);
+        return this;
+    }
+
+    /**
      * When this {@link WindowContainerTransaction} failed to finish on the server side, it will
      * trigger callback with this {@param errorCallbackToken}.
      * @param errorCallbackToken    client provided token that will be passed back as parameter in
@@ -993,6 +1044,8 @@
         public static final int HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS = 13;
         public static final int HIERARCHY_OP_TYPE_START_SHORTCUT = 14;
         public static final int HIERARCHY_OP_TYPE_RESTORE_TRANSIENT_ORDER = 15;
+        public static final int HIERARCHY_OP_TYPE_ADD_RECT_INSETS_PROVIDER = 16;
+        public static final int HIERARCHY_OP_TYPE_REMOVE_INSETS_PROVIDER = 17;
 
         // The following key(s) are for use with mLaunchOptions:
         // When launching a task (eg. from recents), this is the taskId to be launched.
@@ -1012,6 +1065,10 @@
         @Nullable
         private IBinder mReparent;
 
+        private @InsetsState.InternalInsetsType int[] mInsetsTypes;
+
+        private Rect mInsetsProviderFrame;
+
         // Moves/reparents to top of parent when {@code true}, otherwise moves/reparents to bottom.
         private boolean mToTop;
 
@@ -1130,6 +1187,8 @@
             mType = copy.mType;
             mContainer = copy.mContainer;
             mReparent = copy.mReparent;
+            mInsetsTypes = copy.mInsetsTypes;
+            mInsetsProviderFrame = copy.mInsetsProviderFrame;
             mToTop = copy.mToTop;
             mReparentTopOnly = copy.mReparentTopOnly;
             mMoveAdjacentTogether = copy.mMoveAdjacentTogether;
@@ -1146,6 +1205,12 @@
             mType = in.readInt();
             mContainer = in.readStrongBinder();
             mReparent = in.readStrongBinder();
+            mInsetsTypes = in.createIntArray();
+            if (in.readInt() != 0) {
+                mInsetsProviderFrame = Rect.CREATOR.createFromParcel(in);
+            } else {
+                mInsetsProviderFrame = null;
+            }
             mToTop = in.readBoolean();
             mReparentTopOnly = in.readBoolean();
             mMoveAdjacentTogether = in.readBoolean();
@@ -1171,6 +1236,15 @@
             return mReparent;
         }
 
+        @Nullable
+        public @InsetsState.InternalInsetsType int[] getInsetsTypes() {
+            return mInsetsTypes;
+        }
+
+        public Rect getInsetsProviderFrame() {
+            return mInsetsProviderFrame;
+        }
+
         @NonNull
         public IBinder getContainer() {
             return mContainer;
@@ -1276,6 +1350,13 @@
                 case HIERARCHY_OP_TYPE_START_SHORTCUT:
                     return "{StartShortcut: options=" + mLaunchOptions + " info=" + mShortcutInfo
                             + "}";
+                case HIERARCHY_OP_TYPE_ADD_RECT_INSETS_PROVIDER:
+                    return "{addRectInsetsProvider: container=" + mContainer
+                            + " insetsProvidingFrame=" + mInsetsProviderFrame
+                            + " insetsType=" + Arrays.toString(mInsetsTypes) + "}";
+                case HIERARCHY_OP_TYPE_REMOVE_INSETS_PROVIDER:
+                    return "{removeLocalInsetsProvider: container=" + mContainer
+                            + " insetsType=" + Arrays.toString(mInsetsTypes) + "}";
                 default:
                     return "{mType=" + mType + " container=" + mContainer + " reparent=" + mReparent
                             + " mToTop=" + mToTop
@@ -1289,6 +1370,13 @@
             dest.writeInt(mType);
             dest.writeStrongBinder(mContainer);
             dest.writeStrongBinder(mReparent);
+            dest.writeIntArray(mInsetsTypes);
+            if (mInsetsProviderFrame != null) {
+                dest.writeInt(1);
+                mInsetsProviderFrame.writeToParcel(dest, 0);
+            } else {
+                dest.writeInt(0);
+            }
             dest.writeBoolean(mToTop);
             dest.writeBoolean(mReparentTopOnly);
             dest.writeBoolean(mMoveAdjacentTogether);
@@ -1328,6 +1416,10 @@
             @Nullable
             private IBinder mReparent;
 
+            private int[] mInsetsTypes;
+
+            private Rect mInsetsProviderFrame;
+
             private boolean mToTop;
 
             private boolean mReparentTopOnly;
@@ -1369,6 +1461,16 @@
                 return this;
             }
 
+            Builder setInsetsTypes(int[] insetsTypes) {
+                mInsetsTypes = insetsTypes;
+                return this;
+            }
+
+            Builder setInsetsProviderFrame(Rect insetsProviderFrame) {
+                mInsetsProviderFrame = insetsProviderFrame;
+                return this;
+            }
+
             Builder setToTop(boolean toTop) {
                 mToTop = toTop;
                 return this;
@@ -1430,6 +1532,8 @@
                 hierarchyOp.mActivityTypes = mActivityTypes != null
                         ? Arrays.copyOf(mActivityTypes, mActivityTypes.length)
                         : null;
+                hierarchyOp.mInsetsTypes = mInsetsTypes;
+                hierarchyOp.mInsetsProviderFrame = mInsetsProviderFrame;
                 hierarchyOp.mToTop = mToTop;
                 hierarchyOp.mReparentTopOnly = mReparentTopOnly;
                 hierarchyOp.mMoveAdjacentTogether = mMoveAdjacentTogether;
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 94e5ea9..909e277 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -54,7 +54,7 @@
     private static final String TAG = "WindowOnBackDispatcher";
     private static final String BACK_PREDICTABILITY_PROP = "persist.debug.back_predictability";
     private static final boolean IS_BACK_PREDICTABILITY_ENABLED = SystemProperties
-            .getInt(BACK_PREDICTABILITY_PROP, 1) > 0;
+            .getInt(BACK_PREDICTABILITY_PROP, 0) > 0;
 
     /** Convenience hashmap to quickly decide if a callback has been added. */
     private final HashMap<OnBackInvokedCallback, Integer> mAllCallbacks = new HashMap<>();
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 4ae6bf7..70506cc 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -771,11 +771,6 @@
             delegationIntent.setComponent(delegateActivity);
             delegationIntent.putExtra(Intent.EXTRA_INTENT, getIntent());
             delegationIntent.putExtra(ActivityTaskManager.EXTRA_PERMISSION_TOKEN, permissionToken);
-
-            // Query prediction availability; mIsAppPredictorComponentAvailable isn't initialized.
-            delegationIntent.putExtra(
-                    EXTRA_IS_APP_PREDICTION_SERVICE_AVAILABLE, isAppPredictionServiceAvailable());
-
             delegationIntent.addFlags(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
 
             // Don't close until the delegate finishes, or the token will be invalidated.
@@ -972,34 +967,8 @@
     /**
      * Returns true if app prediction service is defined and the component exists on device.
      */
-    @VisibleForTesting
-    public boolean isAppPredictionServiceAvailable() {
-        if (getPackageManager().getAppPredictionServicePackageName() == null) {
-            // Default AppPredictionService is not defined.
-            return false;
-        }
-
-        final String appPredictionServiceName =
-                getString(R.string.config_defaultAppPredictionService);
-        if (appPredictionServiceName == null) {
-            return false;
-        }
-        final ComponentName appPredictionComponentName =
-                ComponentName.unflattenFromString(appPredictionServiceName);
-        if (appPredictionComponentName == null) {
-            return false;
-        }
-
-        // Check if the app prediction component actually exists on the device. The component is
-        // only visible when this is running in a system activity; otherwise this check will fail.
-        Intent intent = new Intent();
-        intent.setComponent(appPredictionComponentName);
-        if (getPackageManager().resolveService(intent, PackageManager.MATCH_ALL) == null) {
-            Log.e(TAG, "App prediction service is defined, but does not exist: "
-                    + appPredictionServiceName);
-            return false;
-        }
-        return true;
+    private boolean isAppPredictionServiceAvailable() {
+        return getPackageManager().getAppPredictionServicePackageName() != null;
     }
 
     /**
diff --git a/core/java/com/android/internal/infra/ServiceConnector.java b/core/java/com/android/internal/infra/ServiceConnector.java
index 9ced609..9e07f97 100644
--- a/core/java/com/android/internal/infra/ServiceConnector.java
+++ b/core/java/com/android/internal/infra/ServiceConnector.java
@@ -147,6 +147,15 @@
     void unbind();
 
     /**
+     * Registers a {@link ServiceLifecycleCallbacks callbacks} to be invoked when the lifecycle
+     * of the managed service changes.
+     *
+     * @param callbacks The callbacks that will be run, or {@code null} to clear the existing
+     *                 callbacks.
+     */
+    void setServiceLifecycleCallbacks(@Nullable ServiceLifecycleCallbacks<I> callbacks);
+
+    /**
      * A request to be run when the service is
      * {@link ServiceConnection#onServiceConnected connected}.
      *
@@ -189,6 +198,32 @@
         }
     }
 
+    /**
+     * Collection of callbacks invoked when the lifecycle of the service changes.
+     *
+     * @param <II> the type of the {@link IInterface ipc interface} for the remote service
+     * @see ServiceConnector#setServiceLifecycleCallbacks(ServiceLifecycleCallbacks)
+     */
+    interface ServiceLifecycleCallbacks<II extends IInterface> {
+        /**
+         * Called when the service has just connected and before any queued jobs are run.
+         */
+        default void onConnected(@NonNull II service) {}
+
+        /**
+         * Called just before the service is disconnected and unbound.
+         */
+        default void onDisconnected(@NonNull II service) {}
+
+        /**
+         * Called when the service Binder has died.
+         *
+         * In cases where {@link #onBinderDied()} is invoked the service becomes unbound without
+         * a callback to {@link #onDisconnected(IInterface)}.
+         */
+        default void onBinderDied() {}
+    }
+
 
     /**
      * Implementation of {@link ServiceConnector}
@@ -230,6 +265,8 @@
         private final @NonNull Handler mHandler;
         protected final @NonNull Executor mExecutor;
 
+        @Nullable
+        private volatile ServiceLifecycleCallbacks<I> mServiceLifecycleCallbacks = null;
         private volatile I mService = null;
         private boolean mBinding = false;
         private boolean mUnbinding = false;
@@ -330,6 +367,19 @@
             }
         }
 
+        private void dispatchOnServiceConnectionStatusChanged(
+                @NonNull I service, boolean isConnected) {
+            ServiceLifecycleCallbacks<I> serviceLifecycleCallbacks = mServiceLifecycleCallbacks;
+            if (serviceLifecycleCallbacks != null) {
+                if (isConnected) {
+                    serviceLifecycleCallbacks.onConnected(service);
+                } else {
+                    serviceLifecycleCallbacks.onDisconnected(service);
+                }
+            }
+            onServiceConnectionStatusChanged(service, isConnected);
+        }
+
         /**
          * Called when the service just connected or is about to disconnect
          */
@@ -504,12 +554,17 @@
             mHandler.post(this::unbindJobThread);
         }
 
+        @Override
+        public void setServiceLifecycleCallbacks(@Nullable ServiceLifecycleCallbacks<I> callbacks) {
+            mServiceLifecycleCallbacks = callbacks;
+        }
+
         void unbindJobThread() {
             cancelTimeout();
             I service = mService;
             boolean wasBound = service != null;
             if (wasBound) {
-                onServiceConnectionStatusChanged(service, false);
+                dispatchOnServiceConnectionStatusChanged(service, false);
                 mContext.unbindService(mServiceConnection);
                 service.asBinder().unlinkToDeath(this, 0);
                 mService = null;
@@ -560,7 +615,7 @@
             } catch (RemoteException e) {
                 Log.e(LOG_TAG, "onServiceConnected " + name + ": ", e);
             }
-            onServiceConnectionStatusChanged(service, true);
+            dispatchOnServiceConnectionStatusChanged(service, true);
             processQueue();
         }
 
@@ -572,7 +627,7 @@
             mBinding = true;
             I service = mService;
             if (service != null) {
-                onServiceConnectionStatusChanged(service, false);
+                dispatchOnServiceConnectionStatusChanged(service, false);
                 mService = null;
             }
         }
@@ -592,6 +647,14 @@
             }
             mService = null;
             unbind();
+            dispatchOnBinderDied();
+        }
+
+        private void dispatchOnBinderDied() {
+            ServiceLifecycleCallbacks<I> serviceLifecycleCallbacks = mServiceLifecycleCallbacks;
+            if (serviceLifecycleCallbacks != null) {
+                serviceLifecycleCallbacks.onBinderDied();
+            }
         }
 
         @Override
@@ -764,5 +827,10 @@
 
         @Override
         public void unbind() {}
+
+        @Override
+        public void setServiceLifecycleCallbacks(@Nullable ServiceLifecycleCallbacks<T> callbacks) {
+            // Do nothing.
+        }
     }
 }
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 6d4b8c5..b1e7d15 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -20,13 +20,21 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
+import android.compat.annotation.EnabledAfter;
+import android.content.Context;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.ProcessInfo;
 import android.net.Credentials;
 import android.net.LocalServerSocket;
 import android.net.LocalSocket;
+import android.os.Build;
 import android.os.FactoryTest;
 import android.os.IVold;
 import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.provider.DeviceConfig;
@@ -34,6 +42,7 @@
 import android.system.Os;
 import android.util.Log;
 
+import com.android.internal.compat.IPlatformCompat;
 import com.android.internal.net.NetworkUtilsInternal;
 
 import dalvik.annotation.optimization.CriticalNative;
@@ -125,6 +134,7 @@
     public static final int MEMORY_TAG_LEVEL_MASK = (1 << 19) | (1 << 20);
 
     public static final int MEMORY_TAG_LEVEL_NONE = 0;
+
     /**
      * Enable pointer tagging in this process.
      * Tags are checked during memory deallocation, but not on access.
@@ -170,10 +180,8 @@
      */
     public static final int GWP_ASAN_LEVEL_ALWAYS = 1 << 22;
 
-    /**
-     * Enable automatic zero-initialization of native heap memory allocations.
-     */
-    public static final int NATIVE_HEAP_ZERO_INIT = 1 << 23;
+    /** Enable automatic zero-initialization of native heap memory allocations. */
+    public static final int NATIVE_HEAP_ZERO_INIT_ENABLED = 1 << 23;
 
     /**
      * Enable profiling from system services. This loads profiling related plugins in ART.
@@ -1170,4 +1178,251 @@
      * we failed to determine the level.
      */
     public static native int nativeCurrentTaggingLevel();
+
+    /**
+     * Native heap allocations will now have a non-zero tag in the most significant byte.
+     *
+     * @see <a href="https://source.android.com/devices/tech/debug/tagged-pointers">Tagged
+     *     Pointers</a>
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+    private static final long NATIVE_HEAP_POINTER_TAGGING = 135754954; // This is a bug id.
+
+    /**
+     * Native heap allocations in AppZygote process and its descendants will now have a non-zero tag
+     * in the most significant byte.
+     *
+     * @see <a href="https://source.android.com/devices/tech/debug/tagged-pointers">Tagged
+     *     Pointers</a>
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S)
+    private static final long NATIVE_HEAP_POINTER_TAGGING_SECONDARY_ZYGOTE = 207557677;
+
+    /**
+     * Enable asynchronous (ASYNC) memory tag checking in this process. This flag will only have an
+     * effect on hardware supporting the ARM Memory Tagging Extension (MTE).
+     */
+    @ChangeId @Disabled
+    private static final long NATIVE_MEMTAG_ASYNC = 135772972; // This is a bug id.
+
+    /**
+     * Enable synchronous (SYNC) memory tag checking in this process. This flag will only have an
+     * effect on hardware supporting the ARM Memory Tagging Extension (MTE). If both
+     * NATIVE_MEMTAG_ASYNC and this option is selected, this option takes preference and MTE is
+     * enabled in SYNC mode.
+     */
+    @ChangeId @Disabled
+    private static final long NATIVE_MEMTAG_SYNC = 177438394; // This is a bug id.
+
+    /** Enable automatic zero-initialization of native heap memory allocations. */
+    @ChangeId @Disabled
+    private static final long NATIVE_HEAP_ZERO_INIT = 178038272; // This is a bug id.
+
+    /**
+     * Enable sampled memory bug detection in the app.
+     *
+     * @see <a href="https://source.android.com/devices/tech/debug/gwp-asan">GWP-ASan</a>.
+     */
+    @ChangeId @Disabled private static final long GWP_ASAN = 135634846; // This is a bug id.
+
+    private static int memtagModeToZygoteMemtagLevel(int memtagMode) {
+        switch (memtagMode) {
+            case ApplicationInfo.MEMTAG_ASYNC:
+                return MEMORY_TAG_LEVEL_ASYNC;
+            case ApplicationInfo.MEMTAG_SYNC:
+                return MEMORY_TAG_LEVEL_SYNC;
+            default:
+                return MEMORY_TAG_LEVEL_NONE;
+        }
+    }
+
+    private static boolean isCompatChangeEnabled(
+            long change,
+            @NonNull ApplicationInfo info,
+            @Nullable IPlatformCompat platformCompat,
+            int enabledAfter) {
+        try {
+            if (platformCompat != null) return platformCompat.isChangeEnabled(change, info);
+        } catch (RemoteException ignore) {
+        }
+        return enabledAfter > 0 && info.targetSdkVersion > enabledAfter;
+    }
+
+    // Returns the requested memory tagging level.
+    private static int getRequestedMemtagLevel(
+            @NonNull ApplicationInfo info,
+            @Nullable ProcessInfo processInfo,
+            @Nullable IPlatformCompat platformCompat) {
+        // Look at the process attribute first.
+        if (processInfo != null && processInfo.memtagMode != ApplicationInfo.MEMTAG_DEFAULT) {
+            return memtagModeToZygoteMemtagLevel(processInfo.memtagMode);
+        }
+
+        // Then at the application attribute.
+        if (info.getMemtagMode() != ApplicationInfo.MEMTAG_DEFAULT) {
+            return memtagModeToZygoteMemtagLevel(info.getMemtagMode());
+        }
+
+        if (isCompatChangeEnabled(NATIVE_MEMTAG_SYNC, info, platformCompat, 0)) {
+            return MEMORY_TAG_LEVEL_SYNC;
+        }
+
+        if (isCompatChangeEnabled(NATIVE_MEMTAG_ASYNC, info, platformCompat, 0)) {
+            return MEMORY_TAG_LEVEL_ASYNC;
+        }
+
+        // Check to ensure the app hasn't explicitly opted-out of TBI via. the manifest attribute.
+        if (!info.allowsNativeHeapPointerTagging()) {
+            return MEMORY_TAG_LEVEL_NONE;
+        }
+
+        String defaultLevel = SystemProperties.get("persist.arm64.memtag.app_default");
+        if ("sync".equals(defaultLevel)) {
+            return MEMORY_TAG_LEVEL_SYNC;
+        } else if ("async".equals(defaultLevel)) {
+            return MEMORY_TAG_LEVEL_ASYNC;
+        }
+
+        // Check to see that the compat feature for TBI is enabled.
+        if (isCompatChangeEnabled(
+                NATIVE_HEAP_POINTER_TAGGING, info, platformCompat, Build.VERSION_CODES.Q)) {
+            return MEMORY_TAG_LEVEL_TBI;
+        }
+
+        return MEMORY_TAG_LEVEL_NONE;
+    }
+
+    private static int decideTaggingLevel(
+            @NonNull ApplicationInfo info,
+            @Nullable ProcessInfo processInfo,
+            @Nullable IPlatformCompat platformCompat) {
+        // Get the desired tagging level (app manifest + compat features).
+        int level = getRequestedMemtagLevel(info, processInfo, platformCompat);
+
+        // Take into account the hardware capabilities.
+        if (nativeSupportsMemoryTagging()) {
+            // MTE devices can not do TBI, because the Zygote process already has live MTE
+            // allocations. Downgrade TBI to NONE.
+            if (level == MEMORY_TAG_LEVEL_TBI) {
+                level = MEMORY_TAG_LEVEL_NONE;
+            }
+        } else if (nativeSupportsTaggedPointers()) {
+            // TBI-but-not-MTE devices downgrade MTE modes to TBI.
+            // The idea is that if an app opts into full hardware tagging (MTE), it must be ok with
+            // the "fake" pointer tagging (TBI).
+            if (level == MEMORY_TAG_LEVEL_ASYNC || level == MEMORY_TAG_LEVEL_SYNC) {
+                level = MEMORY_TAG_LEVEL_TBI;
+            }
+        } else {
+            // Otherwise disable all tagging.
+            level = MEMORY_TAG_LEVEL_NONE;
+        }
+
+        return level;
+    }
+
+    private static int decideGwpAsanLevel(
+            @NonNull ApplicationInfo info,
+            @Nullable ProcessInfo processInfo,
+            @Nullable IPlatformCompat platformCompat) {
+        // Look at the process attribute first.
+        if (processInfo != null && processInfo.gwpAsanMode != ApplicationInfo.GWP_ASAN_DEFAULT) {
+            return processInfo.gwpAsanMode == ApplicationInfo.GWP_ASAN_ALWAYS
+                    ? GWP_ASAN_LEVEL_ALWAYS
+                    : GWP_ASAN_LEVEL_NEVER;
+        }
+        // Then at the application attribute.
+        if (info.getGwpAsanMode() != ApplicationInfo.GWP_ASAN_DEFAULT) {
+            return info.getGwpAsanMode() == ApplicationInfo.GWP_ASAN_ALWAYS
+                    ? GWP_ASAN_LEVEL_ALWAYS
+                    : GWP_ASAN_LEVEL_NEVER;
+        }
+        // If the app does not specify gwpAsanMode, the default behavior is lottery among the
+        // system apps, and disabled for user apps, unless overwritten by the compat feature.
+        if (isCompatChangeEnabled(GWP_ASAN, info, platformCompat, 0)) {
+            return GWP_ASAN_LEVEL_ALWAYS;
+        }
+        if ((info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+            return GWP_ASAN_LEVEL_LOTTERY;
+        }
+        return GWP_ASAN_LEVEL_NEVER;
+    }
+
+    private static boolean enableNativeHeapZeroInit(
+            @NonNull ApplicationInfo info,
+            @Nullable ProcessInfo processInfo,
+            @Nullable IPlatformCompat platformCompat) {
+        // Look at the process attribute first.
+        if (processInfo != null
+                && processInfo.nativeHeapZeroInitialized != ApplicationInfo.ZEROINIT_DEFAULT) {
+            return processInfo.nativeHeapZeroInitialized == ApplicationInfo.ZEROINIT_ENABLED;
+        }
+        // Then at the application attribute.
+        if (info.getNativeHeapZeroInitialized() != ApplicationInfo.ZEROINIT_DEFAULT) {
+            return info.getNativeHeapZeroInitialized() == ApplicationInfo.ZEROINIT_ENABLED;
+        }
+        // Compat feature last.
+        if (isCompatChangeEnabled(NATIVE_HEAP_ZERO_INIT, info, platformCompat, 0)) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns Zygote runtimeFlags for memory safety features (MTE, GWP-ASan, nativeHeadZeroInit)
+     * for a given app.
+     */
+    public static int getMemorySafetyRuntimeFlags(
+            @NonNull ApplicationInfo info,
+            @Nullable ProcessInfo processInfo,
+            @Nullable String instructionSet,
+            @Nullable IPlatformCompat platformCompat) {
+        int runtimeFlags = decideGwpAsanLevel(info, processInfo, platformCompat);
+        // If instructionSet is non-null, this indicates that the system_server is spawning a
+        // process with an ISA that may be different from its own. System (kernel and hardware)
+        // compatibility for these features is checked in the decideTaggingLevel in the
+        // system_server process (not the child process). As both MTE and TBI are only supported
+        // in aarch64, we can simply ensure that the new process is also aarch64. This prevents
+        // the mismatch where a 64-bit system server spawns a 32-bit child that thinks it should
+        // enable some tagging variant. Theoretically, a 32-bit system server could exist that
+        // spawns 64-bit processes, in which case the new process won't get any tagging. This is
+        // fine as we haven't seen this configuration in practice, and we can reasonable assume
+        // that if tagging is desired, the system server will be 64-bit.
+        if (instructionSet == null || instructionSet.equals("arm64")) {
+            runtimeFlags |= decideTaggingLevel(info, processInfo, platformCompat);
+        }
+        if (enableNativeHeapZeroInit(info, processInfo, platformCompat)) {
+            runtimeFlags |= NATIVE_HEAP_ZERO_INIT_ENABLED;
+        }
+        return runtimeFlags;
+    }
+
+    /**
+     * Returns Zygote runtimeFlags for memory safety features (MTE, GWP-ASan, nativeHeadZeroInit)
+     * for a secondary zygote (AppZygote or WebViewZygote).
+     */
+    public static int getMemorySafetyRuntimeFlagsForSecondaryZygote(
+            @NonNull ApplicationInfo info, @Nullable ProcessInfo processInfo) {
+        final IPlatformCompat platformCompat =
+                IPlatformCompat.Stub.asInterface(
+                        ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+        int runtimeFlags =
+                getMemorySafetyRuntimeFlags(
+                        info, processInfo, null /*instructionSet*/, platformCompat);
+
+        // TBI ("fake" pointer tagging) in AppZygote is controlled by a separate compat feature.
+        if ((runtimeFlags & MEMORY_TAG_LEVEL_MASK) == MEMORY_TAG_LEVEL_TBI
+                && isCompatChangeEnabled(
+                        NATIVE_HEAP_POINTER_TAGGING_SECONDARY_ZYGOTE,
+                        info,
+                        platformCompat,
+                        Build.VERSION_CODES.S)) {
+            // Reset memory tag level to NONE.
+            runtimeFlags &= ~MEMORY_TAG_LEVEL_MASK;
+            runtimeFlags |= MEMORY_TAG_LEVEL_NONE;
+        }
+        return runtimeFlags;
+    }
 }
diff --git a/core/java/com/android/internal/policy/ForceShowNavigationBarSettingsObserver.java b/core/java/com/android/internal/policy/ForceShowNavigationBarSettingsObserver.java
index 3e72564..75dce5a 100644
--- a/core/java/com/android/internal/policy/ForceShowNavigationBarSettingsObserver.java
+++ b/core/java/com/android/internal/policy/ForceShowNavigationBarSettingsObserver.java
@@ -26,7 +26,7 @@
 /**
  * A ContentObserver for listening force show navigation bar relative setting keys:
  *  - {@link Settings.Secure#NAVIGATION_MODE}
- *  - {@link Settings.Secure#NAV_BAR_KIDS_MODE}
+ *  - {@link Settings.Secure#NAV_BAR_FORCE_VISIBLE}
  *
  * @hide
  */
@@ -52,7 +52,7 @@
                 Settings.Secure.getUriFor(Settings.Secure.NAVIGATION_MODE),
                 false, this, UserHandle.USER_ALL);
         r.registerContentObserver(
-                Settings.Secure.getUriFor(Settings.Secure.NAV_BAR_KIDS_MODE),
+                Settings.Secure.getUriFor(Settings.Secure.NAV_BAR_FORCE_VISIBLE),
                 false, this, UserHandle.USER_ALL);
     }
 
@@ -78,6 +78,6 @@
         return Settings.Secure.getIntForUser(mContext.getContentResolver(),
                 Settings.Secure.NAVIGATION_MODE, 0, UserHandle.USER_CURRENT) == 0
                 && Settings.Secure.getIntForUser(mContext.getContentResolver(),
-                Settings.Secure.NAV_BAR_KIDS_MODE, 0, UserHandle.USER_CURRENT) == 1;
+                Settings.Secure.NAV_BAR_FORCE_VISIBLE, 0, UserHandle.USER_CURRENT) == 1;
     }
 }
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 099d1fc..d629d66 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -159,7 +159,7 @@
     /**
     * Used to notify the authentication dialog that a biometric has been authenticated.
     */
-    void onBiometricAuthenticated();
+    void onBiometricAuthenticated(int modality);
     /**
     * Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc.
     */
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index dcc1a76..9163b6d 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -125,7 +125,7 @@
             int multiSensorConfig);
 
     // Used to notify the authentication dialog that a biometric has been authenticated
-    void onBiometricAuthenticated();
+    void onBiometricAuthenticated(int modality);
     // Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc
     void onBiometricHelp(int modality, String message);
     // Used to show an error - the dialog will dismiss after a certain amount of time
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index 93864fa..b1846d2 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -671,6 +671,7 @@
             readPermissions(parser, Environment.buildPath(f, "etc", "permissions"),
                     apexPermissionFlag);
         }
+        pruneVendorApexPrivappAllowlists();
     }
 
     @VisibleForTesting
@@ -1197,7 +1198,8 @@
                                 readPrivAppPermissions(parser, mSystemExtPrivAppPermissions,
                                         mSystemExtPrivAppDenyPermissions);
                             } else if (apex) {
-                                readApexPrivAppPermissions(parser, permFile);
+                                readApexPrivAppPermissions(parser, permFile,
+                                        Environment.getApexDirectory().toPath());
                             } else {
                                 readPrivAppPermissions(parser, mPrivAppPermissions,
                                         mPrivAppDenyPermissions);
@@ -1547,6 +1549,21 @@
         }
     }
 
+    /**
+     * Prunes out any privileged permission allowlists bundled in vendor apexes.
+     */
+    @VisibleForTesting
+    public void pruneVendorApexPrivappAllowlists() {
+        for (String moduleName: mAllowedVendorApexes.keySet()) {
+            if (mApexPrivAppPermissions.containsKey(moduleName)
+                    || mApexPrivAppDenyPermissions.containsKey(moduleName)) {
+                Slog.w(TAG, moduleName + " is a vendor apex, ignore its priv-app allowlist");
+                mApexPrivAppPermissions.remove(moduleName);
+                mApexPrivAppDenyPermissions.remove(moduleName);
+            }
+        }
+    }
+
     private void readInstallInUserType(XmlPullParser parser,
             Map<String, Set<String>> doInstallMap,
             Map<String, Set<String>> nonInstallMap)
@@ -1757,8 +1774,7 @@
     /**
      * Returns the module name for a file in the apex module's partition.
      */
-    private String getApexModuleNameFromFilePath(Path path) {
-        final Path apexDirectoryPath = Environment.getApexDirectory().toPath();
+    private String getApexModuleNameFromFilePath(Path path, Path apexDirectoryPath) {
         if (!path.startsWith(apexDirectoryPath)) {
             throw new IllegalArgumentException("File " + path + " is not part of an APEX.");
         }
@@ -1770,9 +1786,14 @@
         return path.getName(apexDirectoryPath.getNameCount()).toString();
     }
 
-    private void readApexPrivAppPermissions(XmlPullParser parser, File permFile)
-            throws IOException, XmlPullParserException {
-        final String moduleName = getApexModuleNameFromFilePath(permFile.toPath());
+    /**
+     * Reads the contents of the privileged permission allowlist stored inside an APEX.
+     */
+    @VisibleForTesting
+    public void readApexPrivAppPermissions(XmlPullParser parser, File permFile,
+            Path apexDirectoryPath) throws IOException, XmlPullParserException {
+        final String moduleName =
+                getApexModuleNameFromFilePath(permFile.toPath(), apexDirectoryPath);
         final ArrayMap<String, ArraySet<String>> privAppPermissions;
         if (mApexPrivAppPermissions.containsKey(moduleName)) {
             privAppPermissions = mApexPrivAppPermissions.get(moduleName);
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 90b272c..e781760 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -829,13 +829,6 @@
 }
 
 static jint
-android_media_AudioSystem_getDevicesForStream(JNIEnv *env, jobject thiz, jint stream)
-{
-    return (jint)deviceTypesToBitMask(
-            AudioSystem::getDevicesForStream(static_cast<audio_stream_type_t>(stream)));
-}
-
-static jint
 android_media_AudioSystem_getPrimaryOutputSamplingRate(JNIEnv *env, jobject clazz)
 {
     return (jint) AudioSystem::getPrimaryOutputSamplingRate();
@@ -2959,7 +2952,6 @@
          {"getMasterMono", "()Z", (void *)android_media_AudioSystem_getMasterMono},
          {"setMasterBalance", "(F)I", (void *)android_media_AudioSystem_setMasterBalance},
          {"getMasterBalance", "()F", (void *)android_media_AudioSystem_getMasterBalance},
-         {"getDevicesForStream", "(I)I", (void *)android_media_AudioSystem_getDevicesForStream},
          {"getPrimaryOutputSamplingRate", "()I",
           (void *)android_media_AudioSystem_getPrimaryOutputSamplingRate},
          {"getPrimaryOutputFrameCount", "()I",
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 5971670..5b7092c 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -346,7 +346,7 @@
     GWP_ASAN_LEVEL_NEVER = 0 << 21,
     GWP_ASAN_LEVEL_LOTTERY = 1 << 21,
     GWP_ASAN_LEVEL_ALWAYS = 2 << 21,
-    NATIVE_HEAP_ZERO_INIT = 1 << 23,
+    NATIVE_HEAP_ZERO_INIT_ENABLED = 1 << 23,
     PROFILEABLE = 1 << 24,
 };
 
@@ -1709,13 +1709,13 @@
     // would be nice to have them for apps, we will have to wait until they are
     // proven out, have more efficient hardware, and/or apply them only to new
     // applications.
-    if (!(runtime_flags & RuntimeFlags::NATIVE_HEAP_ZERO_INIT)) {
+    if (!(runtime_flags & RuntimeFlags::NATIVE_HEAP_ZERO_INIT_ENABLED)) {
         mallopt(M_BIONIC_ZERO_INIT, 0);
     }
 
     // Now that we've used the flag, clear it so that we don't pass unknown flags to the ART
     // runtime.
-    runtime_flags &= ~RuntimeFlags::NATIVE_HEAP_ZERO_INIT;
+    runtime_flags &= ~RuntimeFlags::NATIVE_HEAP_ZERO_INIT_ENABLED;
 
     bool forceEnableGwpAsan = false;
     switch (runtime_flags & RuntimeFlags::GWP_ASAN_LEVEL_MASK) {
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 4c0ba8c..6a421f0 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -385,7 +385,18 @@
 
     optional SettingProto multi_press_timeout = 38 [ (android.privacy).dest = DEST_AUTOMATIC ];
 
-    optional SettingProto nav_bar_kids_mode = 91 [ (android.privacy).dest = DEST_AUTOMATIC ];
+    message NavBar {
+        option (android.msg_privacy).dest = DEST_EXPLICIT;
+
+        // Nav bar is forced to always be visible, even in immersive mode.
+        optional SettingProto nav_bar_force_visible = 1 [ (android.privacy).dest = DEST_AUTOMATIC ];
+        // Indicates whether the device is in kids nav mode.
+        optional SettingProto nav_bar_kids_mode = 2 [ (android.privacy).dest = DEST_AUTOMATIC ];
+    }
+    optional NavBar nav_bar = 92;
+    reserved 91; // Formerly nav_bar_kids_mode
+    reserved "nav_bar_kids_mode"; // Moved to message NavBar
+
     optional SettingProto navigation_mode = 76 [ (android.privacy).dest = DEST_AUTOMATIC ];
 
     message NfcPayment {
@@ -668,5 +679,5 @@
 
     // Please insert fields in alphabetical order and group them into messages
     // if possible (to avoid reaching the method limit).
-    // Next tag = 92;
+    // Next tag = 93;
 }
diff --git a/core/proto/android/server/biometrics.proto b/core/proto/android/server/biometrics.proto
index fc9da90..ac9e3e0 100644
--- a/core/proto/android/server/biometrics.proto
+++ b/core/proto/android/server/biometrics.proto
@@ -91,23 +91,9 @@
         STATE_CLIENT_DIED_CANCELLING = 10;
     }
 
-    enum MultiSensorState {
-        // Initializing or not yet started.
-        MULTI_SENSOR_STATE_UNKNOWN = 0;
-        // Sensors are in the process of being transitioned and there is no active sensor.
-        MULTI_SENSOR_STATE_SWITCHING = 1;
-        // Face sensor is being used as the primary input.
-        MULTI_SENSOR_STATE_FACE_SCANNING = 2;
-        // Fingerprint sensor is being used as the primary input.
-        MULTI_SENSOR_STATE_FP_SCANNING = 3;
-    }
-
     repeated SensorServiceStateProto sensor_service_states = 1;
 
     optional AuthSessionState auth_session_state = 2;
-
-    // Additional session state information, when the device has multiple sensors.
-    optional MultiSensorState auth_session_multi_sensor_state = 3;
 }
 
 // Overall state for an instance of a <Biometric>Service, for example FingerprintService or
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index e8f7b93..3929027 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -446,6 +446,7 @@
     optional bool has_compat_scale = 43;
     optional float global_scale = 44;
     repeated .android.graphics.RectProto keep_clear_areas = 45;
+    repeated .android.graphics.RectProto unrestricted_keep_clear_areas = 46;
 }
 
 message IdentifierProto {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 73cdaba..d7744e3 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -6926,6 +6926,10 @@
                  android:permission="android.permission.BIND_JOB_SERVICE">
         </service>
 
+        <service android:name="com.android.server.BinaryTransparencyService$UpdateMeasurementsJobService"
+                 android:permission="android.permission.BIND_JOB_SERVICE">
+        </service>
+
         <service android:name="com.android.server.pm.PackageManagerShellCommandDataLoader"
             android:exported="false">
             <intent-filter>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 4648f8a..0763ddb 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2113,7 +2113,7 @@
         TODO(b/189347385) make this a @SystemAPI -->
     <string name="config_systemTelevisionRemoteService" translatable="false">@string/config_tvRemoteServicePackage</string>
     <!-- The name of the package that will hold the device management role -->
-    <string name="config_deviceManager" translatable="false"></string>
+    <string name="config_devicePolicyManagement" translatable="false"></string>
     <!-- The name of the package that will hold the app protection service role. -->
     <string name="config_systemAppProtectionService" translatable="false"></string>
     <!-- The name of the package that will hold the system calendar sync manager role. -->
@@ -2122,7 +2122,7 @@
     <string name="config_defaultAutomotiveNavigation" translatable="false"></string>
 
     <!-- The name of the package that will handle updating the device management role. -->
-    <string name="config_deviceManagerUpdater" translatable="false"></string>
+    <string name="config_devicePolicyManagementUpdater" translatable="false"></string>
 
     <!-- The name of the package that will be allowed to change its components' label/icon. -->
     <string name="config_overrideComponentUiPackage" translatable="false">com.android.stk</string>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 3beb4b2..3467e1b 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3301,7 +3301,7 @@
     <!-- @hide @SystemApi -->
     <public name="config_systemSupervision" />
     <!-- @hide @SystemApi -->
-    <public name="config_deviceManager" />
+    <public name="config_devicePolicyManagement" />
     <!-- @hide @SystemApi -->
     <public name="config_systemAppProtectionService" />
     <!-- @hide @SystemApi @TestApi -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index ffd1f125..dae746b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4726,7 +4726,7 @@
 
   <java-symbol type="bool" name="config_enableSafetyCenter" />
 
-  <java-symbol type="string" name="config_deviceManagerUpdater" />
+  <java-symbol type="string" name="config_devicePolicyManagementUpdater" />
 
   <java-symbol type="string" name="config_deviceSpecificDeviceStatePolicyProvider" />
 
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index a80424e..04857ec 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -1494,6 +1494,16 @@
             android:permission="android.permission.BIND_SETTINGS_SUGGESTIONS_SERVICE"
             android:exported="true"/>
 
+        <service
+            android:name="com.android.internal.infra.ServiceConnectorTest$TestService"
+            android:process=":ServiceConnectorRemoteService"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.BIND_TEST_SERVICE" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </service>
+
         <provider android:name="android.app.activity.LocalProvider"
                 android:authorities="com.android.frameworks.coretests.LocalProvider">
             <meta-data android:name="com.android.frameworks.coretests.string" android:value="foo" />
diff --git a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ITestServiceConnectorService.aidl b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ITestServiceConnectorService.aidl
new file mode 100644
index 0000000..09544b3
--- /dev/null
+++ b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ITestServiceConnectorService.aidl
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2022 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.frameworks.coretests.aidl;
+
+interface ITestServiceConnectorService {
+    void crashProcess();
+}
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index 23ec3ea..cf78646 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -1307,55 +1307,6 @@
                 is(testBaseScore * SHORTCUT_TARGET_SCORE_BOOST));
     }
 
-    /**
-     * The case when AppPrediction service is not defined in PackageManager is already covered
-     * as a test parameter {@link ChooserActivityTest#packageManagers}. This test is checking the
-     * case when the prediction service is defined but the component is not available on the device.
-     */
-    @Test
-    public void testIsAppPredictionServiceAvailable() {
-        Intent sendIntent = createSendTextIntent();
-        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
-        when(
-                ChooserActivityOverrideData
-                        .getInstance()
-                        .resolverListController
-                        .getResolversForIntent(
-                                Mockito.anyBoolean(),
-                                Mockito.anyBoolean(),
-                                Mockito.isA(List.class)))
-                .thenReturn(resolvedComponentInfos);
-
-        final ChooserActivity activity =
-                mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
-        waitForIdle();
-        if (activity.getPackageManager().getAppPredictionServicePackageName() == null) {
-            assertThat(activity.isAppPredictionServiceAvailable(), is(false));
-        } else {
-            if (!shouldTestTogglingAppPredictionServiceAvailabilityAtRuntime()) {
-                return;
-            }
-
-            // This isn't a toggle per-se, but isAppPredictionServiceAvailable only works in
-            // system (see comment in the method).
-            assertThat(activity.isAppPredictionServiceAvailable(), is(true));
-
-            ChooserActivityOverrideData.getInstance().resources =
-                    Mockito.spy(activity.getResources());
-            when(
-                    ChooserActivityOverrideData
-                            .getInstance()
-                            .resources
-                            .getString(
-                                    getRuntimeResourceId(
-                                            "config_defaultAppPredictionService",
-                                            "string")))
-                    .thenReturn("ComponentNameThatDoesNotExist");
-
-            assertThat(activity.isAppPredictionServiceAvailable(), is(false));
-        }
-    }
-
     @Test
     public void testConvertToChooserTarget_predictionService() {
         Intent sendIntent = createSendTextIntent();
diff --git a/core/tests/coretests/src/com/android/internal/infra/ServiceConnectorTest.java b/core/tests/coretests/src/com/android/internal/infra/ServiceConnectorTest.java
new file mode 100644
index 0000000..725dcf3
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/infra/ServiceConnectorTest.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2022 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.internal.infra;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.Nullable;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.UserHandle;
+
+import androidx.annotation.NonNull;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.frameworks.coretests.aidl.ITestServiceConnectorService;
+import com.android.internal.infra.ServiceConnectorTest.CapturingServiceLifecycleCallbacks.ServiceLifeCycleEvent;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Unit tests for {@link ServiceConnector}
+ */
+@RunWith(AndroidJUnit4.class)
+public class ServiceConnectorTest {
+
+    private final CapturingServiceLifecycleCallbacks mCapturingServiceLifecycleCallbacks =
+            new CapturingServiceLifecycleCallbacks();
+    private ServiceConnector<ITestServiceConnectorService> mServiceConnector;
+
+    @Before
+    public void setup() {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        Intent testServiceConnectorServiceIntent = new Intent(TestService.ACTION_TEST_SERVICE);
+        testServiceConnectorServiceIntent.setPackage(context.getPackageName());
+
+        ServiceConnector.Impl<ITestServiceConnectorService> serviceConnector =
+                new ServiceConnector.Impl<ITestServiceConnectorService>(
+                        context,
+                        testServiceConnectorServiceIntent,
+                        /* bindingFlags= */ 0,
+                        UserHandle.myUserId(),
+                        ITestServiceConnectorService.Stub::asInterface);
+        serviceConnector.setServiceLifecycleCallbacks(mCapturingServiceLifecycleCallbacks);
+        mServiceConnector = serviceConnector;
+    }
+
+    @Test
+    public void connect_invokesLifecycleCallbacks() throws Exception {
+        connectAndWaitForDone();
+
+        assertThat(mCapturingServiceLifecycleCallbacks.getCapturedLifecycleEvents())
+                .containsExactly(ServiceLifeCycleEvent.ON_CONNECTED)
+                .inOrder();
+    }
+
+    @Test
+    public void connect_alreadyConnected_invokesLifecycleCallbacksOnce() throws Exception {
+        connectAndWaitForDone();
+        connectAndWaitForDone();
+
+        assertThat(mCapturingServiceLifecycleCallbacks.getCapturedLifecycleEvents())
+                .containsExactly(ServiceLifeCycleEvent.ON_CONNECTED)
+                .inOrder();
+    }
+
+    @Test
+    public void unbind_neverConnected_noLifecycleCallbacks() {
+        unbindAndWaitForDone();
+
+        assertThat(mCapturingServiceLifecycleCallbacks.getCapturedLifecycleEvents())
+                .isEmpty();
+    }
+
+    @Test
+    public void unbind_whileConnected_invokesLifecycleCallbacks() throws Exception {
+        connectAndWaitForDone();
+        unbindAndWaitForDone();
+
+        assertThat(mCapturingServiceLifecycleCallbacks.getCapturedLifecycleEvents())
+                .containsExactly(
+                        ServiceLifeCycleEvent.ON_CONNECTED,
+                        ServiceLifeCycleEvent.ON_DISCONNECTED)
+                .inOrder();
+    }
+
+
+    @Test
+    public void unbind_alreadyUnbound_invokesLifecycleCallbacks() throws Exception {
+        connectAndWaitForDone();
+        unbindAndWaitForDone();
+        unbindAndWaitForDone();
+
+        assertThat(mCapturingServiceLifecycleCallbacks.getCapturedLifecycleEvents())
+                .containsExactly(
+                        ServiceLifeCycleEvent.ON_CONNECTED,
+                        ServiceLifeCycleEvent.ON_DISCONNECTED)
+                .inOrder();
+    }
+
+    @Test
+    public void binds_connectsAndUnbindsMultipleTimes_invokesLifecycleCallbacks() throws Exception {
+        connectAndWaitForDone();
+        unbindAndWaitForDone();
+        connectAndWaitForDone();
+        unbindAndWaitForDone();
+        connectAndWaitForDone();
+
+        assertThat(mCapturingServiceLifecycleCallbacks.getCapturedLifecycleEvents())
+                .containsExactly(
+                        ServiceLifeCycleEvent.ON_CONNECTED,
+                        ServiceLifeCycleEvent.ON_DISCONNECTED,
+                        ServiceLifeCycleEvent.ON_CONNECTED,
+                        ServiceLifeCycleEvent.ON_DISCONNECTED,
+                        ServiceLifeCycleEvent.ON_CONNECTED)
+                .inOrder();
+    }
+
+    @Test
+    public void processCrashes_whileConnected_invokesLifecycleCallbacks() throws Exception {
+        connectAndWaitForDone();
+        waitForDone(mServiceConnector.post(service -> service.crashProcess()));
+
+        assertThat(mCapturingServiceLifecycleCallbacks.getCapturedLifecycleEvents())
+                .containsExactly(
+                        ServiceLifeCycleEvent.ON_CONNECTED,
+                        ServiceLifeCycleEvent.ON_BINDER_DIED)
+                .inOrder();
+    }
+
+    private void connectAndWaitForDone() {
+        waitForDone(mServiceConnector.connect());
+    }
+
+    private void unbindAndWaitForDone() {
+        mServiceConnector.unbind();
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+    }
+
+    private static void waitForDone(AndroidFuture<?> androidFuture) {
+        if (androidFuture.isDone()) {
+            return;
+        }
+
+        try {
+            androidFuture.get(10, TimeUnit.SECONDS);
+        } catch (InterruptedException | TimeoutException ex) {
+            throw new RuntimeException(ex);
+        } catch (ExecutionException | CancellationException ex) {
+            // Failed and canceled futures are completed
+            return;
+        }
+    }
+
+    public static final class CapturingServiceLifecycleCallbacks implements
+            ServiceConnector.ServiceLifecycleCallbacks<ITestServiceConnectorService> {
+        public enum ServiceLifeCycleEvent {
+            ON_CONNECTED,
+            ON_DISCONNECTED,
+            ON_BINDER_DIED,
+        }
+
+        private final ArrayList<ServiceLifeCycleEvent> mCapturedLifecycleEventServices =
+                new ArrayList<>();
+
+        public ArrayList<ServiceLifeCycleEvent> getCapturedLifecycleEvents() {
+            return mCapturedLifecycleEventServices;
+        }
+
+        @Override
+        public void onConnected(@NonNull ITestServiceConnectorService service) {
+            requireNonNull(service);
+            mCapturedLifecycleEventServices.add(ServiceLifeCycleEvent.ON_CONNECTED);
+        }
+
+        @Override
+        public void onDisconnected(@NonNull ITestServiceConnectorService service) {
+            requireNonNull(service);
+            mCapturedLifecycleEventServices.add(ServiceLifeCycleEvent.ON_DISCONNECTED);
+        }
+
+        @Override
+        public void onBinderDied() {
+            mCapturedLifecycleEventServices.add(ServiceLifeCycleEvent.ON_BINDER_DIED);
+        }
+    }
+
+    public static final class TestService extends Service {
+
+        public static String ACTION_TEST_SERVICE = "android.intent.action.BIND_TEST_SERVICE";
+
+        @Nullable
+        @Override
+        public IBinder onBind(@Nullable Intent intent) {
+            if (intent == null) {
+                return null;
+            }
+
+            if (!intent.getAction().equals(ACTION_TEST_SERVICE)) {
+                return null;
+            }
+
+            return new TestServiceConnectorService().asBinder();
+        }
+    }
+
+    private static final class TestServiceConnectorService extends
+            ITestServiceConnectorService.Stub {
+        @Override
+        public void crashProcess() {
+            Process.killProcess(Process.myPid());
+        }
+    }
+}
+
diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
index 33b09b8..55f205b 100644
--- a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
@@ -690,6 +690,14 @@
         }
     }
 
+    /**
+     * Gets the total duration of the animation
+     * @hide
+     */
+    public long getTotalDuration() {
+        return mAnimatorSet.getTotalDuration();
+    }
+
     private static class AnimatedVectorDrawableState extends ConstantState {
         @Config int mChangingConfigurations;
         VectorDrawable mVectorDrawable;
@@ -1074,6 +1082,7 @@
         boolean isInfinite();
         void pause();
         void resume();
+        long getTotalDuration();
     }
 
     private static class VectorDrawableAnimatorUI implements VectorDrawableAnimator {
@@ -1085,6 +1094,7 @@
         // setup by init().
         private ArrayList<AnimatorListener> mListenerArray = null;
         private boolean mIsInfinite = false;
+        private long mTotalDuration;
 
         VectorDrawableAnimatorUI(@NonNull AnimatedVectorDrawable drawable) {
             mDrawable = drawable;
@@ -1100,7 +1110,8 @@
             // Keep a deep copy of the set, such that set can be still be constantly representing
             // the static content from XML file.
             mSet = set.clone();
-            mIsInfinite = mSet.getTotalDuration() == Animator.DURATION_INFINITE;
+            mTotalDuration = mSet.getTotalDuration();
+            mIsInfinite = mTotalDuration == Animator.DURATION_INFINITE;
 
             // If there are listeners added before calling init(), now they should be setup.
             if (mListenerArray != null && !mListenerArray.isEmpty()) {
@@ -1219,6 +1230,11 @@
         private void invalidateOwningView() {
             mDrawable.invalidateSelf();
         }
+
+        @Override
+        public long getTotalDuration() {
+            return mTotalDuration;
+        }
     }
 
     /**
@@ -1249,6 +1265,7 @@
         private int mLastListenerId = 0;
         private final IntArray mPendingAnimationActions = new IntArray();
         private final AnimatedVectorDrawable mDrawable;
+        private long mTotalDuration;
 
         VectorDrawableAnimatorRT(AnimatedVectorDrawable drawable) {
             mDrawable = drawable;
@@ -1270,7 +1287,8 @@
                     .getNativeTree();
             nSetVectorDrawableTarget(mSetPtr, vectorDrawableTreePtr);
             mInitialized = true;
-            mIsInfinite = set.getTotalDuration() == Animator.DURATION_INFINITE;
+            mTotalDuration = set.getTotalDuration();
+            mIsInfinite = mTotalDuration == Animator.DURATION_INFINITE;
 
             // Check reversible.
             mIsReversible = true;
@@ -1796,6 +1814,11 @@
             }
             mPendingAnimationActions.clear();
         }
+
+        @Override
+        public long getTotalDuration() {
+            return mTotalDuration;
+        }
     }
 
     private static native long nCreateAnimatorSet();
diff --git a/graphics/java/android/graphics/drawable/AnimationDrawable.java b/graphics/java/android/graphics/drawable/AnimationDrawable.java
index 8c3fa44..7fd2201 100644
--- a/graphics/java/android/graphics/drawable/AnimationDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimationDrawable.java
@@ -424,6 +424,17 @@
             System.arraycopy(mDurations, 0, newDurations, 0, oldSize);
             mDurations = newDurations;
         }
+
+        public long getTotalDuration() {
+            if (mDurations != null) {
+                int total = 0;
+                for (int dur : mDurations) {
+                    total += dur;
+                }
+                return total;
+            }
+            return 0;
+        }
     }
 
     @Override
@@ -435,6 +446,14 @@
         }
     }
 
+    /**
+     * Gets the total duration of the animation
+     * @hide
+     */
+    public long getTotalDuration() {
+        return mAnimationState.getTotalDuration();
+    }
+
     private AnimationDrawable(AnimationState state, Resources res) {
         final AnimationState as = new AnimationState(state, this, res);
         setConstantState(as);
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
index 2e85b30..31dd10a 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -66,6 +66,7 @@
 import java.security.UnrecoverableKeyException;
 import java.security.spec.AlgorithmParameterSpec;
 import java.security.spec.ECGenParameterSpec;
+import java.security.spec.NamedParameterSpec;
 import java.security.spec.RSAKeyGenParameterSpec;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -119,36 +120,42 @@
     private static final int RSA_MIN_KEY_SIZE = 512;
     private static final int RSA_MAX_KEY_SIZE = 8192;
 
-    private static final Map<String, Integer> SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE =
+    private static final Map<String, Integer> SUPPORTED_EC_CURVE_NAME_TO_SIZE =
             new HashMap<String, Integer>();
-    private static final List<String> SUPPORTED_EC_NIST_CURVE_NAMES = new ArrayList<String>();
-    private static final List<Integer> SUPPORTED_EC_NIST_CURVE_SIZES = new ArrayList<Integer>();
+    private static final List<String> SUPPORTED_EC_CURVE_NAMES = new ArrayList<String>();
+    private static final List<Integer> SUPPORTED_EC_CURVE_SIZES = new ArrayList<Integer>();
+    private static final String CURVE_X_25519 = NamedParameterSpec.X25519.getName();
+    private static final String CURVE_ED_25519 = NamedParameterSpec.ED25519.getName();
+
 
     static {
         // Aliases for NIST P-224
-        SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-224", 224);
-        SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp224r1", 224);
+        SUPPORTED_EC_CURVE_NAME_TO_SIZE.put("p-224", 224);
+        SUPPORTED_EC_CURVE_NAME_TO_SIZE.put("secp224r1", 224);
 
 
         // Aliases for NIST P-256
-        SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-256", 256);
-        SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp256r1", 256);
-        SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("prime256v1", 256);
+        SUPPORTED_EC_CURVE_NAME_TO_SIZE.put("p-256", 256);
+        SUPPORTED_EC_CURVE_NAME_TO_SIZE.put("secp256r1", 256);
+        SUPPORTED_EC_CURVE_NAME_TO_SIZE.put("prime256v1", 256);
+        // Aliases for Curve 25519
+        SUPPORTED_EC_CURVE_NAME_TO_SIZE.put(CURVE_X_25519.toLowerCase(Locale.US), 256);
+        SUPPORTED_EC_CURVE_NAME_TO_SIZE.put(CURVE_ED_25519.toLowerCase(Locale.US), 256);
 
         // Aliases for NIST P-384
-        SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-384", 384);
-        SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp384r1", 384);
+        SUPPORTED_EC_CURVE_NAME_TO_SIZE.put("p-384", 384);
+        SUPPORTED_EC_CURVE_NAME_TO_SIZE.put("secp384r1", 384);
 
         // Aliases for NIST P-521
-        SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-521", 521);
-        SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp521r1", 521);
+        SUPPORTED_EC_CURVE_NAME_TO_SIZE.put("p-521", 521);
+        SUPPORTED_EC_CURVE_NAME_TO_SIZE.put("secp521r1", 521);
 
-        SUPPORTED_EC_NIST_CURVE_NAMES.addAll(SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.keySet());
-        Collections.sort(SUPPORTED_EC_NIST_CURVE_NAMES);
+        SUPPORTED_EC_CURVE_NAMES.addAll(SUPPORTED_EC_CURVE_NAME_TO_SIZE.keySet());
+        Collections.sort(SUPPORTED_EC_CURVE_NAMES);
 
-        SUPPORTED_EC_NIST_CURVE_SIZES.addAll(
-                new HashSet<Integer>(SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.values()));
-        Collections.sort(SUPPORTED_EC_NIST_CURVE_SIZES);
+        SUPPORTED_EC_CURVE_SIZES.addAll(
+                new HashSet<Integer>(SUPPORTED_EC_CURVE_NAME_TO_SIZE.values()));
+        Collections.sort(SUPPORTED_EC_CURVE_SIZES);
     }
 
     private final int mOriginalKeymasterAlgorithm;
@@ -164,6 +171,7 @@
     private int mKeySizeBits;
     private SecureRandom mRng;
     private KeyDescriptor mAttestKeyDescriptor;
+    private String mEcCurveName;
 
     private int[] mKeymasterPurposes;
     private int[] mKeymasterBlockModes;
@@ -177,12 +185,15 @@
         mOriginalKeymasterAlgorithm = keymasterAlgorithm;
     }
 
-    private @EcCurve int keySize2EcCurve(int keySizeBits)
+    private static @EcCurve int keySizeAndNameToEcCurve(int keySizeBits, String ecCurveName)
             throws InvalidAlgorithmParameterException {
         switch (keySizeBits) {
             case 224:
                 return EcCurve.P_224;
             case 256:
+                if (isCurve25519(ecCurveName)) {
+                    return EcCurve.CURVE_25519;
+                }
                 return EcCurve.P_256;
             case 384:
                 return EcCurve.P_384;
@@ -247,7 +258,8 @@
             if (mKeySizeBits == -1) {
                 mKeySizeBits = getDefaultKeySize(keymasterAlgorithm);
             }
-            checkValidKeySize(keymasterAlgorithm, mKeySizeBits, mSpec.isStrongBoxBacked());
+            checkValidKeySize(keymasterAlgorithm, mKeySizeBits, mSpec.isStrongBoxBacked(),
+                    mEcCurveName);
 
             if (spec.getKeystoreAlias() == null) {
                 throw new InvalidAlgorithmParameterException("KeyStore entry alias not provided");
@@ -299,6 +311,7 @@
 
             mAttestKeyDescriptor = buildAndCheckAttestKeyDescriptor(spec);
             checkAttestKeyPurpose(spec);
+            checkCorrectKeyPurposeForCurve(spec);
 
             success = true;
         } finally {
@@ -317,6 +330,42 @@
         }
     }
 
+    private void checkCorrectKeyPurposeForCurve(KeyGenParameterSpec spec)
+            throws InvalidAlgorithmParameterException {
+        // Validate the key usage purposes against the curve. x25519 should be
+        // key exchange only, ed25519 signing and attesting.
+
+        if (!isCurve25519(mEcCurveName)) {
+            return;
+        }
+
+        if (mEcCurveName.equalsIgnoreCase(CURVE_X_25519)
+                && spec.getPurposes() != KeyProperties.PURPOSE_AGREE_KEY) {
+            throw new InvalidAlgorithmParameterException(
+                    "x25519 may only be used for key agreement.");
+        } else if (mEcCurveName.equalsIgnoreCase(CURVE_ED_25519)
+                && !hasOnlyAllowedPurposeForEd25519(spec.getPurposes())) {
+            throw new InvalidAlgorithmParameterException(
+                    "ed25519 may not be used for key agreement.");
+        }
+    }
+
+    private static boolean isCurve25519(String ecCurveName) {
+        if (ecCurveName == null) {
+            return false;
+        }
+        return ecCurveName.equalsIgnoreCase(CURVE_X_25519)
+                || ecCurveName.equalsIgnoreCase(CURVE_ED_25519);
+    }
+
+    private static boolean hasOnlyAllowedPurposeForEd25519(@KeyProperties.PurposeEnum int purpose) {
+        final int allowedPurposes = KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY
+                | KeyProperties.PURPOSE_ATTEST_KEY;
+        boolean hasAllowedPurpose = (purpose & allowedPurposes) != 0;
+        boolean hasDisallowedPurpose = (purpose & ~allowedPurposes) != 0;
+        return hasAllowedPurpose && !hasDisallowedPurpose;
+    }
+
     private KeyDescriptor buildAndCheckAttestKeyDescriptor(KeyGenParameterSpec spec)
             throws InvalidAlgorithmParameterException {
         if (spec.getAttestKeyAlias() != null) {
@@ -473,6 +522,7 @@
         mRSAPublicExponent = null;
         mRng = null;
         mKeyStore = null;
+        mEcCurveName = null;
     }
 
     private void initAlgorithmSpecificParameters() throws InvalidAlgorithmParameterException {
@@ -514,13 +564,13 @@
             case KeymasterDefs.KM_ALGORITHM_EC:
                 if (algSpecificSpec instanceof ECGenParameterSpec) {
                     ECGenParameterSpec ecSpec = (ECGenParameterSpec) algSpecificSpec;
-                    String curveName = ecSpec.getName();
-                    Integer ecSpecKeySizeBits = SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.get(
-                            curveName.toLowerCase(Locale.US));
+                    mEcCurveName = ecSpec.getName();
+                    final Integer ecSpecKeySizeBits = SUPPORTED_EC_CURVE_NAME_TO_SIZE.get(
+                            mEcCurveName.toLowerCase(Locale.US));
                     if (ecSpecKeySizeBits == null) {
                         throw new InvalidAlgorithmParameterException(
-                                "Unsupported EC curve name: " + curveName
-                                        + ". Supported: " + SUPPORTED_EC_NIST_CURVE_NAMES);
+                                "Unsupported EC curve name: " + mEcCurveName
+                                        + ". Supported: " + SUPPORTED_EC_CURVE_NAMES);
                     }
                     if (mKeySizeBits == -1) {
                         mKeySizeBits = ecSpecKeySizeBits;
@@ -744,7 +794,7 @@
 
         if (mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_EC) {
             params.add(KeyStore2ParameterUtils.makeEnum(
-                    Tag.EC_CURVE, keySize2EcCurve(mKeySizeBits)
+                    Tag.EC_CURVE, keySizeAndNameToEcCurve(mKeySizeBits, mEcCurveName)
             ));
         }
 
@@ -864,7 +914,8 @@
     private static void checkValidKeySize(
             int keymasterAlgorithm,
             int keySize,
-            boolean isStrongBoxBacked)
+            boolean isStrongBoxBacked,
+            String mEcCurveName)
             throws InvalidAlgorithmParameterException {
         switch (keymasterAlgorithm) {
             case KeymasterDefs.KM_ALGORITHM_EC:
@@ -873,9 +924,13 @@
                             "Unsupported StrongBox EC key size: "
                                     + keySize + " bits. Supported: 256");
                 }
-                if (!SUPPORTED_EC_NIST_CURVE_SIZES.contains(keySize)) {
+                if (isStrongBoxBacked && isCurve25519(mEcCurveName)) {
+                    throw new InvalidAlgorithmParameterException(
+                            "Unsupported StrongBox EC: " + mEcCurveName);
+                }
+                if (!SUPPORTED_EC_CURVE_SIZES.contains(keySize)) {
                     throw new InvalidAlgorithmParameterException("Unsupported EC key size: "
-                            + keySize + " bits. Supported: " + SUPPORTED_EC_NIST_CURVE_SIZES);
+                            + keySize + " bits. Supported: " + SUPPORTED_EC_CURVE_SIZES);
                 }
                 break;
             case KeymasterDefs.KM_ALGORITHM_RSA:
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
index 72a145f..358104f 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
@@ -66,6 +66,11 @@
     private static final String DESEDE_SYSTEM_PROPERTY =
             "ro.hardware.keystore_desede";
 
+    // Conscrypt returns the Ed25519 OID as the JCA key algorithm.
+    private static final String ED25519_OID = "1.3.101.112";
+    // Conscrypt returns "XDH" as the X25519 JCA key algorithm.
+    private static final String X25519_ALIAS = "XDH";
+
     /** @hide **/
     public AndroidKeyStoreProvider() {
         super(PROVIDER_NAME, 1.0, "Android KeyStore security provider");
@@ -78,10 +83,16 @@
         // java.security.KeyPairGenerator
         put("KeyPairGenerator.EC", PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$EC");
         put("KeyPairGenerator.RSA", PACKAGE_NAME +  ".AndroidKeyStoreKeyPairGeneratorSpi$RSA");
+        put("KeyPairGenerator." + X25519_ALIAS,
+                PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$RSA");
+        put("KeyPairGenerator." + ED25519_OID,
+                PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$RSA");
 
         // java.security.KeyFactory
         putKeyFactoryImpl("EC");
         putKeyFactoryImpl("RSA");
+        putKeyFactoryImpl(X25519_ALIAS);
+        putKeyFactoryImpl(ED25519_OID);
 
         // javax.crypto.KeyGenerator
         put("KeyGenerator.AES", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$AES");
@@ -219,12 +230,17 @@
 
         KeyStoreSecurityLevel securityLevel = iSecurityLevel;
         if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(jcaKeyAlgorithm)) {
-
             return new AndroidKeyStoreECPublicKey(descriptor, metadata,
                     iSecurityLevel, (ECPublicKey) publicKey);
         } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(jcaKeyAlgorithm)) {
             return new AndroidKeyStoreRSAPublicKey(descriptor, metadata,
                     iSecurityLevel, (RSAPublicKey) publicKey);
+        } else if (ED25519_OID.equalsIgnoreCase(jcaKeyAlgorithm)) {
+            //TODO(b/214203951) missing classes in conscrypt
+            throw new ProviderException("Curve " + ED25519_OID + " not supported yet");
+        } else if (X25519_ALIAS.equalsIgnoreCase(jcaKeyAlgorithm)) {
+            //TODO(b/214203951) missing classes in conscrypt
+            throw new ProviderException("Curve " + X25519_ALIAS + " not supported yet");
         } else {
             throw new ProviderException("Unsupported Android Keystore public key algorithm: "
                     + jcaKeyAlgorithm);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 9b41468..8d5fdfb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -52,6 +52,9 @@
  */
 public class BackAnimationController implements RemoteCallable<BackAnimationController> {
 
+    private static final String BACK_PREDICTABILITY_PROP = "persist.debug.back_predictability";
+    public static final boolean IS_ENABLED = SystemProperties
+            .getInt(BACK_PREDICTABILITY_PROP, 0) > 0;
     private static final String BACK_PREDICTABILITY_PROGRESS_THRESHOLD_PROP =
             "persist.debug.back_predictability_progress_threshold";
     private static final int PROGRESS_THRESHOLD = SystemProperties
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index c6a68dc..58f79f3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -1608,7 +1608,9 @@
             Log.d(TAG, "addBubble: " + bubble);
         }
 
-        if (getBubbleCount() == 0 && shouldShowStackEdu()) {
+        final boolean firstBubble = getBubbleCount() == 0;
+
+        if (firstBubble && shouldShowStackEdu()) {
             // Override the default stack position if we're showing user education.
             mStackAnimationController.setStackPosition(mPositioner.getDefaultStartPosition());
         }
@@ -1621,7 +1623,7 @@
                 new FrameLayout.LayoutParams(mPositioner.getBubbleSize(),
                         mPositioner.getBubbleSize()));
 
-        if (getBubbleCount() == 0) {
+        if (firstBubble) {
             mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide();
         }
         // Set the dot position to the opposite of the side the stack is resting on, since the stack
@@ -1652,6 +1654,10 @@
                     bubble.cleanupViews();
                 }
                 updateExpandedView();
+                if (getBubbleCount() == 0 && !isExpanded()) {
+                    // This is the last bubble and the stack is collapsed
+                    updateStackPosition();
+                }
                 logBubbleEvent(bubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED);
                 return;
             }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 8a482fb..b52c8d1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -502,16 +502,24 @@
 
         if (!mBounds1.equals(mWinBounds1) || !task1.token.equals(mWinToken1)) {
             wct.setBounds(task1.token, mBounds1);
+            wct.setSmallestScreenWidthDp(task1.token, getSmallestWidthDp(mBounds1));
             mWinBounds1.set(mBounds1);
             mWinToken1 = task1.token;
         }
         if (!mBounds2.equals(mWinBounds2) || !task2.token.equals(mWinToken2)) {
             wct.setBounds(task2.token, mBounds2);
+            wct.setSmallestScreenWidthDp(task2.token, getSmallestWidthDp(mBounds2));
             mWinBounds2.set(mBounds2);
             mWinToken2 = task2.token;
         }
     }
 
+    private int getSmallestWidthDp(Rect bounds) {
+        final int minWidth = Math.min(bounds.width(), bounds.height());
+        final float density = mContext.getResources().getDisplayMetrics().density;
+        return (int) (minWidth / density);
+    }
+
     /**
      * Shift configuration bounds to prevent client apps get configuration changed or relaunch. And
      * restore shifted configuration bounds if it's no longer shifted.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index c94f3d1..0362b3f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -699,7 +699,10 @@
             Context context,
             @ShellMainThread ShellExecutor shellExecutor
     ) {
-        return Optional.of(
-                new BackAnimationController(shellExecutor, context));
+        if (BackAnimationController.IS_ENABLED) {
+            return Optional.of(
+                    new BackAnimationController(shellExecutor, context));
+        }
+        return Optional.empty();
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
index 082fe92..22dd9b9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
@@ -18,7 +18,6 @@
 
 import android.annotation.Nullable;
 import android.content.Context;
-import android.graphics.Rect;
 import android.view.SurfaceSession;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
@@ -49,14 +48,13 @@
         return mIsActive;
     }
 
-    void activate(Rect rootBounds, WindowContainerTransaction wct, boolean includingTopTask) {
+    void activate(WindowContainerTransaction wct, boolean includingTopTask) {
         if (mIsActive) return;
 
         final WindowContainerToken rootToken = mRootTaskInfo.token;
-        wct.setBounds(rootToken, rootBounds)
-                // Moving the root task to top after the child tasks were re-parented , or the root
-                // task cannot be visible and focused.
-                .reorder(rootToken, true /* onTop */);
+        // Moving the root task to top after the child tasks were re-parented , or the root
+        // task cannot be visible and focused.
+        wct.reorder(rootToken, true /* onTop */);
         if (includingTopTask) {
             wct.reparentTasks(
                     null /* currentParent */,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 76641f0..7318f48 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -354,8 +354,8 @@
         mSplitLayout.setDivideRatio(splitRatio);
         // Build a request WCT that will launch both apps such that task 0 is on the main stage
         // while task 1 is on the side stage.
-        mMainStage.activate(getMainStageBounds(), wct, false /* reparent */);
-        mSideStage.setBounds(getSideStageBounds(), wct);
+        mMainStage.activate(wct, false /* reparent */);
+        updateWindowBounds(mSplitLayout, wct);
 
         // Make sure the launch options will put tasks in the corresponding split roots
         addActivityOptions(mainOptions, mMainStage);
@@ -470,13 +470,14 @@
 
         mSplitLayout.setDivideRatio(splitRatio);
         if (mMainStage.isActive()) {
-            mMainStage.moveToTop(getMainStageBounds(), wct);
+            mMainStage.moveToTop(wct);
         } else {
             // Build a request WCT that will launch both apps such that task 0 is on the main stage
             // while task 1 is on the side stage.
-            mMainStage.activate(getMainStageBounds(), wct, false /* reparent */);
+            mMainStage.activate(wct, false /* reparent */);
         }
-        mSideStage.moveToTop(getSideStageBounds(), wct);
+        mSideStage.moveToTop(wct);
+        updateWindowBounds(mSplitLayout, wct);
 
         // Make sure the launch options will put tasks in the corresponding split roots
         addActivityOptions(mainOptions, mMainStage);
@@ -774,8 +775,9 @@
             setSideStagePosition(startPosition, wct);
             mSideStage.addTask(taskInfo, wct);
         }
-        mMainStage.activate(getMainStageBounds(), wct, true /* includingTopTask */);
-        mSideStage.moveToTop(getSideStageBounds(), wct);
+        mMainStage.activate(wct, true /* includingTopTask */);
+        mSideStage.moveToTop(wct);
+        updateWindowBounds(mSplitLayout, wct);
     }
 
     void finishEnterSplitScreen(SurfaceControl.Transaction t) {
@@ -997,10 +999,9 @@
                 // Exit to side stage if main stage no longer has children.
                 exitSplitScreen(mSideStage, EXIT_REASON_APP_FINISHED);
             }
-        } else if (isSideStage) {
+        } else if (isSideStage && !mMainStage.isActive()) {
             final WindowContainerTransaction wct = new WindowContainerTransaction();
             mSplitLayout.init();
-            // Make sure the main stage is active.
             prepareEnterSplitScreen(wct);
             mSyncQueue.queue(wct);
             mSyncQueue.runInSync(t -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index 83534c1..c5aab45 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -53,8 +53,8 @@
  * Base class that handle common task org. related for split-screen stages.
  * Note that this class and its sub-class do not directly perform hierarchy operations.
  * They only serve to hold a collection of tasks and provide APIs like
- * {@link #setBounds(Rect, WindowContainerTransaction)} for the centralized {@link StageCoordinator}
- * to perform operations in-sync with other containers.
+ * {@link #addTask(ActivityManager.RunningTaskInfo, WindowContainerTransaction)} for the centralized
+ * {@link StageCoordinator} to perform hierarchy operations in-sync with other containers.
  *
  * @see StageCoordinator
  */
@@ -310,13 +310,9 @@
         wct.reparent(task.token, mRootTaskInfo.token, true /* onTop*/);
     }
 
-    void moveToTop(Rect rootBounds, WindowContainerTransaction wct) {
+    void moveToTop(WindowContainerTransaction wct) {
         final WindowContainerToken rootToken = mRootTaskInfo.token;
-        wct.setBounds(rootToken, rootBounds).reorder(rootToken, true /* onTop */);
-    }
-
-    void setBounds(Rect bounds, WindowContainerTransaction wct) {
-        wct.setBounds(mRootTaskInfo.token, bounds);
+        wct.reorder(rootToken, true /* onTop */);
     }
 
     void reorderChild(int taskId, boolean onTop, WindowContainerTransaction wct) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index 025bcad..33aa018 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -69,6 +69,7 @@
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.launcher3.icons.BaseIconFactory;
 import com.android.launcher3.icons.IconProvider;
+import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 
@@ -115,8 +116,10 @@
     private final Handler mSplashscreenWorkerHandler;
     @VisibleForTesting
     final ColorCache mColorCache;
+    private final ShellExecutor mSplashScreenExecutor;
 
-    SplashscreenContentDrawer(Context context, IconProvider iconProvider, TransactionPool pool) {
+    SplashscreenContentDrawer(Context context, IconProvider iconProvider, TransactionPool pool,
+            ShellExecutor splashScreenExecutor) {
         mContext = context;
         mIconProvider = iconProvider;
         mTransactionPool = pool;
@@ -129,6 +132,7 @@
         shellSplashscreenWorkerThread.start();
         mSplashscreenWorkerHandler = shellSplashscreenWorkerThread.getThreadHandler();
         mColorCache = new ColorCache(mContext, mSplashscreenWorkerHandler);
+        mSplashScreenExecutor = splashScreenExecutor;
     }
 
     /**
@@ -397,7 +401,7 @@
 
         SplashScreenView build() {
             Drawable iconDrawable;
-            final int animationDuration;
+            final long animationDuration;
             if (mSuggestType == STARTING_WINDOW_TYPE_EMPTY_SPLASH_SCREEN
                     || mSuggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
                 // empty or legacy splash screen case
@@ -455,8 +459,8 @@
                         iconDrawable, mDefaultIconSize, mFinalIconSize, mSplashscreenWorkerHandler);
             } else {
                 mFinalIconDrawables = SplashscreenIconDrawableFactory.makeIconDrawable(
-                        mTmpAttrs.mIconBgColor, mThemeColor,
-                        iconDrawable, mDefaultIconSize, mFinalIconSize, mSplashscreenWorkerHandler);
+                        mTmpAttrs.mIconBgColor, mThemeColor, iconDrawable, mDefaultIconSize,
+                        mFinalIconSize, mSplashscreenWorkerHandler, mSplashScreenExecutor);
             }
         }
 
@@ -516,7 +520,7 @@
         }
 
         private SplashScreenView fillViewWithIcon(int iconSize, @Nullable Drawable[] iconDrawable,
-                int animationDuration, Consumer<Runnable> uiThreadInitTask) {
+                long animationDuration, Consumer<Runnable> uiThreadInitTask) {
             Drawable foreground = null;
             Drawable background = null;
             if (iconDrawable != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
index 54281e0..fdd5a15 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
@@ -18,9 +18,7 @@
 
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 
-import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
 import android.annotation.ColorInt;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -36,6 +34,8 @@
 import android.graphics.Rect;
 import android.graphics.drawable.AdaptiveIconDrawable;
 import android.graphics.drawable.Animatable;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.graphics.drawable.AnimationDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.os.Trace;
@@ -44,6 +44,9 @@
 import android.window.SplashScreenView;
 
 import com.android.internal.R;
+import com.android.wm.shell.common.ShellExecutor;
+
+import java.util.function.LongConsumer;
 
 /**
  * Creating a lightweight Drawable object used for splash screen.
@@ -60,14 +63,15 @@
      */
     static Drawable[] makeIconDrawable(@ColorInt int backgroundColor, @ColorInt int themeColor,
             @NonNull Drawable foregroundDrawable, int srcIconSize, int iconSize,
-            Handler splashscreenWorkerHandler) {
+            Handler splashscreenWorkerHandler, ShellExecutor splashScreenExecutor) {
         Drawable foreground;
         Drawable background = null;
         boolean drawBackground =
                 backgroundColor != Color.TRANSPARENT && backgroundColor != themeColor;
 
         if (foregroundDrawable instanceof Animatable) {
-            foreground = new AnimatableIconAnimateListener(foregroundDrawable);
+            foreground = new AnimatableIconAnimateListener(foregroundDrawable,
+                    splashScreenExecutor);
         } else if (foregroundDrawable instanceof AdaptiveIconDrawable) {
             // If the icon is Adaptive, we already use the icon background.
             drawBackground = false;
@@ -266,14 +270,37 @@
      */
     public static class AnimatableIconAnimateListener extends AdaptiveForegroundDrawable
             implements SplashScreenView.IconAnimateListener {
-        private Animatable mAnimatableIcon;
-        private Animator mIconAnimator;
+        private final Animatable mAnimatableIcon;
         private boolean mAnimationTriggered;
         private AnimatorListenerAdapter mJankMonitoringListener;
+        private boolean mRunning;
+        private long mDuration;
+        private LongConsumer mStartListener;
+        private final ShellExecutor mSplashScreenExecutor;
 
-        AnimatableIconAnimateListener(@NonNull Drawable foregroundDrawable) {
+        AnimatableIconAnimateListener(@NonNull Drawable foregroundDrawable,
+                ShellExecutor splashScreenExecutor) {
             super(foregroundDrawable);
-            mForegroundDrawable.setCallback(mCallback);
+            Callback callback = new Callback() {
+                @Override
+                public void invalidateDrawable(@NonNull Drawable who) {
+                    invalidateSelf();
+                }
+
+                @Override
+                public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what,
+                        long when) {
+                    scheduleSelf(what, when);
+                }
+
+                @Override
+                public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
+                    unscheduleSelf(what);
+                }
+            };
+            mForegroundDrawable.setCallback(callback);
+            mSplashScreenExecutor = splashScreenExecutor;
+            mAnimatableIcon = (Animatable) mForegroundDrawable;
         }
 
         @Override
@@ -282,83 +309,68 @@
         }
 
         @Override
-        public boolean prepareAnimate(long duration, Runnable startListener) {
-            mAnimatableIcon = (Animatable) mForegroundDrawable;
-            mIconAnimator = ValueAnimator.ofInt(0, 1);
-            mIconAnimator.setDuration(duration);
-            mIconAnimator.addListener(new Animator.AnimatorListener() {
-                @Override
-                public void onAnimationStart(Animator animation) {
-                    if (startListener != null) {
-                        startListener.run();
-                    }
-                    try {
-                        if (mJankMonitoringListener != null) {
-                            mJankMonitoringListener.onAnimationStart(animation);
-                        }
-                        mAnimatableIcon.start();
-                    } catch (Exception ex) {
-                        Log.e(TAG, "Error while running the splash screen animated icon", ex);
-                        animation.cancel();
-                    }
-                }
+        public void prepareAnimate(long duration, LongConsumer startListener) {
+            stopAnimation();
+            mDuration = duration;
+            mStartListener = startListener;
+        }
 
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    mAnimatableIcon.stop();
-                    if (mJankMonitoringListener != null) {
-                        mJankMonitoringListener.onAnimationEnd(animation);
-                    }
+        private void startAnimation() {
+            if (mJankMonitoringListener != null) {
+                mJankMonitoringListener.onAnimationStart(null);
+            }
+            try {
+                mAnimatableIcon.start();
+            } catch (Exception ex) {
+                Log.e(TAG, "Error while running the splash screen animated icon", ex);
+                mRunning = false;
+                if (mJankMonitoringListener != null) {
+                    mJankMonitoringListener.onAnimationCancel(null);
                 }
+                if (mStartListener != null) {
+                    mStartListener.accept(mDuration);
+                }
+                return;
+            }
+            long animDuration = mDuration;
+            if (mAnimatableIcon instanceof AnimatedVectorDrawable
+                    && ((AnimatedVectorDrawable) mAnimatableIcon).getTotalDuration() > 0) {
+                animDuration = ((AnimatedVectorDrawable) mAnimatableIcon).getTotalDuration();
+            } else if (mAnimatableIcon instanceof AnimationDrawable
+                    && ((AnimationDrawable) mAnimatableIcon).getTotalDuration() > 0) {
+                animDuration = ((AnimationDrawable) mAnimatableIcon).getTotalDuration();
+            }
+            mRunning = true;
+            mSplashScreenExecutor.executeDelayed(this::stopAnimation, animDuration);
+            if (mStartListener != null) {
+                mStartListener.accept(Math.max(animDuration, 0));
+            }
+        }
 
-                @Override
-                public void onAnimationCancel(Animator animation) {
-                    mAnimatableIcon.stop();
-                    if (mJankMonitoringListener != null) {
-                        mJankMonitoringListener.onAnimationCancel(animation);
-                    }
-                }
-
-                @Override
-                public void onAnimationRepeat(Animator animation) {
-                    // do not repeat
-                    mAnimatableIcon.stop();
-                }
-            });
-            return true;
+        private void onAnimationEnd() {
+            mAnimatableIcon.stop();
+            if (mJankMonitoringListener != null) {
+                mJankMonitoringListener.onAnimationEnd(null);
+            }
+            mStartListener = null;
+            mRunning = false;
         }
 
         @Override
         public void stopAnimation() {
-            if (mIconAnimator != null && mIconAnimator.isRunning()) {
-                mIconAnimator.end();
+            if (mRunning) {
+                mSplashScreenExecutor.removeCallbacks(this::stopAnimation);
+                onAnimationEnd();
                 mJankMonitoringListener = null;
             }
         }
 
-        private final Callback mCallback = new Callback() {
-            @Override
-            public void invalidateDrawable(@NonNull Drawable who) {
-                invalidateSelf();
-            }
-
-            @Override
-            public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
-                scheduleSelf(what, when);
-            }
-
-            @Override
-            public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
-                unscheduleSelf(what);
-            }
-        };
-
         private void ensureAnimationStarted() {
             if (mAnimationTriggered) {
                 return;
             }
-            if (mIconAnimator != null && !mIconAnimator.isRunning()) {
-                mIconAnimator.start();
+            if (!mRunning) {
+                startAnimation();
             }
             mAnimationTriggered = true;
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index 61cbf6e..04d6ef7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -148,7 +148,8 @@
         mContext = context;
         mDisplayManager = mContext.getSystemService(DisplayManager.class);
         mSplashScreenExecutor = splashScreenExecutor;
-        mSplashscreenContentDrawer = new SplashscreenContentDrawer(mContext, iconProvider, pool);
+        mSplashscreenContentDrawer = new SplashscreenContentDrawer(mContext, iconProvider, pool,
+                mSplashScreenExecutor);
         mSplashScreenExecutor.execute(() -> mChoreographer = Choreographer.getInstance());
         mWindowManagerGlobal = WindowManagerGlobal.getInstance();
         mDisplayManager.getDisplay(DEFAULT_DISPLAY);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index 83d5f04..daec336 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -28,8 +28,10 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
+import android.app.ActivityManager;
 import android.content.res.Configuration;
 import android.graphics.Rect;
+import android.window.WindowContainerTransaction;
 
 import androidx.test.annotation.UiThreadTest;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -38,6 +40,7 @@
 import com.android.internal.policy.DividerSnapAlgorithm;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestRunningTaskInfoBuilder;
 import com.android.wm.shell.common.DisplayImeController;
 
 import org.junit.Before;
@@ -56,6 +59,7 @@
     @Mock SplitWindowManager.ParentContainerCallbacks mCallbacks;
     @Mock DisplayImeController mDisplayImeController;
     @Mock ShellTaskOrganizer mTaskOrganizer;
+    @Mock WindowContainerTransaction mWct;
     @Captor ArgumentCaptor<Runnable> mRunnableCaptor;
     private SplitLayout mSplitLayout;
 
@@ -149,6 +153,16 @@
         verify(mSplitLayoutHandler).onSnappedToDismiss(eq(true));
     }
 
+    @Test
+    public void testApplyTaskChanges_updatesSmallestScreenWidthDp() {
+        final ActivityManager.RunningTaskInfo task1 = new TestRunningTaskInfoBuilder().build();
+        final ActivityManager.RunningTaskInfo task2 = new TestRunningTaskInfoBuilder().build();
+        mSplitLayout.applyTaskChanges(mWct, task1, task2);
+
+        verify(mWct).setSmallestScreenWidthDp(eq(task1.token), anyInt());
+        verify(mWct).setSmallestScreenWidthDp(eq(task2.token), anyInt());
+    }
+
     private void waitDividerFlingFinished() {
         verify(mSplitLayout).flingDividePosition(anyInt(), anyInt(), mRunnableCaptor.capture());
         mRunnableCaptor.getValue().run();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java
index c972067..0639ad5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java
@@ -67,8 +67,7 @@
 
     @Test
     public void testActiveDeactivate() {
-        mMainStage.activate(mRootTaskInfo.configuration.windowConfiguration.getBounds(), mWct,
-                true /* reparent */);
+        mMainStage.activate(mWct, true /* reparent */);
         assertThat(mMainStage.isActive()).isTrue();
 
         mMainStage.deactivate(mWct);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index 59c377a..19d2a7e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -44,7 +44,6 @@
 
 import android.annotation.NonNull;
 import android.app.ActivityManager;
-import android.graphics.Rect;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.view.SurfaceControl;
@@ -421,8 +420,7 @@
                 mock(SurfaceControl.Transaction.class),
                 mock(SurfaceControl.Transaction.class),
                 mock(Transitions.TransitionFinishCallback.class));
-        mMainStage.activate(new Rect(0, 0, 100, 100), new WindowContainerTransaction(),
-                true /* includingTopTask */);
+        mMainStage.activate(new WindowContainerTransaction(), true /* includingTopTask */);
     }
 
     private boolean containsSplitEnter(@NonNull WindowContainerTransaction wct) {
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 6c99a07..3a2f0d9 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -5624,27 +5624,33 @@
      * Note that the information may be imprecise when the implementation
      * cannot distinguish whether a particular device is enabled.
      *
-     * {@hide}
+     * @deprecated on {@link android.os.Build.VERSION_CODES#T} as new devices
+     *             will have multi-bit device types.
+     *             Prefer to use {@link #getDevicesForAttributes()} instead,
+     *             noting that getDevicesForStream() has a few small discrepancies
+     *             for better volume handling.
+     * @hide
      */
     @UnsupportedAppUsage
+    @Deprecated
     public int getDevicesForStream(int streamType) {
         switch (streamType) {
-        case STREAM_VOICE_CALL:
-        case STREAM_SYSTEM:
-        case STREAM_RING:
-        case STREAM_MUSIC:
-        case STREAM_ALARM:
-        case STREAM_NOTIFICATION:
-        case STREAM_DTMF:
-        case STREAM_ACCESSIBILITY:
-            final IAudioService service = getService();
-            try {
-                return service.getDevicesForStream(streamType);
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
-        default:
-            return 0;
+            case STREAM_VOICE_CALL:
+            case STREAM_SYSTEM:
+            case STREAM_RING:
+            case STREAM_MUSIC:
+            case STREAM_ALARM:
+            case STREAM_NOTIFICATION:
+            case STREAM_DTMF:
+            case STREAM_ACCESSIBILITY:
+                final IAudioService service = getService();
+                try {
+                    return service.getDeviceMaskForStream(streamType);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            default:
+                return 0;
         }
     }
 
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 210f3e5..acb34b5 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -29,6 +29,7 @@
 import android.media.audio.common.AidlConversion;
 import android.media.audiofx.AudioEffect;
 import android.media.audiopolicy.AudioMix;
+import android.media.audiopolicy.AudioProductStrategy;
 import android.os.Build;
 import android.os.IBinder;
 import android.os.Parcel;
@@ -47,6 +48,7 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.TreeSet;
 
 /* IF YOU CHANGE ANY OF THE CONSTANTS IN THIS FILE, DO NOT FORGET
  * TO UPDATE THE CORRESPONDING NATIVE GLUE AND AudioManager.java.
@@ -1655,9 +1657,63 @@
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public static native boolean getMasterMute();
-    /** @hide */
+    /** @hide
+     * Only used (unsupported) for legacy apps.
+     * @deprecated on {@link android.os.Build.VERSION_CODES#T} as new devices
+     *             will have multi-bit device types.
+     *             Use {@link AudioManager#getDevicesForAttributes(AudioAttributes)} instead.
+     */
     @UnsupportedAppUsage
-    public static native int getDevicesForStream(int stream);
+    @Deprecated
+    public static int getDevicesForStream(int stream) {
+        final AudioAttributes attr =
+                AudioProductStrategy.getAudioAttributesForStrategyWithLegacyStreamType(stream);
+        return getDeviceMaskFromSet(generateAudioDeviceTypesSet(
+                getDevicesForAttributes(attr, true /* forVolume */)));
+    }
+
+    /** @hide
+     * Conversion from a device set to a bit mask.
+     *
+     * Legacy compatibility method (use a device list instead of a bit mask).
+     * Conversion to bit mask skips multi-bit (S and later) internal device types
+     * (e.g. AudioSystem.DEVICE_OUT* or AudioManager.DEVICE_OUT*) for legacy
+     * compatibility reasons.  Legacy apps will not understand these new device types
+     * and it will raise false matches with old device types.
+     */
+    public static int getDeviceMaskFromSet(@NonNull Set<Integer> deviceSet) {
+        int deviceMask = DEVICE_NONE; // zero.
+        int deviceInChecksum = DEVICE_BIT_IN;
+        for (Integer device : deviceSet) {
+            if ((device & (device - 1) & ~DEVICE_BIT_IN) != 0) {
+                Log.v(TAG, "getDeviceMaskFromSet skipping multi-bit device value " + device);
+                continue;
+            }
+            deviceMask |= device;
+            deviceInChecksum &= device;
+        }
+        // Validate that deviceSet is either ALL input devices or ALL output devices.
+        // We check that the "OR" of all the DEVICE_BIT_INs == the "AND" of all DEVICE_BIT_INs.
+        if (!deviceSet.isEmpty() && deviceInChecksum != (deviceMask & DEVICE_BIT_IN)) {
+            Log.e(TAG, "getDeviceMaskFromSet: Invalid set: " + deviceSetToString(deviceSet));
+        }
+        return deviceMask;
+    }
+
+    /** @hide */
+    @NonNull
+    public static String deviceSetToString(@NonNull Set<Integer> devices) {
+        int n = 0;
+        StringBuilder sb = new StringBuilder();
+        for (Integer device : devices) {
+            if (n++ > 0) {
+                sb.append(", ");
+            }
+            sb.append(AudioSystem.getDeviceName(device));
+            sb.append("(" + Integer.toHexString(device) + ")");
+        }
+        return sb.toString();
+    }
 
     /**
      * @hide
@@ -2252,18 +2308,15 @@
 
     /**
      * @hide
-     * Return a set of audio device types from a bit mask audio device type, which may
+     * Return a set of audio device types from a list of audio device attributes, which may
      * represent multiple audio device types.
-     * FIXME: Remove this when getting ride of bit mask usage of audio device types.
      */
-    public static Set<Integer> generateAudioDeviceTypesSet(int types) {
-        Set<Integer> deviceTypes = new HashSet<>();
-        Set<Integer> allDeviceTypes =
-                (types & DEVICE_BIT_IN) == 0 ? DEVICE_OUT_ALL_SET : DEVICE_IN_ALL_SET;
-        for (int deviceType : allDeviceTypes) {
-            if ((types & deviceType) == deviceType) {
-                deviceTypes.add(deviceType);
-            }
+    @NonNull
+    public static Set<Integer> generateAudioDeviceTypesSet(
+            @NonNull List<AudioDeviceAttributes> deviceList) {
+        Set<Integer> deviceTypes = new TreeSet<>();
+        for (AudioDeviceAttributes device : deviceList) {
+            deviceTypes.add(device.getInternalType());
         }
         return deviceTypes;
     }
@@ -2274,7 +2327,7 @@
      */
     public static Set<Integer> intersectionAudioDeviceTypes(
             @NonNull Set<Integer> a, @NonNull Set<Integer> b) {
-        Set<Integer> intersection = new HashSet<>(a);
+        Set<Integer> intersection = new TreeSet<>(a);
         intersection.retainAll(b);
         return intersection;
     }
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index d702eb9..fa3057a 100755
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -360,7 +360,7 @@
 
     boolean isMusicActive(in boolean remotely);
 
-    int getDevicesForStream(in int streamType);
+    int getDeviceMaskForStream(in int streamType);
 
     int[] getAvailableCommunicationDeviceIds();
 
diff --git a/media/java/android/media/NearbyDevice.java b/media/java/android/media/NearbyDevice.java
index cb85109..c203e7b 100644
--- a/media/java/android/media/NearbyDevice.java
+++ b/media/java/android/media/NearbyDevice.java
@@ -81,6 +81,8 @@
 
     /**
      * Gets a human-readable string of the range zone.
+     *
+     * @hide
      */
     @NonNull
     public static String rangeZoneToString(@RangeZone int rangeZone) {
@@ -103,13 +105,14 @@
     @NonNull private final String mMediaRoute2Id;
     @RangeZone private final int mRangeZone;
 
-    public NearbyDevice(@NonNull String mediaRoute2Id, int rangeZone) {
+    /** Creates a device object with the given ID and range zone. */
+    public NearbyDevice(@NonNull String mediaRoute2Id, @RangeZone int rangeZone) {
         mMediaRoute2Id = mediaRoute2Id;
         mRangeZone = rangeZone;
     }
 
     private NearbyDevice(@NonNull Parcel in) {
-        mMediaRoute2Id = in.readString();
+        mMediaRoute2Id = in.readString8();
         mRangeZone = in.readInt();
     }
 
diff --git a/media/jni/soundpool/Stream.cpp b/media/jni/soundpool/Stream.cpp
index 50bb79c..9ed8770 100644
--- a/media/jni/soundpool/Stream.cpp
+++ b/media/jni/soundpool/Stream.cpp
@@ -15,6 +15,7 @@
  */
 
 //#define LOG_NDEBUG 0
+#include <utility>
 #define LOG_TAG "SoundPool::Stream"
 #include <utils/Log.h>
 #include <android/content/AttributionSourceState.h>
@@ -309,13 +310,11 @@
     }
     if (mAudioTrack == nullptr) {
         // mToggle toggles each time a track is started on a given stream.
-        // The toggle is concatenated with the Stream address and passed to AudioTrack
-        // as callback user data. This enables the detection of callbacks received from the old
+        // This enables the detection of callbacks received from the old
         // audio track while the new one is being started and avoids processing them with
         // wrong audio audio buffer size  (mAudioBufferSize)
         auto toggle = mToggle ^ 1;
         // NOLINTNEXTLINE(performance-no-int-to-ptr)
-        void* userData = reinterpret_cast<void*>((uintptr_t)this | toggle);
         audio_channel_mask_t soundChannelMask = sound->getChannelMask();
         // When sound contains a valid channel mask, use it as is.
         // Otherwise, use stream count to calculate channel mask.
@@ -327,10 +326,11 @@
         android::content::AttributionSourceState attributionSource;
         attributionSource.packageName = mStreamManager->getOpPackageName();
         attributionSource.token = sp<BBinder>::make();
+        mCallback =  sp<StreamCallback>::make(this, toggle),
         // TODO b/182469354 make consistent with AudioRecord, add util for native source
         mAudioTrack = new AudioTrack(streamType, sampleRate, sound->getFormat(),
                 channelMask, sound->getIMemory(), AUDIO_OUTPUT_FLAG_FAST,
-                staticCallback, userData,
+                mCallback,
                 0 /*default notification frames*/, AUDIO_SESSION_ALLOCATE,
                 AudioTrack::TRANSFER_DEFAULT,
                 nullptr /*offloadInfo*/, attributionSource,
@@ -375,16 +375,55 @@
     mStreamID = nextStreamID;  // prefer this to be the last, as it is an atomic sync point
 }
 
-/* static */
-void Stream::staticCallback(int event, void* user, void* info)
-{
-    const auto userAsInt = (uintptr_t)user;
-    // NOLINTNEXTLINE(performance-no-int-to-ptr)
-    auto stream = reinterpret_cast<Stream*>(userAsInt & ~1);
-    stream->callback(event, info, int(userAsInt & 1), 0 /* tries */);
+int Stream::getCorrespondingStreamID() {
+    std::lock_guard lock(mLock);
+    return static_cast<int>(mAudioTrack ? mStreamID : getPairStream()->mStreamID);
+}
+size_t Stream::StreamCallback::onMoreData(const AudioTrack::Buffer&) {
+    ALOGW("%s streamID %d Unexpected EVENT_MORE_DATA for static track",
+            __func__, mStream->getCorrespondingStreamID());
+    return 0;
 }
 
-void Stream::callback(int event, void* info, int toggle, int tries)
+void Stream::StreamCallback::onUnderrun() {
+    ALOGW("%s streamID %d Unexpected EVENT_UNDERRUN for static track",
+            __func__, mStream->getCorrespondingStreamID());
+}
+
+void Stream::StreamCallback::onLoopEnd(int32_t) {
+    ALOGV("%s streamID %d EVENT_LOOP_END", __func__, mStream->getCorrespondingStreamID());
+}
+
+void Stream::StreamCallback::onMarker(uint32_t) {
+    ALOGW("%s streamID %d Unexpected EVENT_MARKER for static track",
+            __func__, mStream->getCorrespondingStreamID());
+}
+
+void Stream::StreamCallback::onNewPos(uint32_t) {
+    ALOGW("%s streamID %d Unexpected EVENT_NEW_POS for static track",
+            __func__, mStream->getCorrespondingStreamID());
+}
+
+void Stream::StreamCallback::onBufferEnd() {
+    mStream->onBufferEnd(mToggle, 0);
+}
+
+void Stream::StreamCallback::onNewIAudioTrack() {
+    ALOGV("%s streamID %d NEW_IAUDIOTRACK", __func__, mStream->getCorrespondingStreamID());
+}
+
+void Stream::StreamCallback::onStreamEnd() {
+    ALOGW("%s streamID %d Unexpected EVENT_STREAM_END for static track",
+            __func__, mStream->getCorrespondingStreamID());
+}
+
+size_t Stream::StreamCallback::onCanWriteMoreData(const AudioTrack::Buffer&) {
+    ALOGW("%s streamID %d Unexpected EVENT_CAN_WRITE_MORE_DATA for static track",
+            __func__, mStream->getCorrespondingStreamID());
+    return 0;
+}
+
+void Stream::onBufferEnd(int toggle, int tries)
 {
     int32_t activeStreamIDToRestart = 0;
     {
@@ -400,7 +439,7 @@
             if (tries < 3) {
                 lock.unlock();
                 ALOGV("%s streamID %d going to pair stream", __func__, (int)mStreamID);
-                getPairStream()->callback(event, info, toggle, tries + 1);
+                getPairStream()->onBufferEnd(toggle, tries + 1);
             } else {
                 ALOGW("%s streamID %d cannot find track", __func__, (int)mStreamID);
             }
@@ -410,31 +449,10 @@
             ALOGD("%s streamID %d wrong toggle", __func__, (int)mStreamID);
             return;
         }
-        switch (event) {
-        case AudioTrack::EVENT_MORE_DATA:
-            ALOGW("%s streamID %d Invalid EVENT_MORE_DATA for static track",
-                    __func__, (int)mStreamID);
-            break;
-        case AudioTrack::EVENT_UNDERRUN:
-            ALOGW("%s streamID %d Invalid EVENT_UNDERRUN for static track",
-                    __func__, (int)mStreamID);
-            break;
-        case AudioTrack::EVENT_BUFFER_END:
-            ALOGV("%s streamID %d EVENT_BUFFER_END", __func__, (int)mStreamID);
-            if (mState != IDLE) {
-                activeStreamIDToRestart = mStreamID;
-                mStopTimeNs = systemTime();
-            }
-            break;
-        case AudioTrack::EVENT_LOOP_END:
-            ALOGV("%s streamID %d EVENT_LOOP_END", __func__, (int)mStreamID);
-            break;
-        case AudioTrack::EVENT_NEW_IAUDIOTRACK:
-            ALOGV("%s streamID %d NEW_IAUDIOTRACK", __func__, (int)mStreamID);
-            break;
-        default:
-            ALOGW("%s streamID %d Invalid event %d", __func__, (int)mStreamID, event);
-            break;
+        ALOGV("%s streamID %d EVENT_BUFFER_END", __func__, (int)mStreamID);
+        if (mState != IDLE) {
+            activeStreamIDToRestart = mStreamID;
+            mStopTimeNs = systemTime();
         }
     } // lock ends here.  This is on the callback thread, no need to be precise.
     if (activeStreamIDToRestart > 0) {
diff --git a/media/jni/soundpool/Stream.h b/media/jni/soundpool/Stream.h
index aa0eef5..0054eec 100644
--- a/media/jni/soundpool/Stream.h
+++ b/media/jni/soundpool/Stream.h
@@ -124,6 +124,35 @@
     // This never changes.  See top of header.
     Stream* getPairStream() const;
 
+    // Stream ID of ourselves, or the pair depending on who holds the AudioTrack
+    int getCorrespondingStreamID();
+
+protected:
+    // AudioTrack callback interface implementation
+    class StreamCallback : public AudioTrack::IAudioTrackCallback {
+      public:
+        StreamCallback(Stream * stream, bool toggle) : mStream(stream), mToggle(toggle) {}
+        size_t onMoreData(const AudioTrack::Buffer& buffer) override;
+        void onUnderrun() override;
+        void onLoopEnd(int32_t loopsRemaining) override;
+        void onMarker(uint32_t markerPosition) override;
+        void onNewPos(uint32_t newPos) override;
+        void onBufferEnd() override;
+        void onNewIAudioTrack() override;
+        void onStreamEnd() override;
+        size_t onCanWriteMoreData(const AudioTrack::Buffer& buffer) override;
+
+        // Holding a raw ptr is technically unsafe, but, Stream objects persist
+        // through the lifetime of the StreamManager through the use of a
+        // unique_ptr<Stream[]>. Ensuring lifetime will cause us to give up
+        // locality as well as pay RefBase/sp performance cost, which we are
+        // unwilling to do. Non-owning refs to unique_ptrs are idiomatically raw
+        // ptrs, as below.
+        Stream * const mStream;
+        const bool mToggle;
+    };
+
+    sp<StreamCallback> mCallback;
 private:
     // garbage is used to release tracks and data outside of any lock.
     void play_l(const std::shared_ptr<Sound>& sound, int streamID,
@@ -133,9 +162,7 @@
     void setVolume_l(float leftVolume, float rightVolume) REQUIRES(mLock);
 
     // For use with AudioTrack callback.
-    static void staticCallback(int event, void* user, void* info);
-    void callback(int event, void* info, int toggle, int tries)
-            NO_THREAD_SAFETY_ANALYSIS; // uses unique_lock
+    void onBufferEnd(int toggle, int tries) NO_THREAD_SAFETY_ANALYSIS;
 
     // StreamManager should be set on construction and not changed.
     // release mLock before calling into StreamManager
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/BluetoothProfileConnectionInfoTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/BluetoothProfileConnectionInfoTest.java
deleted file mode 100644
index f23794b..0000000
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/BluetoothProfileConnectionInfoTest.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2021 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.mediaframeworktest.unit;
-
-import static org.junit.Assert.assertEquals;
-
-import android.bluetooth.BluetoothProfile;
-import android.media.BluetoothProfileConnectionInfo;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-public class BluetoothProfileConnectionInfoTest {
-
-    @Test
-    public void testCoverageA2dp() {
-        final boolean supprNoisy = false;
-        final int volume = 42;
-        final BluetoothProfileConnectionInfo info = BluetoothProfileConnectionInfo
-                .createA2dpInfo(supprNoisy, volume);
-        assertEquals(info.getProfile(), BluetoothProfile.A2DP);
-        assertEquals(info.isSuppressNoisyIntent(), supprNoisy);
-        assertEquals(info.getVolume(), volume);
-    }
-
-    @Test
-    public void testCoverageA2dpSink() {
-        final int volume = 42;
-        final BluetoothProfileConnectionInfo info = BluetoothProfileConnectionInfo
-                .createA2dpSinkInfo(volume);
-        assertEquals(info.getProfile(), BluetoothProfile.A2DP_SINK);
-        assertEquals(info.getVolume(), volume);
-    }
-
-    @Test
-    public void testCoveragehearingAid() {
-        final boolean supprNoisy = true;
-        final BluetoothProfileConnectionInfo info = BluetoothProfileConnectionInfo
-                .createHearingAidInfo(supprNoisy);
-        assertEquals(info.getProfile(), BluetoothProfile.HEARING_AID);
-        assertEquals(info.isSuppressNoisyIntent(), supprNoisy);
-    }
-
-    @Test
-    public void testCoverageLeAudio() {
-        final boolean supprNoisy = false;
-        final boolean isLeOutput = true;
-        final BluetoothProfileConnectionInfo info = BluetoothProfileConnectionInfo
-                .createLeAudioInfo(supprNoisy, isLeOutput);
-        assertEquals(info.getProfile(), BluetoothProfile.LE_AUDIO);
-        assertEquals(info.isSuppressNoisyIntent(), supprNoisy);
-        assertEquals(info.isLeOutput(), isLeOutput);
-    }
-}
-
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index 0c36051..65428de 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -48,6 +48,7 @@
     static APerformanceHintManager* create(sp<IHintManager> iHintManager);
 
     sp<IHintManager> mHintManager;
+    const sp<IBinder> mToken = sp<BBinder>::make();
     const int64_t mPreferredRateNanos;
 };
 
@@ -119,11 +120,10 @@
 
 APerformanceHintSession* APerformanceHintManager::createSession(
         const int32_t* threadIds, size_t size, int64_t initialTargetWorkDurationNanos) {
-    sp<IBinder> token = sp<BBinder>::make();
     std::vector<int32_t> tids(threadIds, threadIds + size);
     sp<IHintSession> session;
     binder::Status ret =
-            mHintManager->createHintSession(token, tids, initialTargetWorkDurationNanos, &session);
+            mHintManager->createHintSession(mToken, tids, initialTargetWorkDurationNanos, &session);
     if (!ret.isOk() || !session) {
         return nullptr;
     }
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index 7a5ea47..1b7298a 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -19,6 +19,7 @@
         "androidx.appcompat_appcompat",
         "androidx.lifecycle_lifecycle-runtime",
         "androidx.mediarouter_mediarouter-nodeps",
+        "com.google.android.material_material",
         "iconloader",
 
         "WifiTrackerLibRes",
diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml
index 77c4533..5e3907c 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml
@@ -31,7 +31,7 @@
     <!-- Dialog accent color -->
     <color name="settingslib_dialog_accent">@android:color/system_accent1_100</color>
     <!-- Dialog background color. -->
-    <color name="settingslib_dialog_background">@android:color/system_neutral1_800</color>
+    <color name="settingslib_dialog_background">@color/settingslib_surface_dark</color>
     <!-- Dialog error color. -->
     <color name="settingslib_dialog_colorError">#f28b82</color> <!-- Red 300 -->
 
@@ -49,4 +49,8 @@
     <color name="settingslib_text_color_preference_category_title">@android:color/system_accent1_100</color>
 
     <color name="settingslib_ripple_color">@color/settingslib_material_grey_900</color>
+
+    <color name="settingslib_surface_dark">@android:color/system_neutral1_800</color>
+
+    <color name="settingslib_colorSurface">@color/settingslib_surface_dark</color>
 </resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
index 6adb789..c4dbc5e 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
@@ -71,5 +71,12 @@
     <color name="settingslib_text_color_preference_category_title">@android:color/system_accent1_600</color>
 
     <color name="settingslib_ripple_color">?android:attr/colorControlHighlight</color>
+
     <color name="settingslib_material_grey_900">#ff212121</color>
+
+    <color name="settingslib_colorAccentPrimary">@color/settingslib_accent_primary_device_default</color>
+
+    <color name="settingslib_colorAccentSecondary">@color/settingslib_accent_secondary_device_default</color>
+
+    <color name="settingslib_colorSurface">@color/settingslib_surface_light</color>
 </resources>
diff --git a/packages/SettingsLib/res/color-night-v31/settingslib_tabs_indicator_color.xml b/packages/SettingsLib/res/color-night-v31/settingslib_tabs_indicator_color.xml
new file mode 100644
index 0000000..9a09360
--- /dev/null
+++ b/packages/SettingsLib/res/color-night-v31/settingslib_tabs_indicator_color.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2022 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="@color/settingslib_colorAccentSecondary" />
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/color-night-v31/settingslib_tabs_text_color.xml b/packages/SettingsLib/res/color-night-v31/settingslib_tabs_text_color.xml
new file mode 100644
index 0000000..33f96df
--- /dev/null
+++ b/packages/SettingsLib/res/color-night-v31/settingslib_tabs_text_color.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2022 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_selected="true" android:color="?android:attr/textColorPrimaryInverse"/>
+    <item android:state_enabled="false" android:color="?android:attr/textColorTertiary"/>
+    <item android:color="?android:attr/textColorSecondary"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/color-v31/settingslib_tabs_indicator_color.xml b/packages/SettingsLib/res/color-v31/settingslib_tabs_indicator_color.xml
new file mode 100644
index 0000000..57fef52f
--- /dev/null
+++ b/packages/SettingsLib/res/color-v31/settingslib_tabs_indicator_color.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2022 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="@color/settingslib_colorAccentPrimary" />
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/color-v31/settingslib_tabs_text_color.xml b/packages/SettingsLib/res/color-v31/settingslib_tabs_text_color.xml
new file mode 100644
index 0000000..df2346d
--- /dev/null
+++ b/packages/SettingsLib/res/color-v31/settingslib_tabs_text_color.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2022 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_selected="true" android:color="?android:attr/textColorPrimary"/>
+    <item android:state_enabled="false" android:color="?android:attr/textColorTertiary"/>
+    <item android:color="?android:attr/textColorSecondary"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/drawable-v31/settingslib_tabs_background.xml b/packages/SettingsLib/res/drawable-v31/settingslib_tabs_background.xml
new file mode 100644
index 0000000..f10b563
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-v31/settingslib_tabs_background.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2022 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.
+-->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+        android:color="?android:colorControlHighlight">
+    <item android:id="@android:id/mask">
+        <inset
+            android:insetLeft="4dp"
+            android:insetRight="4dp">
+            <shape android:shape="rectangle">
+                <corners android:radius="12dp" />
+                <solid android:color="?android:colorControlHighlight" />
+            </shape>
+        </inset>
+    </item>
+
+    <item android:id="@android:id/background">
+        <inset
+               android:insetLeft="4dp"
+               android:insetRight="4dp">
+            <shape android:shape="rectangle">
+                <solid android:color="@color/settingslib_colorSurface" />
+                <corners android:radius="12dp" />
+            </shape>
+        </inset>
+    </item>
+</ripple>
diff --git a/packages/SettingsLib/res/drawable-v31/settingslib_tabs_indicator_background.xml b/packages/SettingsLib/res/drawable-v31/settingslib_tabs_indicator_background.xml
new file mode 100644
index 0000000..f5a9782
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-v31/settingslib_tabs_indicator_background.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2022 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.
+-->
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+       android:insetLeft="4dp"
+       android:insetRight="4dp">
+    <shape android:shape="rectangle">
+        <solid android:color="@color/settingslib_tabs_indicator_color" />
+        <corners android:radius="12dp" />
+    </shape>
+</inset>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values-v31/styles.xml b/packages/SettingsLib/res/values-v31/styles.xml
new file mode 100644
index 0000000..343de2c
--- /dev/null
+++ b/packages/SettingsLib/res/values-v31/styles.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2022 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.
+  -->
+<resources>
+    <style name="SettingsLibTabsTextAppearance" parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title">
+        <item name="android:textSize">16sp</item>
+    </style>
+
+    <style name="SettingsLibTabsStyle" parent="Base.Widget.Design.TabLayout">
+        <item name="android:background">@android:color/transparent</item>
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">48dp</item>
+        <item name="android:layout_marginStart">?android:attr/listPreferredItemPaddingStart</item>
+        <item name="android:layout_marginEnd">?android:attr/listPreferredItemPaddingEnd</item>
+        <item name="tabGravity">fill</item>
+        <item name="tabBackground">@drawable/settingslib_tabs_background</item>
+        <item name="tabIndicator">@drawable/settingslib_tabs_indicator_background</item>
+        <item name="tabIndicatorColor">@color/settingslib_tabs_indicator_color</item>
+        <item name="tabIndicatorFullWidth">true</item>
+        <item name="tabIndicatorGravity">stretch</item>
+        <item name="tabIndicatorAnimationMode">fade</item>
+        <item name="tabIndicatorAnimationDuration">0</item>
+        <item name="tabTextAppearance">@style/SettingsLibTabsTextAppearance</item>
+        <item name="tabTextColor">@color/settingslib_tabs_text_color</item>
+    </style>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/AvatarPhotoController.java b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPhotoController.java
index 61b8911..0cb2c0b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/AvatarPhotoController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPhotoController.java
@@ -21,7 +21,8 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
-import android.database.Cursor;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.Canvas;
@@ -30,15 +31,15 @@
 import android.graphics.RectF;
 import android.media.ExifInterface;
 import android.net.Uri;
-import android.os.AsyncTask;
 import android.os.StrictMode;
-import android.provider.ContactsContract;
 import android.provider.MediaStore;
 import android.util.EventLog;
 import android.util.Log;
 
 import androidx.core.content.FileProvider;
 
+import com.android.settingslib.utils.ThreadUtils;
+
 import libcore.io.Streams;
 
 import java.io.File;
@@ -47,39 +48,64 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.concurrent.ExecutionException;
 
 class AvatarPhotoController {
+
+    interface AvatarUi {
+        boolean isFinishing();
+
+        void returnUriResult(Uri uri);
+
+        void startActivityForResult(Intent intent, int resultCode);
+
+        boolean startSystemActivityForResult(Intent intent, int resultCode);
+
+        int getPhotoSize();
+    }
+
+    interface ContextInjector {
+        File getCacheDir();
+
+        Uri createTempImageUri(File parentDir, String fileName, boolean purge);
+
+        ContentResolver getContentResolver();
+    }
+
     private static final String TAG = "AvatarPhotoController";
 
-    private static final int REQUEST_CODE_CHOOSE_PHOTO = 1001;
-    private static final int REQUEST_CODE_TAKE_PHOTO = 1002;
-    private static final int REQUEST_CODE_CROP_PHOTO = 1003;
-    // in rare cases we get a null Cursor when querying for DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI
-    // so we need a default photo size
-    private static final int DEFAULT_PHOTO_SIZE = 500;
+    static final int REQUEST_CODE_CHOOSE_PHOTO = 1001;
+    static final int REQUEST_CODE_TAKE_PHOTO = 1002;
+    static final int REQUEST_CODE_CROP_PHOTO = 1003;
 
     private static final String IMAGES_DIR = "multi_user";
+    private static final String PRE_CROP_PICTURE_FILE_NAME = "PreCropEditUserPhoto.jpg";
     private static final String CROP_PICTURE_FILE_NAME = "CropEditUserPhoto.jpg";
     private static final String TAKE_PICTURE_FILE_NAME = "TakeEditUserPhoto.jpg";
 
     private final int mPhotoSize;
 
-    private final AvatarPickerActivity mActivity;
-    private final String mFileAuthority;
+    private final AvatarUi mAvatarUi;
+    private final ContextInjector mContextInjector;
 
     private final File mImagesDir;
+    private final Uri mPreCropPictureUri;
     private final Uri mCropPictureUri;
     private final Uri mTakePictureUri;
 
-    AvatarPhotoController(AvatarPickerActivity activity, boolean waiting, String fileAuthority) {
-        mActivity = activity;
-        mFileAuthority = fileAuthority;
+    AvatarPhotoController(AvatarUi avatarUi, ContextInjector contextInjector, boolean waiting) {
+        mAvatarUi = avatarUi;
+        mContextInjector = contextInjector;
 
-        mImagesDir = new File(activity.getCacheDir(), IMAGES_DIR);
+        mImagesDir = new File(mContextInjector.getCacheDir(), IMAGES_DIR);
         mImagesDir.mkdir();
-        mCropPictureUri = createTempImageUri(activity, CROP_PICTURE_FILE_NAME, !waiting);
-        mTakePictureUri = createTempImageUri(activity, TAKE_PICTURE_FILE_NAME, !waiting);
-        mPhotoSize = getPhotoSize(activity);
+        mPreCropPictureUri = mContextInjector
+                .createTempImageUri(mImagesDir, PRE_CROP_PICTURE_FILE_NAME, !waiting);
+        mCropPictureUri =
+                mContextInjector.createTempImageUri(mImagesDir, CROP_PICTURE_FILE_NAME, !waiting);
+        mTakePictureUri =
+                mContextInjector.createTempImageUri(mImagesDir, TAKE_PICTURE_FILE_NAME, !waiting);
+        mPhotoSize = mAvatarUi.getPhotoSize();
     }
 
     /**
@@ -102,16 +128,12 @@
 
         switch (requestCode) {
             case REQUEST_CODE_CROP_PHOTO:
-                mActivity.returnUriResult(pictureUri);
+                mAvatarUi.returnUriResult(pictureUri);
                 return true;
             case REQUEST_CODE_TAKE_PHOTO:
             case REQUEST_CODE_CHOOSE_PHOTO:
                 if (mTakePictureUri.equals(pictureUri)) {
-                    if (PhotoCapabilityUtils.canCropPhoto(mActivity)) {
-                        cropPhoto();
-                    } else {
-                        onPhotoNotCropped(pictureUri);
-                    }
+                    cropPhoto(pictureUri);
                 } else {
                     copyAndCropPhoto(pictureUri);
                 }
@@ -123,55 +145,52 @@
     void takePhoto() {
         Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE_SECURE);
         appendOutputExtra(intent, mTakePictureUri);
-        mActivity.startActivityForResult(intent, REQUEST_CODE_TAKE_PHOTO);
+        mAvatarUi.startActivityForResult(intent, REQUEST_CODE_TAKE_PHOTO);
     }
 
     void choosePhoto() {
         Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES, null);
         intent.setType("image/*");
-        mActivity.startActivityForResult(intent, REQUEST_CODE_CHOOSE_PHOTO);
+        mAvatarUi.startActivityForResult(intent, REQUEST_CODE_CHOOSE_PHOTO);
     }
 
     private void copyAndCropPhoto(final Uri pictureUri) {
-        // TODO: Replace AsyncTask
-        new AsyncTask<Void, Void, Void>() {
-            @Override
-            protected Void doInBackground(Void... params) {
-                final ContentResolver cr = mActivity.getContentResolver();
+        try {
+            ThreadUtils.postOnBackgroundThread(() -> {
+                final ContentResolver cr = mContextInjector.getContentResolver();
                 try (InputStream in = cr.openInputStream(pictureUri);
-                     OutputStream out = cr.openOutputStream(mTakePictureUri)) {
+                        OutputStream out = cr.openOutputStream(mPreCropPictureUri)) {
                     Streams.copy(in, out);
                 } catch (IOException e) {
                     Log.w(TAG, "Failed to copy photo", e);
+                    return;
                 }
-                return null;
-            }
-
-            @Override
-            protected void onPostExecute(Void result) {
-                if (!mActivity.isFinishing() && !mActivity.isDestroyed()) {
-                    cropPhoto();
-                }
-            }
-        }.execute();
+                ThreadUtils.postOnMainThread(() -> {
+                    if (!mAvatarUi.isFinishing()) {
+                        cropPhoto(mPreCropPictureUri);
+                    }
+                });
+            }).get();
+        } catch (InterruptedException | ExecutionException e) {
+            Log.e(TAG, "Error performing copy-and-crop", e);
+        }
     }
 
-    private void cropPhoto() {
+    private void cropPhoto(final Uri pictureUri) {
         // TODO: Use a public intent, when there is one.
         Intent intent = new Intent("com.android.camera.action.CROP");
-        intent.setDataAndType(mTakePictureUri, "image/*");
+        intent.setDataAndType(pictureUri, "image/*");
         appendOutputExtra(intent, mCropPictureUri);
         appendCropExtras(intent);
-        if (intent.resolveActivity(mActivity.getPackageManager()) != null) {
-            try {
-                StrictMode.disableDeathOnFileUriExposure();
-                mActivity.startActivityForResult(intent, REQUEST_CODE_CROP_PHOTO);
-            } finally {
-                StrictMode.enableDeathOnFileUriExposure();
+        try {
+            StrictMode.disableDeathOnFileUriExposure();
+            if (mAvatarUi.startSystemActivityForResult(intent, REQUEST_CODE_CROP_PHOTO)) {
+                return;
             }
-        } else {
-            onPhotoNotCropped(mTakePictureUri);
+        } finally {
+            StrictMode.enableDeathOnFileUriExposure();
         }
+        onPhotoNotCropped(pictureUri);
     }
 
     private void appendOutputExtra(Intent intent, Uri pictureUri) {
@@ -192,24 +211,22 @@
     }
 
     private void onPhotoNotCropped(final Uri data) {
-        // TODO: Replace AsyncTask to avoid possible memory leaks and handle configuration change
-        new AsyncTask<Void, Void, Bitmap>() {
-            @Override
-            protected Bitmap doInBackground(Void... params) {
+        try {
+            ThreadUtils.postOnBackgroundThread(() -> {
                 // Scale and crop to a square aspect ratio
                 Bitmap croppedImage = Bitmap.createBitmap(mPhotoSize, mPhotoSize,
                         Bitmap.Config.ARGB_8888);
                 Canvas canvas = new Canvas(croppedImage);
                 Bitmap fullImage;
                 try {
-                    InputStream imageStream = mActivity.getContentResolver()
+                    InputStream imageStream = mContextInjector.getContentResolver()
                             .openInputStream(data);
                     fullImage = BitmapFactory.decodeStream(imageStream);
                 } catch (FileNotFoundException fe) {
-                    return null;
+                    return;
                 }
                 if (fullImage != null) {
-                    int rotation = getRotation(mActivity, data);
+                    int rotation = getRotation(data);
                     final int squareSize = Math.min(fullImage.getWidth(),
                             fullImage.getHeight());
                     final int left = (fullImage.getWidth() - squareSize) / 2;
@@ -222,29 +239,27 @@
                     matrix.setRectToRect(rectSource, rectDest, Matrix.ScaleToFit.CENTER);
                     matrix.postRotate(rotation, mPhotoSize / 2f, mPhotoSize / 2f);
                     canvas.drawBitmap(fullImage, matrix, new Paint());
-                    return croppedImage;
-                } else {
-                    // Bah! Got nothin.
-                    return null;
-                }
-            }
+                    saveBitmapToFile(croppedImage, new File(mImagesDir, CROP_PICTURE_FILE_NAME));
 
-            @Override
-            protected void onPostExecute(Bitmap bitmap) {
-                saveBitmapToFile(bitmap, new File(mImagesDir, CROP_PICTURE_FILE_NAME));
-                mActivity.returnUriResult(mCropPictureUri);
-            }
-        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
+                    ThreadUtils.postOnMainThread(() -> {
+                        mAvatarUi.returnUriResult(mCropPictureUri);
+                    });
+                }
+            }).get();
+        } catch (InterruptedException | ExecutionException e) {
+            Log.e(TAG, "Error performing internal crop", e);
+        }
     }
 
     /**
      * Reads the image's exif data and determines the rotation degree needed to display the image
      * in portrait mode.
      */
-    private int getRotation(Context context, Uri selectedImage) {
+    private int getRotation(Uri selectedImage) {
         int rotation = -1;
         try {
-            InputStream imageStream = context.getContentResolver().openInputStream(selectedImage);
+            InputStream imageStream =
+                    mContextInjector.getContentResolver().openInputStream(selectedImage);
             ExifInterface exif = new ExifInterface(imageStream);
             rotation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1);
         } catch (IOException exception) {
@@ -274,24 +289,74 @@
         }
     }
 
-    private static int getPhotoSize(Context context) {
-        try (Cursor cursor = context.getContentResolver().query(
-                ContactsContract.DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI,
-                new String[]{ContactsContract.DisplayPhoto.DISPLAY_MAX_DIM}, null, null, null)) {
-            if (cursor != null) {
-                cursor.moveToFirst();
-                return cursor.getInt(0);
-            } else {
-                return DEFAULT_PHOTO_SIZE;
+    static class AvatarUiImpl implements AvatarUi {
+        private final AvatarPickerActivity mActivity;
+
+        AvatarUiImpl(AvatarPickerActivity activity) {
+            mActivity = activity;
+        }
+
+        @Override
+        public boolean isFinishing() {
+            return mActivity.isFinishing() || mActivity.isDestroyed();
+        }
+
+        @Override
+        public void returnUriResult(Uri uri) {
+            mActivity.returnUriResult(uri);
+        }
+
+        @Override
+        public void startActivityForResult(Intent intent, int resultCode) {
+            mActivity.startActivityForResult(intent, resultCode);
+        }
+
+        @Override
+        public boolean startSystemActivityForResult(Intent intent, int code) {
+            ActivityInfo info = intent.resolveActivityInfo(mActivity.getPackageManager(),
+                    PackageManager.MATCH_SYSTEM_ONLY);
+            if (info == null) {
+                Log.w(TAG, "No system package activity could be found for code " + code);
+                return false;
             }
+            intent.setPackage(info.packageName);
+            mActivity.startActivityForResult(intent, code);
+            return true;
+        }
+
+        @Override
+        public int getPhotoSize() {
+            return mActivity.getResources()
+                    .getDimensionPixelSize(com.android.internal.R.dimen.user_icon_size);
         }
     }
 
-    private Uri createTempImageUri(Context context, String fileName, boolean purge) {
-        final File fullPath = new File(mImagesDir, fileName);
-        if (purge) {
-            fullPath.delete();
+    static class ContextInjectorImpl implements ContextInjector {
+        private final Context mContext;
+        private final String mFileAuthority;
+
+        ContextInjectorImpl(Context context, String fileAuthority) {
+            mContext = context;
+            mFileAuthority = fileAuthority;
         }
-        return FileProvider.getUriForFile(context, mFileAuthority, fullPath);
+
+        @Override
+        public File getCacheDir() {
+            return mContext.getCacheDir();
+        }
+
+        @Override
+        public Uri createTempImageUri(File parentDir, String fileName, boolean purge) {
+            final File fullPath = new File(parentDir, fileName);
+            if (purge) {
+                fullPath.delete();
+            }
+            return FileProvider.getUriForFile(mContext, mFileAuthority, fullPath);
+        }
+
+        @Override
+        public ContentResolver getContentResolver() {
+            return mContext.getContentResolver();
+        }
     }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java
index 1e1dfae..75bb70a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java
@@ -95,7 +95,9 @@
         restoreState(savedInstanceState);
 
         mAvatarPhotoController = new AvatarPhotoController(
-                this, mWaitingForActivityResult, getFileAuthority());
+                new AvatarPhotoController.AvatarUiImpl(this),
+                new AvatarPhotoController.ContextInjectorImpl(this, getFileAuthority()),
+                mWaitingForActivityResult);
     }
 
     private void setUpButtons() {
diff --git a/packages/SettingsLib/tests/integ/AndroidManifest.xml b/packages/SettingsLib/tests/integ/AndroidManifest.xml
index da808dd..2a4dfdd 100644
--- a/packages/SettingsLib/tests/integ/AndroidManifest.xml
+++ b/packages/SettingsLib/tests/integ/AndroidManifest.xml
@@ -25,10 +25,19 @@
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
 
-
     <application>
         <uses-library android:name="android.test.runner" />
         <activity android:name=".drawer.SettingsDrawerActivityTest$TestActivity"/>
+
+        <provider
+            android:name="androidx.core.content.FileProvider"
+            android:authorities="com.android.settingslib.test"
+            android:grantUriPermissions="true"
+            android:exported="false">
+            <meta-data
+                android:name="android.support.FILE_PROVIDER_PATHS"
+                android:resource="@xml/file_paths" />
+        </provider>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/packages/SettingsLib/tests/integ/res/xml/file_paths.xml b/packages/SettingsLib/tests/integ/res/xml/file_paths.xml
new file mode 100644
index 0000000..ccd11a4
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/res/xml/file_paths.xml
@@ -0,0 +1,20 @@
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+
+<paths xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- Offer access to files under Context.getCacheDir() -->
+    <cache-path name="my_cache" />
+</paths>
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AvatarPhotoControllerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AvatarPhotoControllerTest.java
new file mode 100644
index 0000000..9ebdba3
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AvatarPhotoControllerTest.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2022 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.settingslib.users;
+
+import static com.android.settingslib.users.AvatarPhotoController.REQUEST_CODE_CHOOSE_PHOTO;
+import static com.android.settingslib.users.AvatarPhotoController.REQUEST_CODE_CROP_PHOTO;
+import static com.android.settingslib.users.AvatarPhotoController.REQUEST_CODE_TAKE_PHOTO;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.provider.MediaStore;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+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;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+@RunWith(AndroidJUnit4.class)
+public class AvatarPhotoControllerTest {
+
+    private static final long TIMEOUT_MILLIS = 5000;
+    private static final int PHOTO_SIZE = 200;
+
+    @Mock AvatarPhotoController.AvatarUi mMockAvatarUi;
+
+    private File mImagesDir;
+    private AvatarPhotoController mController;
+    private Uri mTakePhotoUri = Uri.parse(
+            "content://com.android.settingslib.test/my_cache/multi_user/TakeEditUserPhoto.jpg");
+    private Uri mCropPhotoUri = Uri.parse(
+            "content://com.android.settingslib.test/my_cache/multi_user/CropEditUserPhoto.jpg");
+    private Context mContext = InstrumentationRegistry.getTargetContext();
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mMockAvatarUi.getPhotoSize()).thenReturn(PHOTO_SIZE);
+        when(mMockAvatarUi.startSystemActivityForResult(any(), anyInt())).thenReturn(true);
+
+        mImagesDir = new File(
+                InstrumentationRegistry.getTargetContext().getCacheDir(), "multi_user");
+        mImagesDir.mkdir();
+
+        AvatarPhotoController.ContextInjector contextInjector =
+                new AvatarPhotoController.ContextInjectorImpl(
+                        InstrumentationRegistry.getTargetContext(), "com.android.settingslib.test");
+        mController = new AvatarPhotoController(mMockAvatarUi, contextInjector, false);
+    }
+
+    @After
+    public void tearDown() {
+        mImagesDir.delete();
+    }
+
+    @Test
+    public void takePhotoHasCorrectIntentAndResultCode() {
+        mController.takePhoto();
+
+        verifyStartActivityForResult(
+                MediaStore.ACTION_IMAGE_CAPTURE_SECURE, REQUEST_CODE_TAKE_PHOTO);
+    }
+
+    @Test
+    public void choosePhotoHasCorrectIntentAndResultCode() {
+        mController.choosePhoto();
+
+        verifyStartActivityForResult(
+                MediaStore.ACTION_PICK_IMAGES, REQUEST_CODE_CHOOSE_PHOTO);
+    }
+
+    @Test
+    public void takePhotoIsFollowedByCrop() throws IOException {
+        new File(mImagesDir, "file.txt").createNewFile();
+
+        Intent intent = new Intent();
+        intent.setData(Uri.parse(
+                "content://com.android.settingslib.test/my_cache/multi_user/file.txt"));
+        mController.onActivityResult(
+                REQUEST_CODE_TAKE_PHOTO, Activity.RESULT_OK, intent);
+
+        verifyStartSystemActivityForResult(
+                "com.android.camera.action.CROP", REQUEST_CODE_CROP_PHOTO);
+    }
+
+    @Test
+    public void takePhotoIsNotFollowedByCropWhenResultCodeNotOk() throws IOException {
+        new File(mImagesDir, "file.txt").createNewFile();
+
+        Intent intent = new Intent();
+        intent.setData(Uri.parse(
+                "content://com.android.settingslib.test/my_cache/multi_user/file.txt"));
+        mController.onActivityResult(
+                REQUEST_CODE_TAKE_PHOTO, Activity.RESULT_CANCELED, intent);
+
+        verify(mMockAvatarUi, never()).startActivityForResult(any(), anyInt());
+        verify(mMockAvatarUi, never()).startSystemActivityForResult(any(), anyInt());
+    }
+
+    @Test
+    public void takePhotoIsFollowedByCropWhenTakePhotoUriReturned() throws IOException {
+        new File(mImagesDir, "TakeEditUserPhoto.jpg").createNewFile();
+
+        Intent intent = new Intent();
+        intent.setData(mTakePhotoUri);
+        mController.onActivityResult(
+                REQUEST_CODE_TAKE_PHOTO, Activity.RESULT_OK, intent);
+
+        verifyStartSystemActivityForResult(
+                "com.android.camera.action.CROP", REQUEST_CODE_CROP_PHOTO);
+    }
+
+    @Test
+    public void takePhotoIsNotFollowedByCropIntentWhenCropNotSupported() throws IOException {
+        when(mMockAvatarUi.startSystemActivityForResult(any(), anyInt())).thenReturn(false);
+
+        File file = new File(mImagesDir, "file.txt");
+        saveBitmapToFile(file);
+
+        Intent intent = new Intent();
+        intent.setData(Uri.parse(
+                "content://com.android.settingslib.test/my_cache/multi_user/file.txt"));
+        mController.onActivityResult(
+                REQUEST_CODE_TAKE_PHOTO, Activity.RESULT_OK, intent);
+
+        verify(mMockAvatarUi, never()).startActivityForResult(any(), anyInt());
+        verify(mMockAvatarUi, never()).startSystemActivityForResult(any(), anyInt());
+    }
+
+    @Test
+    public void choosePhotoIsFollowedByCrop() throws IOException {
+        new File(mImagesDir, "file.txt").createNewFile();
+
+        Intent intent = new Intent();
+        intent.setData(Uri.parse(
+                "content://com.android.settingslib.test/my_cache/multi_user/file.txt"));
+        mController.onActivityResult(
+                REQUEST_CODE_CHOOSE_PHOTO, Activity.RESULT_OK, intent);
+
+        verifyStartSystemActivityForResult(
+                "com.android.camera.action.CROP", REQUEST_CODE_CROP_PHOTO);
+    }
+
+    @Test
+    public void choosePhotoIsNotFollowedByCropWhenResultCodeNotOk() throws IOException {
+        new File(mImagesDir, "file.txt").createNewFile();
+
+        Intent intent = new Intent();
+        intent.setData(Uri.parse(
+                "content://com.android.settingslib.test/my_cache/multi_user/file.txt"));
+        mController.onActivityResult(
+                REQUEST_CODE_CHOOSE_PHOTO, Activity.RESULT_CANCELED, intent);
+
+        verify(mMockAvatarUi, never()).startActivityForResult(any(), anyInt());
+        verify(mMockAvatarUi, never()).startSystemActivityForResult(any(), anyInt());
+    }
+
+    @Test
+    public void choosePhotoIsFollowedByCropWhenTakePhotoUriReturned() throws IOException {
+        new File(mImagesDir, "TakeEditUserPhoto.jpg").createNewFile();
+
+        Intent intent = new Intent();
+        intent.setData(mTakePhotoUri);
+        mController.onActivityResult(
+                REQUEST_CODE_CHOOSE_PHOTO, Activity.RESULT_OK, intent);
+
+        verifyStartSystemActivityForResult(
+                "com.android.camera.action.CROP", REQUEST_CODE_CROP_PHOTO);
+    }
+
+    @Test
+    public void cropPhotoResultIsReturnedIfResultOkAndContent() {
+        Intent intent = new Intent();
+        intent.setData(mCropPhotoUri);
+        mController.onActivityResult(REQUEST_CODE_CROP_PHOTO, Activity.RESULT_OK, intent);
+        verify(mMockAvatarUi, timeout(TIMEOUT_MILLIS)).returnUriResult(mCropPhotoUri);
+    }
+
+    @Test
+    public void cropPhotoResultIsNotReturnedIfResultCancel() {
+        Intent intent = new Intent();
+        intent.setData(mCropPhotoUri);
+        mController.onActivityResult(REQUEST_CODE_CROP_PHOTO, Activity.RESULT_CANCELED, intent);
+        verify(mMockAvatarUi, timeout(TIMEOUT_MILLIS).times(0)).returnUriResult(mCropPhotoUri);
+    }
+
+    @Test
+    public void cropPhotoResultIsNotReturnedIfResultNotContent() {
+        Intent intent = new Intent();
+        intent.setData(Uri.parse("file://test"));
+        mController.onActivityResult(REQUEST_CODE_CROP_PHOTO, Activity.RESULT_OK, intent);
+        verify(mMockAvatarUi, timeout(TIMEOUT_MILLIS).times(0)).returnUriResult(mCropPhotoUri);
+    }
+
+    @Test
+    public void cropDoesNotUseTakePhotoUri() throws IOException {
+        new File(mImagesDir, "file.txt").createNewFile();
+
+        Intent intent = new Intent();
+        intent.setData(Uri.parse(
+                "content://com.android.settingslib.test/my_cache/multi_user/file.txt"));
+        mController.onActivityResult(
+                REQUEST_CODE_TAKE_PHOTO, Activity.RESULT_OK, intent);
+
+        Intent startIntent = verifyStartSystemActivityForResult(
+                "com.android.camera.action.CROP", REQUEST_CODE_CROP_PHOTO);
+        assertThat(startIntent.getData()).isNotEqualTo(mTakePhotoUri);
+    }
+
+    @Test
+    public void internalCropUsedIfNoSystemCropperFound() throws IOException {
+        when(mMockAvatarUi.startSystemActivityForResult(any(), anyInt())).thenReturn(false);
+
+        new File(mImagesDir, "file.txt").createNewFile();
+
+        Intent intent = new Intent();
+        intent.setData(Uri.parse(
+                "content://com.android.settingslib.test/my_cache/multi_user/file.txt"));
+        mController.onActivityResult(
+                REQUEST_CODE_TAKE_PHOTO, Activity.RESULT_OK, intent);
+
+        Intent startIntent = verifyStartSystemActivityForResult(
+                "com.android.camera.action.CROP", REQUEST_CODE_CROP_PHOTO);
+        assertThat(startIntent.getData()).isNotEqualTo(mTakePhotoUri);
+
+        verify(mMockAvatarUi, timeout(TIMEOUT_MILLIS)).returnUriResult(mCropPhotoUri);
+
+        InputStream imageStream = mContext.getContentResolver().openInputStream(mCropPhotoUri);
+        Bitmap bitmap = BitmapFactory.decodeStream(imageStream);
+        assertThat(bitmap.getWidth()).isEqualTo(PHOTO_SIZE);
+        assertThat(bitmap.getHeight()).isEqualTo(PHOTO_SIZE);
+    }
+
+    private Intent verifyStartActivityForResult(String action, int resultCode) {
+        ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+        verify(mMockAvatarUi, timeout(TIMEOUT_MILLIS))
+                .startActivityForResult(captor.capture(), eq(resultCode));
+        Intent intent = captor.getValue();
+        assertThat(intent.getAction()).isEqualTo(action);
+        return intent;
+    }
+
+    private Intent verifyStartSystemActivityForResult(String action, int resultCode) {
+        ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+        verify(mMockAvatarUi, timeout(TIMEOUT_MILLIS))
+                .startSystemActivityForResult(captor.capture(), eq(resultCode));
+        Intent intent = captor.getValue();
+        assertThat(intent.getAction()).isEqualTo(action);
+        return intent;
+    }
+
+    private void saveBitmapToFile(File file) throws IOException {
+        Bitmap bitmap = Bitmap.createBitmap(500, 500, Bitmap.Config.ARGB_8888);
+        OutputStream os = new FileOutputStream(file);
+        bitmap.compress(Bitmap.CompressFormat.PNG, 100, os);
+        os.flush();
+        os.close();
+    }
+
+}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 42aa205..fa3360c 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -231,6 +231,7 @@
         VALIDATORS.put(Secure.SKIP_DIRECTION, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.SILENCE_GESTURE, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES, JSON_OBJECT_VALIDATOR);
+        VALIDATORS.put(Secure.NAV_BAR_FORCE_VISIBLE, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.NAV_BAR_KIDS_MODE, BOOLEAN_VALIDATOR);
         VALIDATORS.put(
                 Secure.NAVIGATION_MODE, new DiscreteValueValidator(new String[] {"0", "1", "2"}));
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index a2b6992..1c5bf81 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -2253,8 +2253,11 @@
                 SecureSettingsProto.MULTI_PRESS_TIMEOUT);
 
         dumpSetting(s, p,
+                Settings.Secure.NAV_BAR_FORCE_VISIBLE,
+                SecureSettingsProto.NavBar.NAV_BAR_FORCE_VISIBLE);
+        dumpSetting(s, p,
                 Settings.Secure.NAV_BAR_KIDS_MODE,
-                SecureSettingsProto.NAV_BAR_KIDS_MODE);
+                SecureSettingsProto.NavBar.NAV_BAR_KIDS_MODE);
 
         dumpSetting(s, p,
                 Settings.Secure.NAVIGATION_MODE,
diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml
index 0ae5dc7..5084ca4 100644
--- a/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml
+++ b/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml
@@ -1,254 +1 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<!-- Copyright (C) 2021 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.
--->
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
-                 xmlns:aapt="http://schemas.android.com/aapt">
-    <aapt:attr name="android:drawable">
-        <vector android:height="60dp" android:width="60dp" android:viewportHeight="60"
-                android:viewportWidth="60">
-            <group android:name="_R_G">
-                <group android:name="_R_G_L_1_G" android:translateX="-0.05000000000000071">
-                    <group android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0" android:translateX="30"
-                           android:translateY="38.75" android:scaleX="1" android:scaleY="1">
-                        <path android:name="_R_G_L_1_G_D_0_P_0"
-                              android:fillColor="@color/biometric_dialog_error"
-                              android:fillAlpha="1" android:fillType="nonZero"
-                              android:pathData=" M-1.2 -1.25 C-1.2,-1.25 1.2,-1.25 1.2,-1.25 C1.2,-1.25 1.2,1.25 1.2,1.25 C1.2,1.25 -1.2,1.25 -1.2,1.25 C-1.2,1.25 -1.2,-1.25 -1.2,-1.25c "/>
-                    </group>
-                    <group android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0" android:translateX="30"
-                           android:translateY="25" android:pivotX="0.002" android:pivotY="7.488"
-                           android:scaleX="1" android:scaleY="1">
-                        <path android:name="_R_G_L_1_G_D_1_P_0"
-                              android:fillColor="@color/biometric_dialog_error"
-                              android:fillAlpha="1" android:fillType="nonZero"
-                              android:pathData=" M-1.2 -7.5 C-1.2,-7.5 1.2,-7.5 1.2,-7.5 C1.2,-7.5 1.2,7.5 1.2,7.5 C1.2,7.5 -1.2,7.5 -1.2,7.5 C-1.2,7.5 -1.2,-7.5 -1.2,-7.5c "/>
-                    </group>
-                    <path android:name="_R_G_L_1_G_D_2_P_0"
-                          android:strokeColor="@color/biometric_dialog_error"
-                          android:strokeLineCap="round" android:strokeLineJoin="round"
-                          android:strokeWidth="2.5" android:strokeAlpha="1"
-                          android:trimPathStart="0" android:trimPathEnd="1"
-                          android:trimPathOffset="0"
-                          android:pathData=" M30 6.2 C16.9,6.2 6.3,16.8 6.3,30 C6.3,43.2 16.9,53.8 30,53.8 C43.1,53.8 53.8,43.2 53.8,30 C53.8,16.8 43.1,6.2 30,6.2c "/>
-                </group>
-                <group android:name="_R_G_L_0_G" android:translateX="-10.325"
-                       android:translateY="-10.25">
-                    <path android:name="_R_G_L_0_G_D_0_P_0"
-                          android:strokeColor="@color/biometric_dialog_accent"
-                          android:strokeLineCap="round" android:strokeLineJoin="round"
-                          android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0"
-                          android:trimPathEnd="0" android:trimPathOffset="0"
-                          android:pathData=" M31.41 48.43 C30.78,46.69 30.78,44.91 30.78,44.91 C30.78,40.09 34.88,36.16 40.32,36.16 C45.77,36.16 49.87,40.09 49.87,44.91 C49.87,44.91 49.87,45.17 49.87,45.17 C49.87,46.97 48.41,48.43 46.61,48.43 C45.28,48.43 44.09,47.63 43.6,46.39 C43.6,46.39 42.51,43.66 42.51,43.66 C42.02,42.42 40.82,41.61 39.49,41.61 C37.69,41.61 36.23,43.07 36.23,44.87 C36.23,47.12 37.26,49.26 39.02,50.67 C39.02,50.67 39.64,51.16 39.64,51.16 "/>
-                    <path android:name="_R_G_L_0_G_D_1_P_0"
-                          android:strokeColor="@color/biometric_dialog_accent"
-                          android:strokeLineCap="round" android:strokeLineJoin="round"
-                          android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0"
-                          android:trimPathEnd="0" android:trimPathOffset="0"
-                          android:pathData=" M32.14 27.3 C34.5,26 37.31,25.25 40.33,25.25 C43.34,25.25 46.15,26 48.51,27.3 "/>
-                    <path android:name="_R_G_L_0_G_D_2_P_0"
-                          android:strokeColor="@color/biometric_dialog_accent"
-                          android:strokeLineCap="round" android:strokeLineJoin="round"
-                          android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0"
-                          android:trimPathEnd="0" android:trimPathOffset="0"
-                          android:pathData=" M29.42 36.16 C31.35,32.94 35.51,30.71 40.33,30.71 C45.14,30.71 49.3,32.94 51.23,36.16 "/>
-                    <path android:name="_R_G_L_0_G_D_3_P_0"
-                          android:strokeColor="@color/biometric_dialog_accent"
-                          android:strokeLineCap="round" android:strokeLineJoin="round"
-                          android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0"
-                          android:trimPathEnd="0" android:trimPathOffset="0"
-                          android:pathData=" M47.14 52.52 C45.33,54.21 42.94,55.25 40.33,55.25 C37.71,55.25 35.32,54.21 33.51,52.52 "/>
-                </group>
-            </group>
-            <group android:name="time_group"/>
-        </vector>
-    </aapt:attr>
-    <target android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0">
-        <aapt:attr name="android:animation">
-            <set android:ordering="together">
-                <objectAnimator android:propertyName="scaleX" android:duration="67"
-                                android:startOffset="0" android:valueFrom="1" android:valueTo="1.1"
-                                android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0"/>
-                    </aapt:attr>
-                </objectAnimator>
-                <objectAnimator android:propertyName="scaleY" android:duration="67"
-                                android:startOffset="0" android:valueFrom="1" android:valueTo="1.1"
-                                android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0"/>
-                    </aapt:attr>
-                </objectAnimator>
-                <objectAnimator android:propertyName="scaleX" android:duration="100"
-                                android:startOffset="67" android:valueFrom="1.1" android:valueTo="0"
-                                android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0"/>
-                    </aapt:attr>
-                </objectAnimator>
-                <objectAnimator android:propertyName="scaleY" android:duration="100"
-                                android:startOffset="67" android:valueFrom="1.1" android:valueTo="0"
-                                android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0"/>
-                    </aapt:attr>
-                </objectAnimator>
-            </set>
-        </aapt:attr>
-    </target>
-    <target android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0">
-        <aapt:attr name="android:animation">
-            <set android:ordering="together">
-                <objectAnimator android:propertyName="scaleX" android:duration="67"
-                                android:startOffset="0" android:valueFrom="1" android:valueTo="1"
-                                android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.659,1 1.0,1.0"/>
-                    </aapt:attr>
-                </objectAnimator>
-                <objectAnimator android:propertyName="scaleY" android:duration="67"
-                                android:startOffset="0" android:valueFrom="1" android:valueTo="1.1"
-                                android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,1 1.0,1.0"/>
-                    </aapt:attr>
-                </objectAnimator>
-                <objectAnimator android:propertyName="scaleX" android:duration="100"
-                                android:startOffset="67" android:valueFrom="1" android:valueTo="1"
-                                android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,1 1.0,1.0"/>
-                    </aapt:attr>
-                </objectAnimator>
-                <objectAnimator android:propertyName="scaleY" android:duration="100"
-                                android:startOffset="67" android:valueFrom="1.1" android:valueTo="0"
-                                android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.096 1.0,1.0"/>
-                    </aapt:attr>
-                </objectAnimator>
-            </set>
-        </aapt:attr>
-    </target>
-    <target android:name="_R_G_L_1_G_D_2_P_0">
-        <aapt:attr name="android:animation">
-            <set android:ordering="together">
-                <objectAnimator android:propertyName="trimPathEnd" android:duration="67"
-                                android:startOffset="0" android:valueFrom="1" android:valueTo="1"
-                                android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
-                    </aapt:attr>
-                </objectAnimator>
-                <objectAnimator android:propertyName="trimPathEnd" android:duration="133"
-                                android:startOffset="67" android:valueFrom="1" android:valueTo="0"
-                                android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
-                    </aapt:attr>
-                </objectAnimator>
-            </set>
-        </aapt:attr>
-    </target>
-    <target android:name="_R_G_L_0_G_D_0_P_0">
-        <aapt:attr name="android:animation">
-            <set android:ordering="together">
-                <objectAnimator android:propertyName="trimPathEnd" android:duration="83"
-                                android:startOffset="0" android:valueFrom="0" android:valueTo="0"
-                                android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
-                    </aapt:attr>
-                </objectAnimator>
-                <objectAnimator android:propertyName="trimPathEnd" android:duration="250"
-                                android:startOffset="83" android:valueFrom="0" android:valueTo="1"
-                                android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
-                    </aapt:attr>
-                </objectAnimator>
-            </set>
-        </aapt:attr>
-    </target>
-    <target android:name="_R_G_L_0_G_D_1_P_0">
-        <aapt:attr name="android:animation">
-            <set android:ordering="together">
-                <objectAnimator android:propertyName="trimPathEnd" android:duration="83"
-                                android:startOffset="0" android:valueFrom="0" android:valueTo="0"
-                                android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
-                    </aapt:attr>
-                </objectAnimator>
-                <objectAnimator android:propertyName="trimPathEnd" android:duration="250"
-                                android:startOffset="83" android:valueFrom="0" android:valueTo="1"
-                                android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
-                    </aapt:attr>
-                </objectAnimator>
-            </set>
-        </aapt:attr>
-    </target>
-    <target android:name="_R_G_L_0_G_D_2_P_0">
-        <aapt:attr name="android:animation">
-            <set android:ordering="together">
-                <objectAnimator android:propertyName="trimPathEnd" android:duration="83"
-                                android:startOffset="0" android:valueFrom="0" android:valueTo="0"
-                                android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
-                    </aapt:attr>
-                </objectAnimator>
-                <objectAnimator android:propertyName="trimPathEnd" android:duration="250"
-                                android:startOffset="83" android:valueFrom="0" android:valueTo="1"
-                                android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
-                    </aapt:attr>
-                </objectAnimator>
-            </set>
-        </aapt:attr>
-    </target>
-    <target android:name="_R_G_L_0_G_D_3_P_0">
-        <aapt:attr name="android:animation">
-            <set android:ordering="together">
-                <objectAnimator android:propertyName="trimPathEnd" android:duration="83"
-                                android:startOffset="0" android:valueFrom="0" android:valueTo="0"
-                                android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
-                    </aapt:attr>
-                </objectAnimator>
-                <objectAnimator android:propertyName="trimPathEnd" android:duration="250"
-                                android:startOffset="83" android:valueFrom="0" android:valueTo="1"
-                                android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
-                    </aapt:attr>
-                </objectAnimator>
-            </set>
-        </aapt:attr>
-    </target>
-    <target android:name="time_group">
-        <aapt:attr name="android:animation">
-            <set android:ordering="together">
-                <objectAnimator android:propertyName="translateX" android:duration="417"
-                                android:startOffset="0" android:valueFrom="0" android:valueTo="1"
-                                android:valueType="floatType"/>
-            </set>
-        </aapt:attr>
-    </target>
-</animated-vector>
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="80dp" android:width="80dp" android:viewportHeight="80" android:viewportWidth="80"><group android:name="_R_G"><group android:name="_R_G_L_3_G" android:translateX="-0.25" android:translateY="-0.25"><path android:name="_R_G_L_3_G_D_0_P_0" android:fillColor="#474747" android:fillAlpha="0" android:fillType="nonZero" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_2_G" android:translateX="-0.25" android:translateY="-0.25" android:pivotX="40.25" android:pivotY="40.25" android:scaleX="0.975" android:scaleY="0.975"><path android:name="_R_G_L_2_G_D_0_P_0" android:strokeColor="#f2b8b5" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2.5" android:strokeAlpha="1" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_1_G" android:translateX="9.950000000000003" android:translateY="10" android:pivotX="30" android:pivotY="30" android:scaleX="1.2" android:scaleY="1.2"><group android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0" android:translateX="30" android:translateY="38.75" android:scaleX="1" android:scaleY="1"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#f2b8b5" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-1.2 -1.25 C-1.2,-1.25 1.2,-1.25 1.2,-1.25 C1.2,-1.25 1.2,1.25 1.2,1.25 C1.2,1.25 -1.2,1.25 -1.2,1.25 C-1.2,1.25 -1.2,-1.25 -1.2,-1.25c "/></group><group android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0" android:translateX="30" android:translateY="25" android:pivotX="0.002" android:pivotY="7.488" android:scaleX="1" android:scaleY="1"><path android:name="_R_G_L_1_G_D_1_P_0" android:fillColor="#f2b8b5" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-1.2 -7.5 C-1.2,-7.5 1.2,-7.5 1.2,-7.5 C1.2,-7.5 1.2,7.5 1.2,7.5 C1.2,7.5 -1.2,7.5 -1.2,7.5 C-1.2,7.5 -1.2,-7.5 -1.2,-7.5c "/></group></group><group android:name="_R_G_L_0_G" android:translateX="20.659" android:translateY="15.75"><path android:name="_R_G_L_0_G_D_0_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="0" android:trimPathOffset="0" android:pathData=" M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 "/><path android:name="_R_G_L_0_G_D_1_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="0" android:trimPathOffset="0" android:pathData=" M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 "/><path android:name="_R_G_L_0_G_D_2_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="0" android:trimPathOffset="0" android:pathData=" M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 "/><path android:name="_R_G_L_0_G_D_3_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="0" android:trimPathOffset="0" android:pathData=" M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_3_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="fillAlpha" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="fillAlpha" android:duration="167" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeWidth" android:duration="167" android:startOffset="0" android:valueFrom="2.5" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeAlpha" android:duration="83" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="strokeAlpha" android:duration="83" android:startOffset="83" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="0.975" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_2_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="167" android:valueFrom="0.975" android:valueTo="0" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="100" android:startOffset="67" android:valueFrom="1.1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="100" android:startOffset="67" android:valueFrom="1.1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.659,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="100" android:startOffset="67" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="100" android:startOffset="67" android:valueFrom="1.1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.096 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathEnd" android:duration="83" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="trimPathEnd" android:duration="250" android:startOffset="83" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_1_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathEnd" android:duration="83" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="trimPathEnd" android:duration="250" android:startOffset="83" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_2_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathEnd" android:duration="83" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="trimPathEnd" android:duration="250" android:startOffset="83" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_3_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathEnd" android:duration="83" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="trimPathEnd" android:duration="250" android:startOffset="83" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="417" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_unlock.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_unlock.xml
new file mode 100644
index 0000000..c4f8181
--- /dev/null
+++ b/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_unlock.xml
@@ -0,0 +1 @@
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="80dp" android:width="80dp" android:viewportHeight="80" android:viewportWidth="80"><group android:name="_R_G"><group android:name="_R_G_L_3_G" android:translateX="-0.25" android:translateY="-0.25"><path android:name="_R_G_L_3_G_D_0_P_0" android:fillColor="#474747" android:fillAlpha="0" android:fillType="nonZero" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_2_G" android:translateX="-0.25" android:translateY="-0.25" android:pivotX="40.25" android:pivotY="40.25" android:scaleX="0.975" android:scaleY="0.975"><path android:name="_R_G_L_2_G_D_0_P_0" android:strokeColor="#f2b8b5" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2.5" android:strokeAlpha="1" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_1_G" android:translateX="9.950000000000003" android:translateY="10" android:pivotX="30" android:pivotY="30" android:scaleX="1.2" android:scaleY="1.2"><group android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0" android:translateX="30" android:translateY="38.75" android:scaleX="1" android:scaleY="1"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#f2b8b5" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-1.2 -1.25 C-1.2,-1.25 1.2,-1.25 1.2,-1.25 C1.2,-1.25 1.2,1.25 1.2,1.25 C1.2,1.25 -1.2,1.25 -1.2,1.25 C-1.2,1.25 -1.2,-1.25 -1.2,-1.25c "/></group><group android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0" android:translateX="30" android:translateY="25" android:pivotX="0.002" android:pivotY="7.488" android:scaleX="1" android:scaleY="1"><path android:name="_R_G_L_1_G_D_1_P_0" android:fillColor="#f2b8b5" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-1.2 -7.5 C-1.2,-7.5 1.2,-7.5 1.2,-7.5 C1.2,-7.5 1.2,7.5 1.2,7.5 C1.2,7.5 -1.2,7.5 -1.2,7.5 C-1.2,7.5 -1.2,-7.5 -1.2,-7.5c "/></group></group><group android:name="_R_G_L_0_G" android:translateX="20.75" android:translateY="15.75" android:pivotX="19.341" android:pivotY="24.25" android:scaleX="0.5" android:scaleY="0"><path android:name="_R_G_L_0_G_D_0_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M30.64 30.14 C30.64,30.14 30.64,38.14 30.64,38.14 C30.64,38.77 30.36,39.32 29.91,39.69 C29.57,39.97 29.12,40.14 28.64,40.14 C28.64,40.14 10.14,40.14 10.14,40.14 C9.04,40.14 8.14,39.25 8.14,38.14 C8.14,38.14 8.14,30.14 8.14,30.14 "/><path android:name="_R_G_L_0_G_D_1_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M19.42 31.53 C18.15,31.52 18.11,30.33 18.11,30.33 C18.11,29.59 18.66,28.98 19.4,28.98 C20.13,28.98 20.69,29.59 20.69,30.33 C20.69,30.33 20.69,30.37 20.69,30.37 C20.69,30.64 20.49,30.87 20.25,30.87 C20.07,30.87 19.91,30.74 19.84,30.55 C19.84,30.55 19.69,30.14 19.69,30.14 C19.63,29.94 19.46,29.82 19.28,29.82 C19.04,29.82 18.61,30.02 18.61,30.29 C18.61,30.43 18.6,30.75 18.76,31.03 C18.87,31.21 19.21,31.77 19.96,31.41 C20.69,31.01 20.69,30.34 20.69,30.34 "/><path android:name="_R_G_L_0_G_D_2_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M8.14 30.22 C8.14,30.22 8.14,22.22 8.14,22.22 C8.14,21.71 8.33,21.25 8.64,20.9 C9,20.48 9.54,20.22 10.14,20.22 C10.14,20.22 28.64,20.22 28.64,20.22 C29.75,20.22 30.64,21.11 30.64,22.22 C30.64,22.22 30.64,30.14 30.64,30.14 "/><path android:name="_R_G_L_0_G_D_3_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_3_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="fillAlpha" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="fillAlpha" android:duration="167" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeWidth" android:duration="167" android:startOffset="0" android:valueFrom="2.5" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeAlpha" android:duration="83" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="strokeAlpha" android:duration="83" android:startOffset="83" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="0.975" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_2_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="167" android:valueFrom="0.975" android:valueTo="0" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="100" android:startOffset="67" android:valueFrom="1.1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="100" android:startOffset="67" android:valueFrom="1.1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.659,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="100" android:startOffset="67" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="100" android:startOffset="67" android:valueFrom="1.1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.096 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_3_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="233" android:startOffset="0" android:valueFrom="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueTo="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="267" android:startOffset="233" android:valueFrom="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueTo="M37.91 20.05 C37.91,20.05 37.89,14.16 37.89,14.16 C37.89,10.79 35.15,8.05 31.86,8.03 C28.46,8.01 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="167" android:startOffset="0" android:valueFrom="0.5" android:valueTo="0.5" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.05,1.4 0.1,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="167" android:startOffset="0" android:valueFrom="0.5" android:valueTo="0.5" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.05,1.4 0.1,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="333" android:startOffset="167" android:valueFrom="0.5" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.05,1.4 0.1,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="333" android:startOffset="167" android:valueFrom="0.5" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.05,1.4 0.1,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="167" android:valueFrom="0" android:valueTo="0.5" android:valueType="floatType"/></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="683" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml
index fc2c7d0..c05a8d5 100644
--- a/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml
+++ b/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml
@@ -1,247 +1 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<!-- Copyright (C) 2021 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.
--->
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
-                 xmlns:aapt="http://schemas.android.com/aapt">
-    <aapt:attr name="android:drawable">
-        <vector android:height="60dp" android:width="60dp" android:viewportHeight="60"
-                android:viewportWidth="60">
-            <group android:name="_R_G">
-                <group android:name="_R_G_L_1_G" android:translateX="-0.05000000000000071">
-                    <group android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0" android:translateX="30"
-                           android:translateY="38.75" android:scaleX="0" android:scaleY="0">
-                        <path android:name="_R_G_L_1_G_D_0_P_0"
-                              android:fillColor="@color/biometric_dialog_error"
-                              android:fillAlpha="1" android:fillType="nonZero"
-                              android:pathData=" M-1.2 -1.25 C-1.2,-1.25 1.2,-1.25 1.2,-1.25 C1.2,-1.25 1.2,1.25 1.2,1.25 C1.2,1.25 -1.2,1.25 -1.2,1.25 C-1.2,1.25 -1.2,-1.25 -1.2,-1.25c "/>
-                    </group>
-                    <group android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0" android:translateX="30"
-                           android:translateY="25" android:pivotX="0.002" android:pivotY="7.488"
-                           android:scaleX="1" android:scaleY="0">
-                        <path android:name="_R_G_L_1_G_D_1_P_0"
-                              android:fillColor="@color/biometric_dialog_error"
-                              android:fillAlpha="1" android:fillType="nonZero"
-                              android:pathData=" M-1.2 -7.5 C-1.2,-7.5 1.2,-7.5 1.2,-7.5 C1.2,-7.5 1.2,7.5 1.2,7.5 C1.2,7.5 -1.2,7.5 -1.2,7.5 C-1.2,7.5 -1.2,-7.5 -1.2,-7.5c "/>
-                    </group>
-                    <path android:name="_R_G_L_1_G_D_2_P_0"
-                          android:strokeColor="@color/biometric_dialog_error"
-                          android:strokeLineCap="round" android:strokeLineJoin="round"
-                          android:strokeWidth="2.5" android:strokeAlpha="1"
-                          android:trimPathStart="1" android:trimPathEnd="1"
-                          android:trimPathOffset="0"
-                          android:pathData=" M30 6.2 C16.9,6.2 6.3,16.8 6.3,30 C6.3,43.2 16.9,53.8 30,53.8 C43.1,53.8 53.8,43.2 53.8,30 C53.8,16.8 43.1,6.2 30,6.2c "/>
-                </group>
-                <group android:name="_R_G_L_0_G" android:translateX="-10.325"
-                       android:translateY="-10.25">
-                    <path android:name="_R_G_L_0_G_D_0_P_0"
-                          android:strokeColor="@color/biometric_dialog_accent"
-                          android:strokeLineCap="round" android:strokeLineJoin="round"
-                          android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0"
-                          android:trimPathEnd="1" android:trimPathOffset="0"
-                          android:pathData=" M31.41 48.43 C30.78,46.69 30.78,44.91 30.78,44.91 C30.78,40.09 34.88,36.16 40.32,36.16 C45.77,36.16 49.87,40.09 49.87,44.91 C49.87,44.91 49.87,45.17 49.87,45.17 C49.87,46.97 48.41,48.43 46.61,48.43 C45.28,48.43 44.09,47.63 43.6,46.39 C43.6,46.39 42.51,43.66 42.51,43.66 C42.02,42.42 40.82,41.61 39.49,41.61 C37.69,41.61 36.23,43.07 36.23,44.87 C36.23,47.12 37.26,49.26 39.02,50.67 C39.02,50.67 39.64,51.16 39.64,51.16 "/>
-                    <path android:name="_R_G_L_0_G_D_1_P_0"
-                          android:strokeColor="@color/biometric_dialog_accent"
-                          android:strokeLineCap="round" android:strokeLineJoin="round"
-                          android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0"
-                          android:trimPathEnd="1" android:trimPathOffset="0"
-                          android:pathData=" M32.14 27.3 C34.5,26 37.31,25.25 40.33,25.25 C43.34,25.25 46.15,26 48.51,27.3 "/>
-                    <path android:name="_R_G_L_0_G_D_2_P_0"
-                          android:strokeColor="@color/biometric_dialog_accent"
-                          android:strokeLineCap="round" android:strokeLineJoin="round"
-                          android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0"
-                          android:trimPathEnd="1" android:trimPathOffset="0"
-                          android:pathData=" M29.42 36.16 C31.35,32.94 35.51,30.71 40.33,30.71 C45.14,30.71 49.3,32.94 51.23,36.16 "/>
-                    <path android:name="_R_G_L_0_G_D_3_P_0"
-                          android:strokeColor="@color/biometric_dialog_accent"
-                          android:strokeLineCap="round" android:strokeLineJoin="round"
-                          android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0"
-                          android:trimPathEnd="1" android:trimPathOffset="0"
-                          android:pathData=" M47.14 52.52 C45.33,54.21 42.94,55.25 40.33,55.25 C37.71,55.25 35.32,54.21 33.51,52.52 "/>
-                </group>
-            </group>
-            <group android:name="time_group"/>
-        </vector>
-    </aapt:attr>
-    <target android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0">
-        <aapt:attr name="android:animation">
-            <set android:ordering="together">
-                <objectAnimator android:propertyName="scaleX" android:duration="167"
-                                android:startOffset="0" android:valueFrom="0" android:valueTo="0"
-                                android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/>
-                    </aapt:attr>
-                </objectAnimator>
-                <objectAnimator android:propertyName="scaleY" android:duration="167"
-                                android:startOffset="0" android:valueFrom="0" android:valueTo="0"
-                                android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/>
-                    </aapt:attr>
-                </objectAnimator>
-                <objectAnimator android:propertyName="scaleX" android:duration="100"
-                                android:startOffset="167" android:valueFrom="0"
-                                android:valueTo="1.1" android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/>
-                    </aapt:attr>
-                </objectAnimator>
-                <objectAnimator android:propertyName="scaleY" android:duration="100"
-                                android:startOffset="167" android:valueFrom="0"
-                                android:valueTo="1.1" android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/>
-                    </aapt:attr>
-                </objectAnimator>
-                <objectAnimator android:propertyName="scaleX" android:duration="67"
-                                android:startOffset="267" android:valueFrom="1.1"
-                                android:valueTo="1" android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.147,1 1.0,1.0"/>
-                    </aapt:attr>
-                </objectAnimator>
-                <objectAnimator android:propertyName="scaleY" android:duration="67"
-                                android:startOffset="267" android:valueFrom="1.1"
-                                android:valueTo="1" android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.147,1 1.0,1.0"/>
-                    </aapt:attr>
-                </objectAnimator>
-            </set>
-        </aapt:attr>
-    </target>
-    <target android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0">
-        <aapt:attr name="android:animation">
-            <set android:ordering="together">
-                <objectAnimator android:propertyName="scaleX" android:duration="167"
-                                android:startOffset="0" android:valueFrom="1" android:valueTo="1"
-                                android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
-                    </aapt:attr>
-                </objectAnimator>
-                <objectAnimator android:propertyName="scaleY" android:duration="167"
-                                android:startOffset="0" android:valueFrom="0" android:valueTo="0"
-                                android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.096 0.2,1 1.0,1.0"/>
-                    </aapt:attr>
-                </objectAnimator>
-                <objectAnimator android:propertyName="scaleX" android:duration="100"
-                                android:startOffset="167" android:valueFrom="1" android:valueTo="1"
-                                android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
-                    </aapt:attr>
-                </objectAnimator>
-                <objectAnimator android:propertyName="scaleY" android:duration="100"
-                                android:startOffset="167" android:valueFrom="0"
-                                android:valueTo="1.1" android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.096 0.2,1 1.0,1.0"/>
-                    </aapt:attr>
-                </objectAnimator>
-                <objectAnimator android:propertyName="scaleX" android:duration="67"
-                                android:startOffset="267" android:valueFrom="1" android:valueTo="1"
-                                android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.341,0 0.2,1 1.0,1.0"/>
-                    </aapt:attr>
-                </objectAnimator>
-                <objectAnimator android:propertyName="scaleY" android:duration="67"
-                                android:startOffset="267" android:valueFrom="1.1"
-                                android:valueTo="1" android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
-                    </aapt:attr>
-                </objectAnimator>
-            </set>
-        </aapt:attr>
-    </target>
-    <target android:name="_R_G_L_1_G_D_2_P_0">
-        <aapt:attr name="android:animation">
-            <set android:ordering="together">
-                <objectAnimator android:propertyName="trimPathStart" android:duration="267"
-                                android:startOffset="0" android:valueFrom="1" android:valueTo="0"
-                                android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
-                    </aapt:attr>
-                </objectAnimator>
-            </set>
-        </aapt:attr>
-    </target>
-    <target android:name="_R_G_L_0_G_D_0_P_0">
-        <aapt:attr name="android:animation">
-            <set android:ordering="together">
-                <objectAnimator android:propertyName="trimPathStart" android:duration="167"
-                                android:startOffset="0" android:valueFrom="0" android:valueTo="1"
-                                android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
-                    </aapt:attr>
-                </objectAnimator>
-            </set>
-        </aapt:attr>
-    </target>
-    <target android:name="_R_G_L_0_G_D_1_P_0">
-        <aapt:attr name="android:animation">
-            <set android:ordering="together">
-                <objectAnimator android:propertyName="trimPathStart" android:duration="167"
-                                android:startOffset="0" android:valueFrom="0" android:valueTo="1"
-                                android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
-                    </aapt:attr>
-                </objectAnimator>
-            </set>
-        </aapt:attr>
-    </target>
-    <target android:name="_R_G_L_0_G_D_2_P_0">
-        <aapt:attr name="android:animation">
-            <set android:ordering="together">
-                <objectAnimator android:propertyName="trimPathStart" android:duration="167"
-                                android:startOffset="0" android:valueFrom="0" android:valueTo="1"
-                                android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
-                    </aapt:attr>
-                </objectAnimator>
-            </set>
-        </aapt:attr>
-    </target>
-    <target android:name="_R_G_L_0_G_D_3_P_0">
-        <aapt:attr name="android:animation">
-            <set android:ordering="together">
-                <objectAnimator android:propertyName="trimPathStart" android:duration="167"
-                                android:startOffset="0" android:valueFrom="0" android:valueTo="1"
-                                android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
-                    </aapt:attr>
-                </objectAnimator>
-            </set>
-        </aapt:attr>
-    </target>
-    <target android:name="time_group">
-        <aapt:attr name="android:animation">
-            <set android:ordering="together">
-                <objectAnimator android:propertyName="translateX" android:duration="350"
-                                android:startOffset="0" android:valueFrom="0" android:valueTo="1"
-                                android:valueType="floatType"/>
-            </set>
-        </aapt:attr>
-    </target>
-</animated-vector>
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="80dp" android:width="80dp" android:viewportHeight="80" android:viewportWidth="80"><group android:name="_R_G"><group android:name="_R_G_L_3_G" android:translateX="-0.25" android:translateY="-0.25"><path android:name="_R_G_L_3_G_D_0_P_0" android:fillColor="#474747" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_2_G" android:translateX="-0.25" android:translateY="-0.25" android:pivotX="40.25" android:pivotY="40.25" android:scaleX="0.975" android:scaleY="0"><path android:name="_R_G_L_2_G_D_0_P_0" android:strokeColor="#f2b8b5" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="0" android:strokeAlpha="0" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_1_G" android:translateX="9.950000000000003" android:translateY="10" android:pivotX="30" android:pivotY="30" android:scaleX="1.2" android:scaleY="1.2"><group android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0" android:translateX="30" android:translateY="38.75" android:scaleX="0" android:scaleY="0"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#f2b8b5" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-1.2 -1.25 C-1.2,-1.25 1.2,-1.25 1.2,-1.25 C1.2,-1.25 1.2,1.25 1.2,1.25 C1.2,1.25 -1.2,1.25 -1.2,1.25 C-1.2,1.25 -1.2,-1.25 -1.2,-1.25c "/></group><group android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0" android:translateX="30" android:translateY="25" android:pivotX="0.002" android:pivotY="7.488" android:scaleX="1" android:scaleY="0"><path android:name="_R_G_L_1_G_D_1_P_0" android:fillColor="#f2b8b5" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-1.2 -7.5 C-1.2,-7.5 1.2,-7.5 1.2,-7.5 C1.2,-7.5 1.2,7.5 1.2,7.5 C1.2,7.5 -1.2,7.5 -1.2,7.5 C-1.2,7.5 -1.2,-7.5 -1.2,-7.5c "/></group></group><group android:name="_R_G_L_0_G" android:translateX="20.659" android:translateY="15.75"><path android:name="_R_G_L_0_G_D_0_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="1" android:trimPathOffset="0" android:pathData=" M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 "/><path android:name="_R_G_L_0_G_D_1_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="1" android:trimPathOffset="0" android:pathData=" M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 "/><path android:name="_R_G_L_0_G_D_2_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="1" android:trimPathOffset="0" android:pathData=" M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 "/><path android:name="_R_G_L_0_G_D_3_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="1" android:trimPathOffset="0" android:pathData=" M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_3_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="fillAlpha" android:duration="83" android:startOffset="0" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeWidth" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="strokeWidth" android:duration="233" android:startOffset="67" android:valueFrom="0" android:valueTo="2.5" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeAlpha" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="strokeAlpha" android:duration="83" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="67" android:valueFrom="0" android:valueTo="0.975" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="100" android:startOffset="167" android:valueFrom="0" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="100" android:startOffset="167" android:valueFrom="0" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="267" android:valueFrom="1.1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.147,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="267" android:valueFrom="1.1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.147,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="167" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.096 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="100" android:startOffset="167" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="100" android:startOffset="167" android:valueFrom="0" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.096 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="267" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.341,0 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="267" android:valueFrom="1.1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathStart" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_1_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathStart" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_2_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathStart" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_3_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathStart" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="350" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_unlock.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_unlock.xml
new file mode 100644
index 0000000..1694429
--- /dev/null
+++ b/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_unlock.xml
@@ -0,0 +1 @@
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="80dp" android:width="80dp" android:viewportHeight="80" android:viewportWidth="80"><group android:name="_R_G"><group android:name="_R_G_L_2_G" android:translateX="-0.25" android:translateY="-0.25"><path android:name="_R_G_L_2_G_D_0_P_0" android:fillColor="#474747" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_1_G" android:translateX="20.75" android:translateY="15.75"><path android:name="_R_G_L_1_G_D_0_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 "/><path android:name="_R_G_L_1_G_D_1_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 "/><path android:name="_R_G_L_1_G_D_2_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 "/><path android:name="_R_G_L_1_G_D_3_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 "/></group><group android:name="_R_G_L_0_G" android:translateX="37.357" android:translateY="43.25" android:pivotX="2.75" android:pivotY="2.75" android:scaleX="1.41866" android:scaleY="1.41866"><path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#d3e3fd" android:fillAlpha="0" android:fillType="nonZero" android:pathData=" M2.75 5.25 C4.13,5.25 5.25,4.13 5.25,2.75 C5.25,1.37 4.13,0.25 2.75,0.25 C1.37,0.25 0.25,1.37 0.25,2.75 C0.25,4.13 1.37,5.25 2.75,5.25c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_1_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="0" android:valueFrom="M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 " android:valueTo="M30.81 32.26 C30.56,32.49 30.27,38.76 29.96,38.9 C29.77,39.46 29.13,39.94 28.57,40.26 C28.15,40.51 26.93,40.65 26.4,40.65 C26.18,40.65 11.91,40.62 11.71,40.58 C10.68,40.53 9.06,39.79 8.89,38.88 C8.6,38.74 8.34,32.48 8.1,32.27 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="143" android:startOffset="107" android:valueFrom="M30.81 32.26 C30.56,32.49 30.27,38.76 29.96,38.9 C29.77,39.46 29.13,39.94 28.57,40.26 C28.15,40.51 26.93,40.65 26.4,40.65 C26.18,40.65 11.91,40.62 11.71,40.58 C10.68,40.53 9.06,39.79 8.89,38.88 C8.6,38.74 8.34,32.48 8.1,32.27 " android:valueTo="M30.64 30.14 C30.64,30.14 30.64,38.14 30.64,38.14 C30.64,38.77 30.36,39.32 29.91,39.69 C29.57,39.97 29.12,40.14 28.64,40.14 C28.64,40.14 10.14,40.14 10.14,40.14 C9.04,40.14 8.14,39.25 8.14,38.14 C8.14,38.14 8.14,30.14 8.14,30.14 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.331,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_1_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeAlpha" android:duration="140" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="strokeAlpha" android:duration="50" android:startOffset="140" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_1_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="0" android:valueFrom="M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 " android:valueTo="M18.93 32.18 C17.11,32.68 16.62,30.26 16.62,30.26 C16.62,28.78 17.81,27.59 19.39,27.59 C20.96,27.59 22.15,28.78 22.15,30.26 C22.15,30.26 22.15,30.34 22.15,30.34 C22.15,30.89 21.11,32.54 19.57,32.19 C19.19,32.1 20.48,31.09 20.34,30.71 C20.34,30.71 20.02,29.88 20.02,29.88 C19.88,29.5 19.53,29.25 19.15,29.25 C18.63,29.25 18,29.67 18,30.22 C18,30.57 18.06,31.08 18.32,31.51 C18.49,31.8 19.02,32.25 19.79,32.04 C20.41,31.7 20.38,31.36 20.38,31.36 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="107" android:valueFrom="M18.93 32.18 C17.11,32.68 16.62,30.26 16.62,30.26 C16.62,28.78 17.81,27.59 19.39,27.59 C20.96,27.59 22.15,28.78 22.15,30.26 C22.15,30.26 22.15,30.34 22.15,30.34 C22.15,30.89 21.11,32.54 19.57,32.19 C19.19,32.1 20.48,31.09 20.34,30.71 C20.34,30.71 20.02,29.88 20.02,29.88 C19.88,29.5 19.53,29.25 19.15,29.25 C18.63,29.25 18,29.67 18,30.22 C18,30.57 18.06,31.08 18.32,31.51 C18.49,31.8 19.02,32.25 19.79,32.04 C20.41,31.7 20.38,31.36 20.38,31.36 " android:valueTo="M19.42 31.53 C18.15,31.52 18.11,30.33 18.11,30.33 C18.11,29.59 18.66,28.98 19.4,28.98 C20.13,28.98 20.69,29.59 20.69,30.33 C20.69,30.33 20.69,30.37 20.69,30.37 C20.69,30.64 20.49,30.87 20.25,30.87 C20.07,30.87 19.91,30.74 19.84,30.55 C19.84,30.55 19.69,30.14 19.69,30.14 C19.63,29.94 19.46,29.82 19.28,29.82 C19.04,29.82 18.61,30.02 18.61,30.29 C18.61,30.43 18.6,30.75 18.76,31.03 C18.87,31.21 19.21,31.77 19.96,31.41 C20.69,31.01 20.69,30.34 20.69,30.34 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_2_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="250" android:startOffset="0" android:valueFrom="M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 " android:valueTo="M8.14 30.22 C8.14,30.22 8.14,22.22 8.14,22.22 C8.14,21.71 8.33,21.25 8.64,20.9 C9,20.48 9.54,20.22 10.14,20.22 C10.14,20.22 28.64,20.22 28.64,20.22 C29.75,20.22 30.64,21.11 30.64,22.22 C30.64,22.22 30.64,30.14 30.64,30.14 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.189,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_3_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="95" android:startOffset="0" android:valueFrom="M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 " android:valueTo="M11.47 14.84 C11.47,14.84 12.21,11.43 13.54,9.84 C14.84,8.28 16.68,7.22 19.35,7.22 C22.01,7.22 23.98,8.4 25.19,10.18 C26.39,11.96 27.25,14.84 27.25,14.84 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="24" android:startOffset="95" android:valueFrom="M11.47 14.84 C11.47,14.84 12.21,11.43 13.54,9.84 C14.84,8.28 16.68,7.22 19.35,7.22 C22.01,7.22 23.98,8.4 25.19,10.18 C26.39,11.96 27.25,14.84 27.25,14.84 " android:valueTo="M12.11 16.85 C12.11,16.85 12.82,12.71 13.37,11.5 C14.17,9.24 16.38,7.53 19.35,7.53 C22.32,7.53 24.61,9.32 25.35,11.72 C25.61,12.64 26.62,16.85 26.62,16.85 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.833,0.767 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="81" android:startOffset="119" android:valueFrom="M12.11 16.85 C12.11,16.85 12.82,12.71 13.37,11.5 C14.17,9.24 16.38,7.53 19.35,7.53 C22.32,7.53 24.61,9.32 25.35,11.72 C25.61,12.64 26.62,16.85 26.62,16.85 " android:valueTo="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.261,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="233" android:startOffset="200" android:valueFrom="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueTo="M37.91 20.05 C37.91,20.05 37.89,14.16 37.89,14.16 C37.89,10.79 35.15,8.05 31.86,8.03 C28.46,8.01 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.123,0 0.23,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="fillAlpha" android:duration="120" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="fillAlpha" android:duration="20" android:startOffset="120" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="120" android:startOffset="0" android:valueFrom="1.4186600000000003" android:valueTo="1.4186600000000003" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="120" android:startOffset="0" android:valueFrom="1.4186600000000003" android:valueTo="1.4186600000000003" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="130" android:startOffset="120" android:valueFrom="1.4186600000000003" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="130" android:startOffset="120" android:valueFrom="1.4186600000000003" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="517" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/auth_biometric_contents.xml b/packages/SystemUI/res/layout/auth_biometric_contents.xml
index 89690e8..58adb91 100644
--- a/packages/SystemUI/res/layout/auth_biometric_contents.xml
+++ b/packages/SystemUI/res/layout/auth_biometric_contents.xml
@@ -49,8 +49,8 @@
 
         <ImageView
             android:id="@+id/biometric_icon"
-            android:layout_width="@dimen/biometric_dialog_biometric_icon_size"
-            android:layout_height="@dimen/biometric_dialog_biometric_icon_size"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
             android:layout_gravity="center"
             android:contentDescription="@null"
             android:scaleType="fitXY" />
diff --git a/packages/SystemUI/res/layout/auth_biometric_face_to_fingerprint_view.xml b/packages/SystemUI/res/layout/auth_biometric_fingerprint_and_face_view.xml
similarity index 83%
rename from packages/SystemUI/res/layout/auth_biometric_face_to_fingerprint_view.xml
rename to packages/SystemUI/res/layout/auth_biometric_fingerprint_and_face_view.xml
index 7cf1789..05ca2a7 100644
--- a/packages/SystemUI/res/layout/auth_biometric_face_to_fingerprint_view.xml
+++ b/packages/SystemUI/res/layout/auth_biometric_fingerprint_and_face_view.xml
@@ -14,12 +14,13 @@
   ~ limitations under the License.
   -->
 
-<com.android.systemui.biometrics.AuthBiometricFaceToFingerprintView
+<com.android.systemui.biometrics.AuthBiometricFingerprintAndFaceView
     xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/contents"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:orientation="vertical">
 
     <include layout="@layout/auth_biometric_contents"/>
 
-</com.android.systemui.biometrics.AuthBiometricFaceToFingerprintView>
\ No newline at end of file
+</com.android.systemui.biometrics.AuthBiometricFingerprintAndFaceView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/auth_biometric_udfps_view.xml b/packages/SystemUI/res/layout/auth_biometric_view.xml
similarity index 83%
rename from packages/SystemUI/res/layout/auth_biometric_udfps_view.xml
rename to packages/SystemUI/res/layout/auth_biometric_view.xml
index 238288e..ee4da25 100644
--- a/packages/SystemUI/res/layout/auth_biometric_udfps_view.xml
+++ b/packages/SystemUI/res/layout/auth_biometric_view.xml
@@ -1,5 +1,5 @@
 <!--
-  ~ Copyright (C) 2020 The Android Open Source Project
+  ~ Copyright (C) 2022 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.
@@ -14,7 +14,7 @@
   ~ limitations under the License.
   -->
 
-<com.android.systemui.biometrics.AuthBiometricUdfpsView
+<com.android.systemui.biometrics.AuthBiometricView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/contents"
     android:layout_width="match_parent"
@@ -23,4 +23,4 @@
 
     <include layout="@layout/auth_biometric_contents"/>
 
-</com.android.systemui.biometrics.AuthBiometricUdfpsView>
\ No newline at end of file
+</com.android.systemui.biometrics.AuthBiometricView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 65c17b9..5a7efca 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -876,7 +876,8 @@
     <dimen name="remote_input_history_extra_height">60dp</dimen>
 
     <!-- Biometric Dialog values -->
-    <dimen name="biometric_dialog_biometric_icon_size">64dp</dimen>
+    <dimen name="biometric_dialog_face_icon_size">64dp</dimen>
+    <dimen name="biometric_dialog_fingerprint_icon_size">80dp</dimen>
     <dimen name="biometric_dialog_button_negative_max_width">160dp</dimen>
     <dimen name="biometric_dialog_button_positive_max_width">136dp</dimen>
     <dimen name="biometric_dialog_corner_size">4dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 6d336e6..9e1f57b 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -322,6 +322,8 @@
     <string name="biometric_dialog_face_icon_description_confirmed">Confirmed</string>
     <!-- Message shown when a biometric is authenticated, waiting for the user to confirm authentication [CHAR LIMIT=40]-->
     <string name="biometric_dialog_tap_confirm">Tap Confirm to complete</string>
+    <!-- Message shown when a biometric has authenticated with a user's face and is waiting for the user to confirm authentication [CHAR LIMIT=60]-->
+    <string name="biometric_dialog_tap_confirm_with_face">Unlocked by your face. Press to continue.</string>
     <!-- Talkback string when a biometric is authenticated [CHAR LIMIT=NONE] -->
     <string name="biometric_dialog_authenticated">Authenticated</string>
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
index 885a177..aafbf7e 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
@@ -172,6 +172,17 @@
     }
 
     @MainThread
+    void moveWindowMagnifierToPositionInternal(int displayId, float positionX, float positionY,
+            IRemoteMagnificationAnimationCallback callback) {
+        final WindowMagnificationController windowMagnificationController =
+                mMagnificationControllerSupplier.get(displayId);
+        if (windowMagnificationController != null) {
+            windowMagnificationController.moveWindowMagnifierToPosition(positionX, positionY,
+                    callback);
+        }
+    }
+
+    @MainThread
     void disableWindowMagnification(int displayId,
             @Nullable IRemoteMagnificationAnimationCallback callback) {
         final WindowMagnificationController windowMagnificationController =
@@ -210,9 +221,9 @@
     }
 
     @Override
-    public void onDrag(int displayId) {
+    public void onMove(int displayId) {
         if (mWindowMagnificationConnectionImpl != null) {
-            mWindowMagnificationConnectionImpl.onDrag(displayId);
+            mWindowMagnificationConnectionImpl.onMove(displayId);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java
index dc1e005..3b4114b 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java
@@ -156,6 +156,7 @@
         }
         mAnimationCallback = animationCallback;
         setupEnableAnimationSpecs(scale, centerX, centerY);
+
         if (mEndSpec.equals(mStartSpec)) {
             if (mState == STATE_DISABLED) {
                 mController.enableWindowMagnificationInternal(scale, centerX, centerY,
@@ -178,6 +179,24 @@
         }
     }
 
+    void moveWindowMagnifierToPosition(float centerX, float centerY,
+            IRemoteMagnificationAnimationCallback callback) {
+        if (mState == STATE_ENABLED) {
+            // We set the animation duration to shortAnimTime which would be reset at the end.
+            mValueAnimator.setDuration(mContext.getResources()
+                    .getInteger(com.android.internal.R.integer.config_shortAnimTime));
+            enableWindowMagnification(Float.NaN, centerX, centerY,
+                    /* magnificationFrameOffsetRatioX */ Float.NaN,
+                    /* magnificationFrameOffsetRatioY */ Float.NaN, callback);
+        } else if (mState == STATE_ENABLING) {
+            sendAnimationCallback(false);
+            mAnimationCallback = callback;
+            mValueAnimator.setDuration(mContext.getResources()
+                    .getInteger(com.android.internal.R.integer.config_shortAnimTime));
+            setupEnableAnimationSpecs(Float.NaN, centerX, centerY);
+        }
+    }
+
     private void setupEnableAnimationSpecs(float scale, float centerX, float centerY) {
         if (mController == null) {
             return;
@@ -193,9 +212,16 @@
                     R.integer.magnification_default_scale) : scale, centerX, centerY);
         } else {
             mStartSpec.set(currentScale, currentCenterX, currentCenterY);
-            mEndSpec.set(Float.isNaN(scale) ? currentScale : scale,
-                    Float.isNaN(centerX) ? currentCenterX : centerX,
-                    Float.isNaN(centerY) ? currentCenterY : centerY);
+
+            final float endScale = (mState == STATE_ENABLING ? mEndSpec.mScale : currentScale);
+            final float endCenterX =
+                    (mState == STATE_ENABLING ? mEndSpec.mCenterX : currentCenterX);
+            final float endCenterY =
+                    (mState == STATE_ENABLING ? mEndSpec.mCenterY : currentCenterY);
+
+            mEndSpec.set(Float.isNaN(scale) ? endScale : scale,
+                    Float.isNaN(centerX) ? endCenterX : centerX,
+                    Float.isNaN(centerY) ? endCenterY : centerY);
         }
         if (DEBUG) {
             Log.d(TAG, "SetupEnableAnimationSpecs : mStartSpec = " + mStartSpec + ", endSpec = "
@@ -269,6 +295,9 @@
             setState(STATE_ENABLED);
         }
         sendAnimationCallback(true);
+        // We reset the duration to config_longAnimTime
+        mValueAnimator.setDuration(mContext.getResources()
+                .getInteger(com.android.internal.R.integer.config_longAnimTime));
     }
 
     @Override
@@ -313,10 +342,10 @@
                 mMagnificationFrameOffsetRatioX, mMagnificationFrameOffsetRatioY);
     }
 
-    private static ValueAnimator newValueAnimator(Resources resources) {
+    private static ValueAnimator newValueAnimator(Resources resource) {
         final ValueAnimator valueAnimator = new ValueAnimator();
         valueAnimator.setDuration(
-                resources.getInteger(com.android.internal.R.integer.config_longAnimTime));
+                resource.getInteger(com.android.internal.R.integer.config_longAnimTime));
         valueAnimator.setInterpolator(new AccelerateInterpolator(2.5f));
         valueAnimator.setFloatValues(0.0f, 1.0f);
         return valueAnimator;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java
index 1d22633..aa684fa 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java
@@ -77,6 +77,13 @@
     }
 
     @Override
+    public void moveWindowMagnifierToPosition(int displayId, float positionX, float positionY,
+            IRemoteMagnificationAnimationCallback callback) {
+        mHandler.post(() -> mWindowMagnification.moveWindowMagnifierToPositionInternal(
+                displayId, positionX, positionY, callback));
+    }
+
+    @Override
     public void showMagnificationButton(int displayId, int magnificationMode) {
         mHandler.post(
                 () -> mModeSwitchesController.showButton(displayId, magnificationMode));
@@ -143,10 +150,10 @@
         }
     }
 
-    void onDrag(int displayId) {
+    void onMove(int displayId) {
         if (mConnectionCallback != null) {
             try {
-                mConnectionCallback.onDrag(displayId);
+                mConnectionCallback.onMove(displayId);
             } catch (RemoteException e) {
                 Log.e(TAG, "Failed to inform taking control by a user", e);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index de03993..50ca447 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -852,6 +852,7 @@
     @Override
     public void move(int xOffset, int yOffset) {
         moveWindowMagnifier(xOffset, yOffset);
+        mWindowMagnifierCallback.onMove(mDisplayId);
     }
 
     /**
@@ -985,6 +986,14 @@
         }
     }
 
+    void moveWindowMagnifierToPosition(float positionX, float positionY,
+            IRemoteMagnificationAnimationCallback callback) {
+        if (mMirrorSurfaceView == null) {
+            return;
+        }
+        mAnimationController.moveWindowMagnifierToPosition(positionX, positionY, callback);
+    }
+
     /**
      * Gets the scale.
      *
@@ -1037,8 +1046,7 @@
 
     @Override
     public boolean onDrag(float offsetX, float offsetY) {
-        moveWindowMagnifier(offsetX, offsetY);
-        mWindowMagnifierCallback.onDrag(mDisplayId);
+        move((int) offsetX, (int) offsetY);
         return true;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java
index bdded10..c334ca6 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java
@@ -17,7 +17,6 @@
 package com.android.systemui.accessibility;
 
 import android.graphics.Rect;
-import android.view.ViewConfiguration;
 
 /**
  * A callback to inform {@link com.android.server.accessibility.AccessibilityManagerService} about
@@ -56,11 +55,9 @@
     void onAccessibilityActionPerformed(int displayId);
 
     /**
-     * Called when the user is performing dragging gesture. It is started after the offset
-     * between the down location and the move event location exceed
-     * {@link ViewConfiguration#getScaledTouchSlop()}.
+     * Called when the user is performing a move action.
      *
      * @param displayId The logical display id.
      */
-    void onDrag(int displayId);
+    void onMove(int displayId);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
new file mode 100644
index 0000000..55611f7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2022 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.biometrics
+
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.util.Log
+import android.widget.ImageView
+import com.android.systemui.R
+import com.android.systemui.biometrics.AuthBiometricView.BiometricState
+import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATED
+import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATING
+import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATING_ANIMATING_IN
+import com.android.systemui.biometrics.AuthBiometricView.STATE_ERROR
+import com.android.systemui.biometrics.AuthBiometricView.STATE_HELP
+import com.android.systemui.biometrics.AuthBiometricView.STATE_IDLE
+import com.android.systemui.biometrics.AuthBiometricView.STATE_PENDING_CONFIRMATION
+
+private const val TAG = "AuthBiometricFaceIconController"
+
+/** Face only icon animator for BiometricPrompt. */
+class AuthBiometricFaceIconController(
+    context: Context,
+    iconView: ImageView
+) : AuthIconController(context, iconView) {
+
+    // false = dark to light, true = light to dark
+    private var lastPulseLightToDark = false
+
+    @BiometricState
+    private var state = 0
+
+    init {
+        val size = context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_face_icon_size)
+        iconView.layoutParams.width = size
+        iconView.layoutParams.height = size
+        showStaticDrawable(R.drawable.face_dialog_pulse_dark_to_light)
+    }
+
+    private fun startPulsing() {
+        lastPulseLightToDark = false
+        animateIcon(R.drawable.face_dialog_pulse_dark_to_light, true)
+    }
+
+    private fun pulseInNextDirection() {
+        val iconRes = if (lastPulseLightToDark) {
+            R.drawable.face_dialog_pulse_dark_to_light
+        } else {
+            R.drawable.face_dialog_pulse_light_to_dark
+        }
+        animateIcon(iconRes, true /* repeat */)
+        lastPulseLightToDark = !lastPulseLightToDark
+    }
+
+    override fun handleAnimationEnd(drawable: Drawable) {
+        if (state == STATE_AUTHENTICATING || state == STATE_HELP) {
+            pulseInNextDirection()
+        }
+    }
+
+    override fun updateIcon(@BiometricState oldState: Int, @BiometricState newState: Int) {
+        val lastStateIsErrorIcon = (oldState == STATE_ERROR || oldState == STATE_HELP)
+        if (newState == STATE_AUTHENTICATING_ANIMATING_IN) {
+            showStaticDrawable(R.drawable.face_dialog_pulse_dark_to_light)
+            iconView.contentDescription = context.getString(
+                R.string.biometric_dialog_face_icon_description_authenticating
+            )
+        } else if (newState == STATE_AUTHENTICATING) {
+            startPulsing()
+            iconView.contentDescription = context.getString(
+                R.string.biometric_dialog_face_icon_description_authenticating
+            )
+        } else if (oldState == STATE_PENDING_CONFIRMATION && newState == STATE_AUTHENTICATED) {
+            animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
+            iconView.contentDescription = context.getString(
+                R.string.biometric_dialog_face_icon_description_confirmed
+            )
+        } else if (lastStateIsErrorIcon && newState == STATE_IDLE) {
+            animateIconOnce(R.drawable.face_dialog_error_to_idle)
+            iconView.contentDescription = context.getString(
+                R.string.biometric_dialog_face_icon_description_idle
+            )
+        } else if (lastStateIsErrorIcon && newState == STATE_AUTHENTICATED) {
+            animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
+            iconView.contentDescription = context.getString(
+                R.string.biometric_dialog_face_icon_description_authenticated
+            )
+        } else if (newState == STATE_ERROR && oldState != STATE_ERROR) {
+            animateIconOnce(R.drawable.face_dialog_dark_to_error)
+        } else if (oldState == STATE_AUTHENTICATING && newState == STATE_AUTHENTICATED) {
+            animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
+            iconView.contentDescription = context.getString(
+                R.string.biometric_dialog_face_icon_description_authenticated
+            )
+        } else if (newState == STATE_PENDING_CONFIRMATION) {
+            animateIconOnce(R.drawable.face_dialog_wink_from_dark)
+            iconView.contentDescription = context.getString(
+                R.string.biometric_dialog_face_icon_description_authenticated
+            )
+        } else if (newState == STATE_IDLE) {
+            showStaticDrawable(R.drawable.face_dialog_idle_static)
+            iconView.contentDescription = context.getString(
+                R.string.biometric_dialog_face_icon_description_idle
+            )
+        } else {
+            Log.w(TAG, "Unhandled state: $newState")
+        }
+        state = newState
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintView.java
deleted file mode 100644
index ae3e94b..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintView.java
+++ /dev/null
@@ -1,245 +0,0 @@
-/*
- * Copyright (C) 2021 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.biometrics;
-
-import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
-import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
-
-import android.content.Context;
-import android.hardware.biometrics.BiometricAuthenticator.Modality;
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
-import android.os.Bundle;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.R;
-
-/**
- * Manages the layout of an auth dialog for devices with both a face sensor and a fingerprint
- * sensor. Face authentication is attempted first, followed by fingerprint if the initial attempt is
- * unsuccessful.
- */
-public class AuthBiometricFaceToFingerprintView extends AuthBiometricFaceView {
-    private static final String TAG = "BiometricPrompt/AuthBiometricFaceToFingerprintView";
-
-    protected static class UdfpsIconController extends IconController {
-        @BiometricState private int mIconState = STATE_IDLE;
-
-        protected UdfpsIconController(
-                @NonNull Context context, @NonNull ImageView iconView, @NonNull TextView textView) {
-            super(context, iconView, textView);
-        }
-
-        void updateState(@BiometricState int newState) {
-            updateState(mIconState, newState);
-        }
-
-        @Override
-        protected void updateState(int lastState, int newState) {
-            final boolean lastStateIsErrorIcon =
-                    lastState == STATE_ERROR || lastState == STATE_HELP;
-
-            switch (newState) {
-                case STATE_IDLE:
-                case STATE_AUTHENTICATING_ANIMATING_IN:
-                case STATE_AUTHENTICATING:
-                case STATE_PENDING_CONFIRMATION:
-                case STATE_AUTHENTICATED:
-                    if (lastStateIsErrorIcon) {
-                        animateOnce(R.drawable.fingerprint_dialog_error_to_fp);
-                    } else {
-                        showStaticDrawable(R.drawable.fingerprint_dialog_fp_to_error);
-                    }
-                    mIconView.setContentDescription(mContext.getString(
-                            R.string.accessibility_fingerprint_dialog_fingerprint_icon));
-                    break;
-
-                case STATE_ERROR:
-                case STATE_HELP:
-                    if (!lastStateIsErrorIcon) {
-                        animateOnce(R.drawable.fingerprint_dialog_fp_to_error);
-                    } else {
-                        showStaticDrawable(R.drawable.fingerprint_dialog_error_to_fp);
-                    }
-                    mIconView.setContentDescription(mContext.getString(
-                            R.string.biometric_dialog_try_again));
-                    break;
-
-                default:
-                    Log.e(TAG, "Unknown biometric dialog state: " + newState);
-                    break;
-            }
-
-            mState = newState;
-            mIconState = newState;
-        }
-    }
-
-    @Modality private int mActiveSensorType = TYPE_FACE;
-    @Nullable private ModalityListener mModalityListener;
-    @Nullable private FingerprintSensorPropertiesInternal mFingerprintSensorProps;
-    @Nullable private UdfpsDialogMeasureAdapter mUdfpsMeasureAdapter;
-    @Nullable @VisibleForTesting UdfpsIconController mUdfpsIconController;
-
-
-    public AuthBiometricFaceToFingerprintView(Context context) {
-        super(context);
-    }
-
-    public AuthBiometricFaceToFingerprintView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    @VisibleForTesting
-    AuthBiometricFaceToFingerprintView(Context context, AttributeSet attrs, Injector injector) {
-        super(context, attrs, injector);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        mUdfpsIconController = new UdfpsIconController(mContext, mIconView, mIndicatorView);
-    }
-
-    @Modality
-    int getActiveSensorType() {
-        return mActiveSensorType;
-    }
-
-    boolean isFingerprintUdfps() {
-        return mFingerprintSensorProps.isAnyUdfpsType();
-    }
-
-    void setModalityListener(@NonNull ModalityListener listener) {
-        mModalityListener = listener;
-    }
-
-    void setFingerprintSensorProps(@NonNull FingerprintSensorPropertiesInternal sensorProps) {
-        mFingerprintSensorProps = sensorProps;
-    }
-
-    @Override
-    protected int getDelayAfterAuthenticatedDurationMs() {
-        return mActiveSensorType == TYPE_FINGERPRINT ? 0
-                : super.getDelayAfterAuthenticatedDurationMs();
-    }
-
-    @Override
-    protected boolean supportsManualRetry() {
-        return false;
-    }
-
-    @Override
-    public void onAuthenticationFailed(
-            @Modality int modality, @Nullable String failureReason) {
-        super.onAuthenticationFailed(modality, checkErrorForFallback(failureReason));
-    }
-
-    @Override
-    public void onError(int modality, String error) {
-        super.onError(modality, checkErrorForFallback(error));
-    }
-
-    private String checkErrorForFallback(String message) {
-        if (mActiveSensorType == TYPE_FACE) {
-            Log.d(TAG, "Falling back to fingerprint: " + message);
-
-            // switching from face -> fingerprint mode, suppress root error messages
-            mCallback.onAction(Callback.ACTION_START_DELAYED_FINGERPRINT_SENSOR);
-            return mContext.getString(R.string.fingerprint_dialog_use_fingerprint_instead);
-        }
-        return message;
-    }
-
-    @Override
-    @BiometricState
-    protected int getStateForAfterError() {
-        if (mActiveSensorType == TYPE_FACE) {
-            return STATE_AUTHENTICATING;
-        }
-
-        return super.getStateForAfterError();
-    }
-
-    @Override
-    public void updateState(@BiometricState int newState) {
-        if (mActiveSensorType == TYPE_FACE) {
-            if (newState == STATE_HELP || newState == STATE_ERROR) {
-                mActiveSensorType = TYPE_FINGERPRINT;
-
-                setRequireConfirmation(false);
-                mConfirmButton.setEnabled(false);
-                mConfirmButton.setVisibility(View.GONE);
-
-                if (mModalityListener != null) {
-                    mModalityListener.onModalitySwitched(TYPE_FACE, mActiveSensorType);
-                }
-
-                // Deactivate the face icon controller so it stops drawing to the view
-                mFaceIconController.deactivate();
-                // Then, activate this icon controller. We need to start in the "idle" state
-                mUdfpsIconController.updateState(STATE_IDLE);
-            }
-        } else { // Fingerprint
-            mUdfpsIconController.updateState(newState);
-        }
-
-        super.updateState(newState);
-    }
-
-    @Override
-    @NonNull
-    AuthDialog.LayoutParams onMeasureInternal(int width, int height) {
-        final AuthDialog.LayoutParams layoutParams = super.onMeasureInternal(width, height);
-        return isFingerprintUdfps()
-                ? getUdfpsMeasureAdapter().onMeasureInternal(width, height, layoutParams)
-                : layoutParams;
-    }
-
-    @NonNull
-    private UdfpsDialogMeasureAdapter getUdfpsMeasureAdapter() {
-        if (mUdfpsMeasureAdapter == null
-                || mUdfpsMeasureAdapter.getSensorProps() != mFingerprintSensorProps) {
-            mUdfpsMeasureAdapter = new UdfpsDialogMeasureAdapter(this, mFingerprintSensorProps);
-        }
-        return mUdfpsMeasureAdapter;
-    }
-
-    @Override
-    public void onSaveState(@NonNull Bundle outState) {
-        super.onSaveState(outState);
-        outState.putInt(AuthDialog.KEY_BIOMETRIC_SENSOR_TYPE, mActiveSensorType);
-        outState.putParcelable(AuthDialog.KEY_BIOMETRIC_SENSOR_PROPS, mFingerprintSensorProps);
-    }
-
-    @Override
-    public void restoreState(@Nullable Bundle savedState) {
-        super.restoreState(savedState);
-        if (savedState != null) {
-            mActiveSensorType = savedState.getInt(AuthDialog.KEY_BIOMETRIC_SENSOR_TYPE, TYPE_FACE);
-            mFingerprintSensorProps =
-                    savedState.getParcelable(AuthDialog.KEY_BIOMETRIC_SENSOR_PROPS);
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java
deleted file mode 100644
index 48f6431..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java
+++ /dev/null
@@ -1,261 +0,0 @@
-/*
- * Copyright (C) 2019 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.biometrics;
-
-import android.content.Context;
-import android.graphics.drawable.Animatable2;
-import android.graphics.drawable.AnimatedVectorDrawable;
-import android.graphics.drawable.Drawable;
-import android.hardware.biometrics.BiometricAuthenticator.Modality;
-import android.os.Handler;
-import android.os.Looper;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.R;
-
-public class AuthBiometricFaceView extends AuthBiometricView {
-
-    private static final String TAG = "BiometricPrompt/AuthBiometricFaceView";
-
-    // Delay before dismissing after being authenticated/confirmed.
-    private static final int HIDE_DELAY_MS = 500;
-
-    protected static class IconController extends Animatable2.AnimationCallback {
-        protected Context mContext;
-        protected ImageView mIconView;
-        protected TextView mTextView;
-        protected Handler mHandler;
-        protected boolean mLastPulseLightToDark; // false = dark to light, true = light to dark
-        protected @BiometricState int mState;
-        protected boolean mDeactivated;
-
-        protected IconController(Context context, ImageView iconView, TextView textView) {
-            mContext = context;
-            mIconView = iconView;
-            mTextView = textView;
-            mHandler = new Handler(Looper.getMainLooper());
-            showStaticDrawable(R.drawable.face_dialog_pulse_dark_to_light);
-        }
-
-        protected void animateOnce(int iconRes) {
-            animateIcon(iconRes, false);
-        }
-
-        protected void showStaticDrawable(int iconRes) {
-            mIconView.setImageDrawable(mContext.getDrawable(iconRes));
-        }
-
-        protected void animateIcon(int iconRes, boolean repeat) {
-            Log.d(TAG, "animateIcon, state: " + mState + ", deactivated: " + mDeactivated);
-            if (mDeactivated) {
-                return;
-            }
-
-            final AnimatedVectorDrawable icon =
-                    (AnimatedVectorDrawable) mContext.getDrawable(iconRes);
-            mIconView.setImageDrawable(icon);
-            icon.forceAnimationOnUI();
-            if (repeat) {
-                icon.registerAnimationCallback(this);
-            }
-            icon.start();
-        }
-
-        protected void startPulsing() {
-            mLastPulseLightToDark = false;
-            animateIcon(R.drawable.face_dialog_pulse_dark_to_light, true);
-        }
-
-        protected void pulseInNextDirection() {
-            int iconRes = mLastPulseLightToDark ? R.drawable.face_dialog_pulse_dark_to_light
-                    : R.drawable.face_dialog_pulse_light_to_dark;
-            animateIcon(iconRes, true /* repeat */);
-            mLastPulseLightToDark = !mLastPulseLightToDark;
-        }
-
-        @Override
-        public void onAnimationEnd(Drawable drawable) {
-            super.onAnimationEnd(drawable);
-            Log.d(TAG, "onAnimationEnd, mState: " + mState + ", deactivated: " + mDeactivated);
-            if (mDeactivated) {
-                return;
-            }
-
-            if (mState == STATE_AUTHENTICATING || mState == STATE_HELP) {
-                pulseInNextDirection();
-            }
-        }
-
-        protected void deactivate() {
-            mDeactivated = true;
-        }
-
-        protected void updateState(int lastState, int newState) {
-            if (mDeactivated) {
-                Log.w(TAG, "Ignoring updateState when deactivated: " + newState);
-                return;
-            }
-
-            final boolean lastStateIsErrorIcon =
-                    lastState == STATE_ERROR || lastState == STATE_HELP;
-
-            if (newState == STATE_AUTHENTICATING_ANIMATING_IN) {
-                showStaticDrawable(R.drawable.face_dialog_pulse_dark_to_light);
-                mIconView.setContentDescription(mContext.getString(
-                        R.string.biometric_dialog_face_icon_description_authenticating));
-            } else if (newState == STATE_AUTHENTICATING) {
-                startPulsing();
-                mIconView.setContentDescription(mContext.getString(
-                        R.string.biometric_dialog_face_icon_description_authenticating));
-            } else if (lastState == STATE_PENDING_CONFIRMATION && newState == STATE_AUTHENTICATED) {
-                animateOnce(R.drawable.face_dialog_dark_to_checkmark);
-                mIconView.setContentDescription(mContext.getString(
-                        R.string.biometric_dialog_face_icon_description_confirmed));
-            } else if (lastStateIsErrorIcon && newState == STATE_IDLE) {
-                animateOnce(R.drawable.face_dialog_error_to_idle);
-                mIconView.setContentDescription(mContext.getString(
-                        R.string.biometric_dialog_face_icon_description_idle));
-            } else if (lastStateIsErrorIcon && newState == STATE_AUTHENTICATED) {
-                animateOnce(R.drawable.face_dialog_dark_to_checkmark);
-                mIconView.setContentDescription(mContext.getString(
-                        R.string.biometric_dialog_face_icon_description_authenticated));
-            } else if (newState == STATE_ERROR && lastState != STATE_ERROR) {
-                animateOnce(R.drawable.face_dialog_dark_to_error);
-            } else if (lastState == STATE_AUTHENTICATING && newState == STATE_AUTHENTICATED) {
-                animateOnce(R.drawable.face_dialog_dark_to_checkmark);
-                mIconView.setContentDescription(mContext.getString(
-                        R.string.biometric_dialog_face_icon_description_authenticated));
-            } else if (newState == STATE_PENDING_CONFIRMATION) {
-                animateOnce(R.drawable.face_dialog_wink_from_dark);
-                mIconView.setContentDescription(mContext.getString(
-                        R.string.biometric_dialog_face_icon_description_authenticated));
-            } else if (newState == STATE_IDLE) {
-                showStaticDrawable(R.drawable.face_dialog_idle_static);
-                mIconView.setContentDescription(mContext.getString(
-                        R.string.biometric_dialog_face_icon_description_idle));
-            } else {
-                Log.w(TAG, "Unhandled state: " + newState);
-            }
-            mState = newState;
-        }
-    }
-
-    @Nullable @VisibleForTesting IconController mFaceIconController;
-    @NonNull private final OnAttachStateChangeListener mOnAttachStateChangeListener =
-            new OnAttachStateChangeListener() {
-        @Override
-        public void onViewAttachedToWindow(View v) {
-
-        }
-
-        @Override
-        public void onViewDetachedFromWindow(View v) {
-            mFaceIconController.deactivate();
-        }
-    };
-
-    public AuthBiometricFaceView(Context context) {
-        this(context, null);
-    }
-
-    public AuthBiometricFaceView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    @VisibleForTesting
-    AuthBiometricFaceView(Context context, AttributeSet attrs, Injector injector) {
-        super(context, attrs, injector);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        mFaceIconController = new IconController(mContext, mIconView, mIndicatorView);
-
-        addOnAttachStateChangeListener(mOnAttachStateChangeListener);
-    }
-
-    @Override
-    protected int getDelayAfterAuthenticatedDurationMs() {
-        return HIDE_DELAY_MS;
-    }
-
-    @Override
-    protected int getStateForAfterError() {
-        return STATE_IDLE;
-    }
-
-    @Override
-    protected void handleResetAfterError() {
-        resetErrorView();
-    }
-
-    @Override
-    protected void handleResetAfterHelp() {
-        resetErrorView();
-    }
-
-    @Override
-    protected boolean supportsSmallDialog() {
-        return true;
-    }
-
-    @Override
-    protected boolean supportsManualRetry() {
-        return true;
-    }
-
-    @Override
-    public void updateState(@BiometricState int newState) {
-        mFaceIconController.updateState(mState, newState);
-
-        if (newState == STATE_AUTHENTICATING_ANIMATING_IN ||
-                (newState == STATE_AUTHENTICATING && getSize() == AuthDialog.SIZE_MEDIUM)) {
-            resetErrorView();
-        }
-
-        // Do this last since the state variable gets updated.
-        super.updateState(newState);
-    }
-
-    @Override
-    public void onAuthenticationFailed(@Modality int modality, @Nullable String failureReason) {
-        if (getSize() == AuthDialog.SIZE_MEDIUM) {
-            if (supportsManualRetry()) {
-                mTryAgainButton.setVisibility(View.VISIBLE);
-                mConfirmButton.setVisibility(View.GONE);
-            }
-        }
-
-        // Do this last since we want to know if the button is being animated (in the case of
-        // small -> medium dialog)
-        super.onAuthenticationFailed(modality, failureReason);
-    }
-
-    private void resetErrorView() {
-        mIndicatorView.setTextColor(mTextColorHint);
-        mIndicatorView.setVisibility(View.INVISIBLE);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.kt
new file mode 100644
index 0000000..be89d10
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2019 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.biometrics
+
+import android.content.Context
+import android.hardware.biometrics.BiometricAuthenticator.Modality
+import android.util.AttributeSet
+
+/** Face only view for BiometricPrompt. */
+class AuthBiometricFaceView(
+    context: Context,
+    attrs: AttributeSet? = null
+) : AuthBiometricView(context, attrs) {
+
+    override fun getDelayAfterAuthenticatedDurationMs() = HIDE_DELAY_MS
+
+    override fun getStateForAfterError() = STATE_IDLE
+
+    override fun handleResetAfterError() = resetErrorView()
+
+    override fun handleResetAfterHelp() = resetErrorView()
+
+    override fun supportsSmallDialog() = true
+
+    override fun supportsManualRetry() = true
+
+    override fun supportsRequireConfirmation() = true
+
+    override fun createIconController(): AuthIconController =
+        AuthBiometricFaceIconController(mContext, mIconView)
+
+    override fun updateState(@BiometricState newState: Int) {
+        if (newState == STATE_AUTHENTICATING_ANIMATING_IN ||
+            newState == STATE_AUTHENTICATING && size == AuthDialog.SIZE_MEDIUM) {
+            resetErrorView()
+        }
+
+        // Do this last since the state variable gets updated.
+        super.updateState(newState)
+    }
+
+    override fun onAuthenticationFailed(
+        @Modality modality: Int,
+        failureReason: String?
+    ) {
+        if (size == AuthDialog.SIZE_MEDIUM) {
+            if (supportsManualRetry()) {
+                mTryAgainButton.visibility = VISIBLE
+                mConfirmButton.visibility = GONE
+            }
+        }
+
+        // Do this last since we want to know if the button is being animated (in the case of
+        // small -> medium dialog)
+        super.onAuthenticationFailed(modality, failureReason)
+    }
+
+    private fun resetErrorView() {
+        mIndicatorView.setTextColor(mTextColorHint)
+        mIndicatorView.visibility = INVISIBLE
+    }
+
+    companion object {
+        /** Delay before dismissing after being authenticated/confirmed. */
+        const val HIDE_DELAY_MS = 500
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
new file mode 100644
index 0000000..3e4e573
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2022 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.biometrics
+
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.widget.ImageView
+import com.android.systemui.R
+import com.android.systemui.biometrics.AuthBiometricView.BiometricState
+import com.android.systemui.biometrics.AuthBiometricView.STATE_PENDING_CONFIRMATION
+import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATED
+import com.android.systemui.biometrics.AuthBiometricView.STATE_ERROR
+import com.android.systemui.biometrics.AuthBiometricView.STATE_HELP
+
+/** Face/Fingerprint combined icon animator for BiometricPrompt. */
+class AuthBiometricFingerprintAndFaceIconController(
+    context: Context,
+    iconView: ImageView
+) : AuthBiometricFingerprintIconController(context, iconView) {
+
+    override val actsAsConfirmButton: Boolean = true
+
+    override fun shouldAnimateForTransition(
+        @BiometricState oldState: Int,
+        @BiometricState newState: Int
+    ): Boolean = when (newState) {
+        STATE_PENDING_CONFIRMATION -> true
+        STATE_AUTHENTICATED -> false
+        else -> super.shouldAnimateForTransition(oldState, newState)
+    }
+
+    override fun getAnimationForTransition(
+        @BiometricState oldState: Int,
+        @BiometricState newState: Int
+    ): Drawable? = when (newState) {
+        STATE_PENDING_CONFIRMATION -> {
+            if (oldState == STATE_ERROR || oldState == STATE_HELP) {
+                context.getDrawable(R.drawable.fingerprint_dialog_error_to_unlock)
+            } else {
+                context.getDrawable(R.drawable.fingerprint_dialog_fp_to_unlock)
+            }
+        }
+        STATE_AUTHENTICATED -> null
+        else -> super.getAnimationForTransition(oldState, newState)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceView.kt
new file mode 100644
index 0000000..7371442
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceView.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 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.biometrics
+
+import android.content.Context
+import android.hardware.biometrics.BiometricAuthenticator.Modality
+import android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE
+import android.util.AttributeSet
+import com.android.systemui.R
+
+/** Face/Fingerprint combined view for BiometricPrompt. */
+class AuthBiometricFingerprintAndFaceView(
+    context: Context,
+    attrs: AttributeSet?
+) : AuthBiometricFingerprintView(context, attrs) {
+
+    constructor (context: Context) : this(context, null)
+
+    override fun getConfirmationPrompt() = R.string.biometric_dialog_tap_confirm_with_face
+
+    override fun forceRequireConfirmation(@Modality modality: Int) = modality == TYPE_FACE
+
+    override fun ignoreUnsuccessfulEventsFrom(@Modality modality: Int) = modality == TYPE_FACE
+
+    override fun onPointerDown(failedModalities: Set<Int>) = failedModalities.contains(TYPE_FACE)
+
+    override fun createIconController(): AuthIconController =
+        AuthBiometricFingerprintAndFaceIconController(mContext, mIconView)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
new file mode 100644
index 0000000..cd16379
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2022 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.biometrics
+
+import android.content.Context
+import android.graphics.drawable.AnimatedVectorDrawable
+import android.graphics.drawable.Drawable
+import android.widget.ImageView
+import com.android.systemui.R
+import com.android.systemui.biometrics.AuthBiometricView.BiometricState
+import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATED
+import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATING
+import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATING_ANIMATING_IN
+import com.android.systemui.biometrics.AuthBiometricView.STATE_ERROR
+import com.android.systemui.biometrics.AuthBiometricView.STATE_HELP
+import com.android.systemui.biometrics.AuthBiometricView.STATE_IDLE
+import com.android.systemui.biometrics.AuthBiometricView.STATE_PENDING_CONFIRMATION
+
+/** Fingerprint only icon animator for BiometricPrompt.  */
+open class AuthBiometricFingerprintIconController(
+    context: Context,
+    iconView: ImageView
+) : AuthIconController(context, iconView) {
+
+    init {
+        val size = context.resources.getDimensionPixelSize(
+            R.dimen.biometric_dialog_fingerprint_icon_size
+        )
+        iconView.layoutParams.width = size
+        iconView.layoutParams.height = size
+    }
+
+    override fun updateIcon(@BiometricState lastState: Int, @BiometricState newState: Int) {
+        val icon = getAnimationForTransition(lastState, newState) ?: return
+
+        iconView.setImageDrawable(icon)
+
+        val iconContentDescription = getIconContentDescription(newState)
+        if (iconContentDescription != null) {
+            iconView.contentDescription = iconContentDescription
+        }
+
+        (icon as? AnimatedVectorDrawable)?.apply {
+            reset()
+            if (shouldAnimateForTransition(lastState, newState)) {
+                forceAnimationOnUI()
+                start()
+            }
+        }
+    }
+
+    private fun getIconContentDescription(@BiometricState newState: Int): CharSequence? {
+        val id = when (newState) {
+            STATE_IDLE,
+            STATE_AUTHENTICATING_ANIMATING_IN,
+            STATE_AUTHENTICATING,
+            STATE_PENDING_CONFIRMATION,
+            STATE_AUTHENTICATED -> R.string.accessibility_fingerprint_dialog_fingerprint_icon
+            STATE_ERROR,
+            STATE_HELP -> R.string.biometric_dialog_try_again
+            else -> null
+        }
+        return if (id != null) context.getString(id) else null
+    }
+
+    protected open fun shouldAnimateForTransition(
+        @BiometricState oldState: Int,
+        @BiometricState newState: Int
+    ) = when (newState) {
+        STATE_HELP,
+        STATE_ERROR -> true
+        STATE_AUTHENTICATING_ANIMATING_IN,
+        STATE_AUTHENTICATING -> oldState == STATE_ERROR || oldState == STATE_HELP
+        STATE_AUTHENTICATED -> false
+        else -> false
+    }
+
+    protected open fun getAnimationForTransition(
+        @BiometricState oldState: Int,
+        @BiometricState newState: Int
+    ): Drawable? {
+        val id = when (newState) {
+            STATE_HELP,
+            STATE_ERROR -> R.drawable.fingerprint_dialog_fp_to_error
+            STATE_AUTHENTICATING_ANIMATING_IN,
+            STATE_AUTHENTICATING -> {
+                if (oldState == STATE_ERROR || oldState == STATE_HELP) {
+                    R.drawable.fingerprint_dialog_error_to_fp
+                } else {
+                    R.drawable.fingerprint_dialog_fp_to_error
+                }
+            }
+            STATE_AUTHENTICATED -> R.drawable.fingerprint_dialog_fp_to_error
+            else -> return null
+        }
+        return if (id != null) context.getDrawable(id) else null
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.java
deleted file mode 100644
index ee602bc..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.java
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * Copyright (C) 2019 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.biometrics;
-
-
-import android.content.Context;
-import android.graphics.drawable.AnimatedVectorDrawable;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.util.Log;
-
-import androidx.annotation.Nullable;
-
-import com.android.systemui.R;
-
-public class AuthBiometricFingerprintView extends AuthBiometricView {
-
-    private static final String TAG = "BiometricPrompt/AuthBiometricFingerprintView";
-
-    public AuthBiometricFingerprintView(Context context) {
-        this(context, null);
-    }
-
-    public AuthBiometricFingerprintView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    @Override
-    protected int getDelayAfterAuthenticatedDurationMs() {
-        return 0;
-    }
-
-    @Override
-    protected int getStateForAfterError() {
-        return STATE_AUTHENTICATING;
-    }
-
-    @Override
-    protected void handleResetAfterError() {
-        showTouchSensorString();
-    }
-
-    @Override
-    protected void handleResetAfterHelp() {
-        showTouchSensorString();
-    }
-
-    @Override
-    protected boolean supportsSmallDialog() {
-        return false;
-    }
-
-    @Override
-    public void updateState(@BiometricState int newState) {
-        updateIcon(mState, newState);
-
-        // Do this last since the state variable gets updated.
-        super.updateState(newState);
-    }
-
-    @Override
-    void onAttachedToWindowInternal() {
-        super.onAttachedToWindowInternal();
-        showTouchSensorString();
-    }
-
-    private void showTouchSensorString() {
-        mIndicatorView.setText(R.string.fingerprint_dialog_touch_sensor);
-        mIndicatorView.setTextColor(mTextColorHint);
-    }
-
-    private void updateIcon(int lastState, int newState) {
-        final Drawable icon = getAnimationForTransition(lastState, newState);
-        if (icon == null) {
-            Log.e(TAG, "Animation not found, " + lastState + " -> " + newState);
-            return;
-        }
-
-        final AnimatedVectorDrawable animation = icon instanceof AnimatedVectorDrawable
-                ? (AnimatedVectorDrawable) icon
-                : null;
-
-        mIconView.setImageDrawable(icon);
-
-        final CharSequence iconContentDescription = getIconContentDescription(newState);
-        if (iconContentDescription != null) {
-            mIconView.setContentDescription(iconContentDescription);
-        }
-
-        if (animation != null && shouldAnimateForTransition(lastState, newState)) {
-            animation.forceAnimationOnUI();
-            animation.start();
-        }
-    }
-
-    @Nullable
-    private CharSequence getIconContentDescription(int newState) {
-        switch (newState) {
-            case STATE_IDLE:
-            case STATE_AUTHENTICATING_ANIMATING_IN:
-            case STATE_AUTHENTICATING:
-            case STATE_PENDING_CONFIRMATION:
-            case STATE_AUTHENTICATED:
-                return mContext.getString(
-                        R.string.accessibility_fingerprint_dialog_fingerprint_icon);
-
-            case STATE_ERROR:
-            case STATE_HELP:
-                return mContext.getString(R.string.biometric_dialog_try_again);
-
-            default:
-                return null;
-        }
-    }
-
-    private boolean shouldAnimateForTransition(int oldState, int newState) {
-        switch (newState) {
-            case STATE_HELP:
-            case STATE_ERROR:
-                return true;
-            case STATE_AUTHENTICATING_ANIMATING_IN:
-            case STATE_AUTHENTICATING:
-                if (oldState == STATE_ERROR || oldState == STATE_HELP) {
-                    return true;
-                } else {
-                    return false;
-                }
-            case STATE_AUTHENTICATED:
-                return false;
-            default:
-                return false;
-        }
-    }
-
-    private Drawable getAnimationForTransition(int oldState, int newState) {
-        int iconRes;
-
-        switch (newState) {
-            case STATE_HELP:
-            case STATE_ERROR:
-                iconRes = R.drawable.fingerprint_dialog_fp_to_error;
-                break;
-            case STATE_AUTHENTICATING_ANIMATING_IN:
-            case STATE_AUTHENTICATING:
-                if (oldState == STATE_ERROR || oldState == STATE_HELP) {
-                    iconRes = R.drawable.fingerprint_dialog_error_to_fp;
-                } else {
-                    iconRes = R.drawable.fingerprint_dialog_fp_to_error;
-                }
-                break;
-            case STATE_AUTHENTICATED:
-                iconRes = R.drawable.fingerprint_dialog_fp_to_error;
-                break;
-            default:
-                return null;
-        }
-
-        return mContext.getDrawable(iconRes);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt
new file mode 100644
index 0000000..368bc3a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2019 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.biometrics
+
+import android.content.Context
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
+import android.util.AttributeSet
+import android.util.Log
+import android.widget.FrameLayout
+import android.widget.TextView
+import com.android.systemui.R
+
+private const val TAG = "AuthBiometricFingerprintView"
+
+/** Fingerprint only view for BiometricPrompt.  */
+open class AuthBiometricFingerprintView(
+    context: Context,
+    attrs: AttributeSet? = null
+) : AuthBiometricView(context, attrs) {
+    /** If this view is for a UDFPS sensor.  */
+    var isUdfps = false
+        private set
+
+    private var udfpsAdapter: UdfpsDialogMeasureAdapter? = null
+
+    /** Set the [sensorProps] of this sensor so the view can be customized prior to layout. */
+    fun setSensorProperties(sensorProps: FingerprintSensorPropertiesInternal) {
+        isUdfps = sensorProps.isAnyUdfpsType
+        udfpsAdapter = if (isUdfps) UdfpsDialogMeasureAdapter(this, sensorProps) else null
+    }
+
+    override fun onMeasureInternal(width: Int, height: Int): AuthDialog.LayoutParams {
+        val layoutParams = super.onMeasureInternal(width, height)
+        return udfpsAdapter?.onMeasureInternal(width, height, layoutParams) ?: layoutParams
+    }
+
+    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
+        super.onLayout(changed, left, top, right, bottom)
+
+        val adapter = udfpsAdapter
+        if (adapter != null) {
+            // Move the UDFPS icon and indicator text if necessary. This probably only needs to happen
+            // for devices where the UDFPS sensor is too low.
+            // TODO(b/201510778): Update this logic to support cases where the sensor or text overlap
+            //  the button bar area.
+            val bottomSpacerHeight = adapter.bottomSpacerHeight
+            Log.w(TAG, "bottomSpacerHeight: $bottomSpacerHeight")
+            if (bottomSpacerHeight < 0) {
+                val iconFrame = findViewById<FrameLayout>(R.id.biometric_icon_frame)!!
+                iconFrame.translationY = -bottomSpacerHeight.toFloat()
+                val indicator = findViewById<TextView>(R.id.indicator)!!
+                indicator.translationY = -bottomSpacerHeight.toFloat()
+            }
+        }
+    }
+
+    override fun getDelayAfterAuthenticatedDurationMs() = 0
+
+    override fun getStateForAfterError() = STATE_AUTHENTICATING
+
+    override fun handleResetAfterError() = showTouchSensorString()
+
+    override fun handleResetAfterHelp() = showTouchSensorString()
+
+    override fun supportsSmallDialog() = false
+
+    override fun createIconController(): AuthIconController =
+        AuthBiometricFingerprintIconController(mContext, mIconView)
+
+    override fun onAttachedToWindow() {
+        super.onAttachedToWindow()
+        showTouchSensorString()
+    }
+
+    private fun showTouchSensorString() {
+        mIndicatorView.setText(R.string.fingerprint_dialog_touch_sensor)
+        mIndicatorView.setTextColor(mTextColorHint)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt
new file mode 100644
index 0000000..ce5e600
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2022 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.biometrics
+
+import android.annotation.DrawableRes
+import android.content.Context
+import android.graphics.drawable.Animatable2
+import android.graphics.drawable.AnimatedVectorDrawable
+import android.graphics.drawable.Drawable
+import android.util.Log
+import android.widget.ImageView
+import com.android.systemui.biometrics.AuthBiometricView.BiometricState
+
+private const val TAG = "AuthIconController"
+
+/** Controller for animating the BiometricPrompt icon/affordance. */
+abstract class AuthIconController(
+    protected val context: Context,
+    protected val iconView: ImageView
+) : Animatable2.AnimationCallback() {
+
+    /** If this controller should ignore events and pause. */
+    var deactivated: Boolean = false
+
+    /** If the icon view should be treated as an alternate "confirm" button. */
+    open val actsAsConfirmButton: Boolean = false
+
+    final override fun onAnimationStart(drawable: Drawable) {
+        super.onAnimationStart(drawable)
+    }
+
+    final override fun onAnimationEnd(drawable: Drawable) {
+        super.onAnimationEnd(drawable)
+
+        if (!deactivated) {
+            handleAnimationEnd(drawable)
+        }
+    }
+
+    /** Set the icon to a static image. */
+    protected fun showStaticDrawable(@DrawableRes iconRes: Int) {
+        iconView.setImageDrawable(context.getDrawable(iconRes))
+    }
+
+    /** Animate a resource. */
+    protected fun animateIconOnce(@DrawableRes iconRes: Int) {
+        animateIcon(iconRes, false)
+    }
+
+    /** Animate a resource. */
+    protected fun animateIcon(@DrawableRes iconRes: Int, repeat: Boolean) {
+        if (!deactivated) {
+            val icon = context.getDrawable(iconRes) as AnimatedVectorDrawable
+            iconView.setImageDrawable(icon)
+            icon.forceAnimationOnUI()
+            if (repeat) {
+                icon.registerAnimationCallback(this)
+            }
+            icon.start()
+        }
+    }
+
+    /** Update the icon to reflect the [newState]. */
+    fun updateState(@BiometricState lastState: Int, @BiometricState newState: Int) {
+        if (deactivated) {
+            Log.w(TAG, "Ignoring updateState when deactivated: $newState")
+        } else {
+            updateIcon(lastState, newState)
+        }
+    }
+
+    /** If the icon should act as a "retry" button in the [currentState]. */
+    fun iconTapSendsRetryWhen(@BiometricState currentState: Int): Boolean = false
+
+    /** Call during [updateState] if the controller is not [deactivated]. */
+    abstract fun updateIcon(@BiometricState lastState: Int, @BiometricState newState: Int)
+
+    /** Called during [onAnimationEnd] if the controller is not [deactivated]. */
+    open fun handleAnimationEnd(drawable: Drawable) {}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricUdfpsView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricUdfpsView.java
deleted file mode 100644
index d80d9cc..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricUdfpsView.java
+++ /dev/null
@@ -1,80 +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.systemui.biometrics;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.widget.FrameLayout;
-import android.widget.TextView;
-
-import com.android.systemui.R;
-
-/**
- * Manages the layout for under-display fingerprint sensors (UDFPS). Ensures that UI elements
- * do not overlap with
- */
-public class AuthBiometricUdfpsView extends AuthBiometricFingerprintView {
-    private static final String TAG = "AuthBiometricUdfpsView";
-
-    @Nullable private UdfpsDialogMeasureAdapter mMeasureAdapter;
-
-    public AuthBiometricUdfpsView(Context context) {
-        this(context, null /* attrs */);
-    }
-
-    public AuthBiometricUdfpsView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    void setSensorProps(@NonNull FingerprintSensorPropertiesInternal sensorProps) {
-        if (mMeasureAdapter == null || mMeasureAdapter.getSensorProps() != sensorProps) {
-            mMeasureAdapter = new UdfpsDialogMeasureAdapter(this, sensorProps);
-        }
-    }
-
-    @Override
-    @NonNull
-    AuthDialog.LayoutParams onMeasureInternal(int width, int height) {
-        final AuthDialog.LayoutParams layoutParams = super.onMeasureInternal(width, height);
-        return mMeasureAdapter != null
-                ? mMeasureAdapter.onMeasureInternal(width, height, layoutParams)
-                : layoutParams;
-    }
-
-    @Override
-    void onLayoutInternal() {
-        super.onLayoutInternal();
-
-        // Move the UDFPS icon and indicator text if necessary. This probably only needs to happen
-        // for devices where the UDFPS sensor is too low.
-        // TODO(b/201510778): Update this logic to support cases where the sensor or text overlap
-        //  the button bar area.
-        final int bottomSpacerHeight = mMeasureAdapter.getBottomSpacerHeight();
-        Log.w(TAG, "bottomSpacerHeight: " + bottomSpacerHeight);
-        if (bottomSpacerHeight < 0) {
-            FrameLayout iconFrame = findViewById(R.id.biometric_icon_frame);
-            iconFrame.setTranslationY(-bottomSpacerHeight);
-
-            TextView indicator = findViewById(R.id.indicator);
-            indicator.setTranslationY(-bottomSpacerHeight);
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
index 1496f17..76d4aa8 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
@@ -25,6 +25,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.StringRes;
 import android.content.Context;
 import android.hardware.biometrics.BiometricAuthenticator.Modality;
 import android.hardware.biometrics.BiometricPrompt;
@@ -44,19 +45,21 @@
 import android.widget.TextView;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.widget.LockPatternUtils;
 import com.android.systemui.R;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 
 /**
- * Contains the Biometric views (title, subtitle, icon, buttons, etc) and its controllers.
+ * Contains the Biometric views (title, subtitle, icon, buttons, etc.) and its controllers.
  */
-public abstract class AuthBiometricView extends LinearLayout {
+public class AuthBiometricView extends LinearLayout {
 
-    private static final String TAG = "BiometricPrompt/AuthBiometricView";
+    private static final String TAG = "AuthBiometricView";
 
     /**
      * Authentication hardware idle.
@@ -102,13 +105,6 @@
         int ACTION_BUTTON_TRY_AGAIN = 4;
         int ACTION_ERROR = 5;
         int ACTION_USE_DEVICE_CREDENTIAL = 6;
-        /**
-         * Notify the receiver to start the fingerprint sensor.
-         *
-         * This is only applicable to multi-sensor devices that need to delay fingerprint auth
-         * (i.e face -> fingerprint).
-         */
-        int ACTION_START_DELAYED_FINGERPRINT_SENSOR = 7;
 
         /**
          * When an action has occurred. The caller will only invoke this when the callback should
@@ -118,66 +114,9 @@
         void onAction(int action);
     }
 
-    @VisibleForTesting
-    static class Injector {
-        AuthBiometricView mBiometricView;
-
-        public Button getNegativeButton() {
-            return mBiometricView.findViewById(R.id.button_negative);
-        }
-
-        public Button getCancelButton() {
-            return mBiometricView.findViewById(R.id.button_cancel);
-        }
-
-        public Button getUseCredentialButton() {
-            return mBiometricView.findViewById(R.id.button_use_credential);
-        }
-
-        public Button getConfirmButton() {
-            return mBiometricView.findViewById(R.id.button_confirm);
-        }
-
-        public Button getTryAgainButton() {
-            return mBiometricView.findViewById(R.id.button_try_again);
-        }
-
-        public TextView getTitleView() {
-            return mBiometricView.findViewById(R.id.title);
-        }
-
-        public TextView getSubtitleView() {
-            return mBiometricView.findViewById(R.id.subtitle);
-        }
-
-        public TextView getDescriptionView() {
-            return mBiometricView.findViewById(R.id.description);
-        }
-
-        public TextView getIndicatorView() {
-            return mBiometricView.findViewById(R.id.indicator);
-        }
-
-        public ImageView getIconView() {
-            return mBiometricView.findViewById(R.id.biometric_icon);
-        }
-
-        public View getIconHolderView() {
-            return mBiometricView.findViewById(R.id.biometric_icon_frame);
-        }
-
-        public int getDelayAfterError() {
-            return BiometricPrompt.HIDE_DIALOG_DELAY;
-        }
-
-        public int getMediumToLargeAnimationDurationMs() {
-            return AuthDialog.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS;
-        }
-    }
-
-    private final Injector mInjector;
     protected final Handler mHandler;
     private final AccessibilityManager mAccessibilityManager;
+    private final LockPatternUtils mLockPatternUtils;
     protected final int mTextColorError;
     protected final int mTextColorHint;
 
@@ -195,6 +134,11 @@
     protected ImageView mIconView;
     protected TextView mIndicatorView;
 
+    @VisibleForTesting @NonNull AuthIconController mIconController;
+    @VisibleForTesting int mAnimationDurationShort = AuthDialog.ANIMATE_SMALL_TO_MEDIUM_DURATION_MS;
+    @VisibleForTesting int mAnimationDurationLong = AuthDialog.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS;
+    @VisibleForTesting int mAnimationDurationHideDialog = BiometricPrompt.HIDE_DIALOG_DELAY;
+
     // Negative button position, exclusively for the app-specified behavior
     @VisibleForTesting Button mNegativeButton;
     // Negative button position, exclusively for cancelling auth after passive auth success
@@ -217,30 +161,7 @@
     protected boolean mDialogSizeAnimating;
     protected Bundle mSavedState;
 
-    /**
-     * Delay after authentication is confirmed, before the dialog should be animated away.
-     */
-    protected abstract int getDelayAfterAuthenticatedDurationMs();
-    /**
-     * State that the dialog/icon should be in after showing a help message.
-     */
-    protected abstract int getStateForAfterError();
-    /**
-     * Invoked when the error message is being cleared.
-     */
-    protected abstract void handleResetAfterError();
-    /**
-     * Invoked when the help message is being cleared.
-     */
-    protected abstract void handleResetAfterHelp();
-
-    /**
-     * @return true if the dialog supports {@link AuthDialog.DialogSize#SIZE_SMALL}
-     */
-    protected abstract boolean supportsSmallDialog();
-
     private final Runnable mResetErrorRunnable;
-
     private final Runnable mResetHelpRunnable;
 
     private final OnClickListener mBackgroundClickListener = (view) -> {
@@ -262,11 +183,6 @@
     }
 
     public AuthBiometricView(Context context, AttributeSet attrs) {
-        this(context, attrs, new Injector());
-    }
-
-    @VisibleForTesting
-    AuthBiometricView(Context context, AttributeSet attrs, Injector injector) {
         super(context, attrs);
         mHandler = new Handler(Looper.getMainLooper());
         mTextColorError = getResources().getColor(
@@ -274,10 +190,8 @@
         mTextColorHint = getResources().getColor(
                 R.color.biometric_dialog_gray, context.getTheme());
 
-        mInjector = injector;
-        mInjector.mBiometricView = this;
-
         mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
+        mLockPatternUtils = new LockPatternUtils(context);
 
         mResetErrorRunnable = () -> {
             updateState(getStateForAfterError());
@@ -292,36 +206,91 @@
         };
     }
 
-    public void setPanelController(AuthPanelController panelController) {
+    /** Delay after authentication is confirmed, before the dialog should be animated away. */
+    protected int getDelayAfterAuthenticatedDurationMs() {
+        return 0;
+    }
+
+    /** State that the dialog/icon should be in after showing a help message. */
+    protected int getStateForAfterError() {
+        return STATE_IDLE;
+    }
+
+    /** Invoked when the error message is being cleared. */
+    protected void handleResetAfterError() {}
+
+    /** Invoked when the help message is being cleared. */
+    protected void handleResetAfterHelp() {}
+
+    /** True if the dialog supports {@link AuthDialog.DialogSize#SIZE_SMALL}. */
+    protected boolean supportsSmallDialog() {
+        return false;
+    }
+
+    /** The string to show when the user must tap to confirm via the button or icon. */
+    @StringRes
+    protected int getConfirmationPrompt() {
+        return R.string.biometric_dialog_tap_confirm;
+    }
+
+    /** True if require confirmation will be honored when set via the API. */
+    protected boolean supportsRequireConfirmation() {
+        return false;
+    }
+
+    /** True if confirmation will be required even if it was not supported/requested. */
+    protected boolean forceRequireConfirmation(@Modality int modality) {
+        return false;
+    }
+
+    /** Ignore all events from this (secondary) modality except successful authentication. */
+    protected boolean ignoreUnsuccessfulEventsFrom(@Modality int modality) {
+        return false;
+    }
+
+    /**
+     * Create the controller for managing the icons transitions during the prompt.
+     *
+     * Subclass should override.
+     */
+    @NonNull
+    protected AuthIconController createIconController() {
+        return new AuthIconController(mContext, mIconView) {
+            @Override
+            public void updateIcon(int lastState, int newState) {}
+        };
+    }
+
+    void setPanelController(AuthPanelController panelController) {
         mPanelController = panelController;
     }
 
-    public void setPromptInfo(PromptInfo promptInfo) {
+    void setPromptInfo(PromptInfo promptInfo) {
         mPromptInfo = promptInfo;
     }
 
-    public void setCallback(Callback callback) {
+    void setCallback(Callback callback) {
         mCallback = callback;
     }
 
-    public void setBackgroundView(View backgroundView) {
+    void setBackgroundView(View backgroundView) {
         backgroundView.setOnClickListener(mBackgroundClickListener);
     }
 
-    public void setUserId(int userId) {
+    void setUserId(int userId) {
         mUserId = userId;
     }
 
-    public void setEffectiveUserId(int effectiveUserId) {
+    void setEffectiveUserId(int effectiveUserId) {
         mEffectiveUserId = effectiveUserId;
     }
 
-    public void setRequireConfirmation(boolean requireConfirmation) {
-        mRequireConfirmation = requireConfirmation;
+    void setRequireConfirmation(boolean requireConfirmation) {
+        mRequireConfirmation = requireConfirmation && supportsRequireConfirmation();
     }
 
     @VisibleForTesting
-    void updateSize(@AuthDialog.DialogSize int newSize) {
+    final void updateSize(@AuthDialog.DialogSize int newSize) {
         Log.v(TAG, "Current size: " + mSize + " New size: " + newSize);
         if (newSize == AuthDialog.SIZE_SMALL) {
             mTitleView.setVisibility(View.GONE);
@@ -376,7 +345,7 @@
 
             // Choreograph together
             final AnimatorSet as = new AnimatorSet();
-            as.setDuration(AuthDialog.ANIMATE_SMALL_TO_MEDIUM_DURATION_MS);
+            as.setDuration(mAnimationDurationShort);
             as.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationStart(Animator animation) {
@@ -429,7 +398,7 @@
             // Translate at full duration
             final ValueAnimator translationAnimator = ValueAnimator.ofFloat(
                     biometricView.getY(), biometricView.getY() - translationY);
-            translationAnimator.setDuration(mInjector.getMediumToLargeAnimationDurationMs());
+            translationAnimator.setDuration(mAnimationDurationLong);
             translationAnimator.addUpdateListener((animation) -> {
                 final float translation = (float) animation.getAnimatedValue();
                 biometricView.setTranslationY(translation);
@@ -438,7 +407,7 @@
                 @Override
                 public void onAnimationEnd(Animator animation) {
                     super.onAnimationEnd(animation);
-                    if (biometricView.getParent() != null) {
+                    if (biometricView.getParent() instanceof ViewGroup) {
                         ((ViewGroup) biometricView.getParent()).removeView(biometricView);
                     }
                     mSize = newSize;
@@ -447,7 +416,7 @@
 
             // Opacity to 0 in half duration
             final ValueAnimator opacityAnimator = ValueAnimator.ofFloat(1, 0);
-            opacityAnimator.setDuration(mInjector.getMediumToLargeAnimationDurationMs() / 2);
+            opacityAnimator.setDuration(mAnimationDurationLong / 2);
             opacityAnimator.addUpdateListener((animation) -> {
                 final float opacity = (float) animation.getAnimatedValue();
                 biometricView.setAlpha(opacity);
@@ -457,7 +426,7 @@
             mPanelController.updateForContentDimensions(
                     mPanelController.getContainerWidth(),
                     mPanelController.getContainerHeight(),
-                    mInjector.getMediumToLargeAnimationDurationMs());
+                    mAnimationDurationLong);
 
             // Start the animations together
             AnimatorSet as = new AnimatorSet();
@@ -466,7 +435,7 @@
             animators.add(opacityAnimator);
 
             as.playTogether(animators);
-            as.setDuration(mInjector.getMediumToLargeAnimationDurationMs() * 2 / 3);
+            as.setDuration(mAnimationDurationLong * 2 / 3);
             as.start();
         } else {
             Log.e(TAG, "Unknown transition from: " + mSize + " to: " + newSize);
@@ -481,6 +450,8 @@
     public void updateState(@BiometricState int newState) {
         Log.v(TAG, "newState: " + newState);
 
+        mIconController.updateState(mState, newState);
+
         switch (newState) {
             case STATE_AUTHENTICATING_ANIMATING_IN:
             case STATE_AUTHENTICATING:
@@ -510,10 +481,11 @@
                 mNegativeButton.setVisibility(View.GONE);
                 mCancelButton.setVisibility(View.VISIBLE);
                 mUseCredentialButton.setVisibility(View.GONE);
-                mConfirmButton.setEnabled(true);
-                mConfirmButton.setVisibility(View.VISIBLE);
+                // forced confirmations (multi-sensor) use the icon view as the confirm button
+                mConfirmButton.setEnabled(mRequireConfirmation);
+                mConfirmButton.setVisibility(mRequireConfirmation ? View.VISIBLE : View.GONE);
                 mIndicatorView.setTextColor(mTextColorHint);
-                mIndicatorView.setText(R.string.biometric_dialog_tap_confirm);
+                mIndicatorView.setText(getConfirmationPrompt());
                 mIndicatorView.setVisibility(View.VISIBLE);
                 break;
 
@@ -536,9 +508,9 @@
         updateState(STATE_AUTHENTICATING);
     }
 
-    public void onAuthenticationSucceeded() {
+    public void onAuthenticationSucceeded(@Modality int modality) {
         removePendingAnimations();
-        if (mRequireConfirmation) {
+        if (mRequireConfirmation || forceRequireConfirmation(modality)) {
             updateState(STATE_PENDING_CONFIRMATION);
         } else {
             updateState(STATE_AUTHENTICATED);
@@ -553,6 +525,10 @@
      */
     public void onAuthenticationFailed(
             @Modality int modality, @Nullable String failureReason) {
+        if (ignoreUnsuccessfulEventsFrom(modality)) {
+            return;
+        }
+
         showTemporaryMessage(failureReason, mResetErrorRunnable);
         updateState(STATE_ERROR);
     }
@@ -564,12 +540,27 @@
      * @param error message
      */
     public void onError(@Modality int modality, String error) {
+        if (ignoreUnsuccessfulEventsFrom(modality)) {
+            return;
+        }
+
         showTemporaryMessage(error, mResetErrorRunnable);
         updateState(STATE_ERROR);
 
-        mHandler.postDelayed(() -> {
-            mCallback.onAction(Callback.ACTION_ERROR);
-        }, mInjector.getDelayAfterError());
+        mHandler.postDelayed(() -> mCallback.onAction(Callback.ACTION_ERROR),
+                mAnimationDurationHideDialog);
+    }
+
+    /**
+     * Fingerprint pointer down event. This does nothing by default and will not be called if the
+     * device does not have an appropriate sensor (UDFPS), but it may be used as an alternative
+     * to the "retry" button when fingerprint is used with other modalities.
+     *
+     * @param failedModalities the set of modalities that have failed
+     * @return true if a retry was initiated as a result of this event
+     */
+    public boolean onPointerDown(Set<Integer> failedModalities) {
+        return false;
     }
 
     /**
@@ -579,6 +570,9 @@
      * @param help message
      */
     public void onHelp(@Modality int modality, String help) {
+        if (ignoreUnsuccessfulEventsFrom(modality)) {
+            return;
+        }
         if (mSize != AuthDialog.SIZE_MEDIUM) {
             Log.w(TAG, "Help received in size: " + mSize);
             return;
@@ -639,7 +633,7 @@
         // select to enable marquee unless a screen reader is enabled
         mIndicatorView.setSelected(!mAccessibilityManager.isEnabled()
                 || !mAccessibilityManager.isTouchExplorationEnabled());
-        mHandler.postDelayed(resetMessageRunnable, mInjector.getDelayAfterError());
+        mHandler.postDelayed(resetMessageRunnable, mAnimationDurationHideDialog);
 
         Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this);
     }
@@ -647,29 +641,22 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        onFinishInflateInternal();
-    }
 
-    /**
-     * After inflation, but before things like restoreState, onAttachedToWindow, etc.
-     */
-    @VisibleForTesting
-    void onFinishInflateInternal() {
-        mTitleView = mInjector.getTitleView();
-        mSubtitleView = mInjector.getSubtitleView();
-        mDescriptionView = mInjector.getDescriptionView();
-        mIconView = mInjector.getIconView();
-        mIconHolderView = mInjector.getIconHolderView();
-        mIndicatorView = mInjector.getIndicatorView();
+        mTitleView = findViewById(R.id.title);
+        mSubtitleView = findViewById(R.id.subtitle);
+        mDescriptionView = findViewById(R.id.description);
+        mIconView = findViewById(R.id.biometric_icon);
+        mIconHolderView = findViewById(R.id.biometric_icon_frame);
+        mIndicatorView = findViewById(R.id.indicator);
 
         // Negative-side (left) buttons
-        mNegativeButton = mInjector.getNegativeButton();
-        mCancelButton = mInjector.getCancelButton();
-        mUseCredentialButton = mInjector.getUseCredentialButton();
+        mNegativeButton = findViewById(R.id.button_negative);
+        mCancelButton = findViewById(R.id.button_cancel);
+        mUseCredentialButton = findViewById(R.id.button_use_credential);
 
         // Positive-side (right) buttons
-        mConfirmButton = mInjector.getConfirmButton();
-        mTryAgainButton = mInjector.getTryAgainButton();
+        mConfirmButton = findViewById(R.id.button_confirm);
+        mTryAgainButton = findViewById(R.id.button_try_again);
 
         mNegativeButton.setOnClickListener((view) -> {
             mCallback.onAction(Callback.ACTION_BUTTON_NEGATIVE);
@@ -693,6 +680,15 @@
             mTryAgainButton.setVisibility(View.GONE);
             Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this);
         });
+
+        mIconController = createIconController();
+        if (mIconController.getActsAsConfirmButton()) {
+            mIconView.setOnClickListener((view) -> {
+                if (mState == STATE_PENDING_CONFIRMATION) {
+                    updateState(STATE_AUTHENTICATED);
+                }
+            });
+        }
     }
 
     /**
@@ -706,21 +702,13 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        onAttachedToWindowInternal();
-    }
 
-    /**
-     * Contains all the testable logic that should be invoked when {@link #onAttachedToWindow()} is
-     * invoked.
-     */
-    @VisibleForTesting
-    void onAttachedToWindowInternal() {
         mTitleView.setText(mPromptInfo.getTitle());
 
         if (isDeviceCredentialAllowed()) {
             final CharSequence credentialButtonText;
-            final @Utils.CredentialType int credentialType =
-                    Utils.getCredentialType(mContext, mEffectiveUserId);
+            @Utils.CredentialType final int credentialType =
+                    Utils.getCredentialType(mLockPatternUtils, mEffectiveUserId);
             switch (credentialType) {
                 case Utils.CREDENTIAL_PIN:
                     credentialButtonText =
@@ -731,9 +719,6 @@
                             getResources().getString(R.string.biometric_dialog_use_pattern);
                     break;
                 case Utils.CREDENTIAL_PASSWORD:
-                    credentialButtonText =
-                            getResources().getString(R.string.biometric_dialog_use_password);
-                    break;
                 default:
                     credentialButtonText =
                             getResources().getString(R.string.biometric_dialog_use_password);
@@ -749,7 +734,6 @@
         }
 
         setTextOrHide(mSubtitleView, mPromptInfo.getSubtitle());
-
         setTextOrHide(mDescriptionView, mPromptInfo.getDescription());
 
         if (mSavedState == null) {
@@ -774,6 +758,8 @@
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
 
+        mIconController.setDeactivated(true);
+
         // Empty the handler, otherwise things like ACTION_AUTHENTICATED may be duplicated once
         // the new dialog is restored.
         mHandler.removeCallbacksAndMessages(null /* all */);
@@ -856,15 +842,7 @@
     @Override
     public void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
-        onLayoutInternal();
-    }
 
-    /**
-     * Contains all the testable logic that should be invoked when
-     * {@link #onLayout(boolean, int, int, int, int)}, is invoked.
-     */
-    @VisibleForTesting
-    void onLayoutInternal() {
         // Start with initial size only once. Subsequent layout changes don't matter since we
         // only care about the initial icon position.
         if (mIconOriginalY == 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 21edb24..6b6af4c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -16,9 +16,10 @@
 
 package com.android.systemui.biometrics;
 
-import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_DEFAULT;
 import static android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode;
 
+import android.annotation.DurationMillisLong;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -52,13 +53,16 @@
 import android.widget.ScrollView;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.widget.LockPatternUtils;
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Top level container/controller for the BiometricPrompt UI.
@@ -66,54 +70,52 @@
 public class AuthContainerView extends LinearLayout
         implements AuthDialog, WakefulnessLifecycle.Observer {
 
-    private static final String TAG = "BiometricPrompt/AuthContainerView";
-    private static final int ANIMATION_DURATION_SHOW_MS = 250;
-    private static final int ANIMATION_DURATION_AWAY_MS = 350; // ms
+    private static final String TAG = "AuthContainerView";
 
-    static final int STATE_UNKNOWN = 0;
-    static final int STATE_ANIMATING_IN = 1;
-    static final int STATE_PENDING_DISMISS = 2;
-    static final int STATE_SHOWING = 3;
-    static final int STATE_ANIMATING_OUT = 4;
-    static final int STATE_GONE = 5;
+    private static final int ANIMATION_DURATION_SHOW_MS = 250;
+    private static final int ANIMATION_DURATION_AWAY_MS = 350;
+
+    private static final int STATE_UNKNOWN = 0;
+    private static final int STATE_ANIMATING_IN = 1;
+    private static final int STATE_PENDING_DISMISS = 2;
+    private static final int STATE_SHOWING = 3;
+    private static final int STATE_ANIMATING_OUT = 4;
+    private static final int STATE_GONE = 5;
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({STATE_UNKNOWN, STATE_ANIMATING_IN, STATE_PENDING_DISMISS, STATE_SHOWING,
             STATE_ANIMATING_OUT, STATE_GONE})
-    @interface ContainerState {}
+    private @interface ContainerState {}
 
-    final Config mConfig;
-    final int mEffectiveUserId;
-    @Nullable private final List<FingerprintSensorPropertiesInternal> mFpProps;
-    @Nullable private final List<FaceSensorPropertiesInternal> mFaceProps;
+    private final Config mConfig;
+    private final int mEffectiveUserId;
     private final Handler mHandler;
-    private final Injector mInjector;
     private final IBinder mWindowToken = new Binder();
     private final WindowManager mWindowManager;
-    private final AuthPanelController mPanelController;
     private final Interpolator mLinearOutSlowIn;
-    @VisibleForTesting final BiometricCallback mBiometricCallback;
     private final CredentialCallback mCredentialCallback;
-
-    @VisibleForTesting final FrameLayout mFrameLayout;
-    @VisibleForTesting @Nullable AuthBiometricView mBiometricView;
-    @VisibleForTesting @Nullable AuthCredentialView mCredentialView;
-
-    @VisibleForTesting final ImageView mBackgroundView;
-    @VisibleForTesting final ScrollView mBiometricScrollView;
-    private final View mPanelView;
-
-    private final float mTranslationY;
-
+    private final LockPatternUtils mLockPatternUtils;
     private final WakefulnessLifecycle mWakefulnessLifecycle;
 
-    @VisibleForTesting @ContainerState int mContainerState = STATE_UNKNOWN;
+    @VisibleForTesting final BiometricCallback mBiometricCallback;
+
+    @Nullable private AuthBiometricView mBiometricView;
+    @Nullable private AuthCredentialView mCredentialView;
+    private final AuthPanelController mPanelController;
+    private final FrameLayout mFrameLayout;
+    private final ImageView mBackgroundView;
+    private final ScrollView mBiometricScrollView;
+    private final View mPanelView;
+    private final float mTranslationY;
+    @ContainerState private int mContainerState = STATE_UNKNOWN;
+    private final Set<Integer> mFailedModalities = new HashSet<Integer>();
 
     // Non-null only if the dialog is in the act of dismissing and has not sent the reason yet.
-    @Nullable @AuthDialogCallback.DismissedReason Integer mPendingCallbackReason;
+    @Nullable @AuthDialogCallback.DismissedReason private Integer mPendingCallbackReason;
     // HAT received from LockSettingsService when credential is verified.
-    @Nullable byte[] mCredentialAttestation;
+    @Nullable private byte[] mCredentialAttestation;
 
+    @VisibleForTesting
     static class Config {
         Context mContext;
         AuthDialogCallback mCallback;
@@ -122,11 +124,11 @@
         int mUserId;
         String mOpPackageName;
         int[] mSensorIds;
-        boolean mCredentialAllowed;
         boolean mSkipIntro;
         long mOperationId;
         long mRequestId;
-        @BiometricMultiSensorMode int mMultiSensorConfig;
+        boolean mSkipAnimation = false;
+        @BiometricMultiSensorMode int mMultiSensorConfig = BIOMETRIC_MULTI_SENSOR_DEFAULT;
     }
 
     public static class Builder {
@@ -167,7 +169,7 @@
             return this;
         }
 
-        public Builder setOperationId(long operationId) {
+        public Builder setOperationId(@DurationMillisLong long operationId) {
             mConfig.mOperationId = operationId;
             return this;
         }
@@ -178,55 +180,27 @@
             return this;
         }
 
+        @VisibleForTesting
+        public Builder setSkipAnimationDuration(boolean skip) {
+            mConfig.mSkipAnimation = skip;
+            return this;
+        }
+
         /** The multi-sensor mode. */
         public Builder setMultiSensorConfig(@BiometricMultiSensorMode int multiSensorConfig) {
             mConfig.mMultiSensorConfig = multiSensorConfig;
             return this;
         }
 
-        public AuthContainerView build(int[] sensorIds, boolean credentialAllowed,
+        public AuthContainerView build(int[] sensorIds,
                 @Nullable List<FingerprintSensorPropertiesInternal> fpProps,
                 @Nullable List<FaceSensorPropertiesInternal> faceProps,
-                WakefulnessLifecycle wakefulnessLifecycle) {
+                @NonNull WakefulnessLifecycle wakefulnessLifecycle,
+                @NonNull UserManager userManager,
+                @NonNull LockPatternUtils lockPatternUtils) {
             mConfig.mSensorIds = sensorIds;
-            mConfig.mCredentialAllowed = credentialAllowed;
-            return new AuthContainerView(
-                    mConfig, new Injector(), fpProps, faceProps, wakefulnessLifecycle);
-        }
-    }
-
-    public static class Injector {
-        ScrollView getBiometricScrollView(FrameLayout parent) {
-            return parent.findViewById(R.id.biometric_scrollview);
-        }
-
-        FrameLayout inflateContainerView(LayoutInflater factory, ViewGroup root) {
-            return (FrameLayout) factory.inflate(
-                    R.layout.auth_container_view, root, false /* attachToRoot */);
-        }
-
-        AuthPanelController getPanelController(Context context, View panelView) {
-            return new AuthPanelController(context, panelView);
-        }
-
-        ImageView getBackgroundView(FrameLayout parent) {
-            return parent.findViewById(R.id.background);
-        }
-
-        View getPanelView(FrameLayout parent) {
-            return parent.findViewById(R.id.panel);
-        }
-
-        int getAnimateCredentialStartDelayMs() {
-            return AuthDialog.ANIMATE_CREDENTIAL_START_DELAY_MS;
-        }
-
-        UserManager getUserManager(Context context) {
-            return UserManager.get(context);
-        }
-
-        int getCredentialType(Context context, int effectiveUserId) {
-            return Utils.getCredentialType(context, effectiveUserId);
+            return new AuthContainerView(mConfig, fpProps, faceProps, wakefulnessLifecycle,
+                    userManager, lockPatternUtils, new Handler(Looper.getMainLooper()));
         }
     }
 
@@ -246,6 +220,7 @@
                     animateAway(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE);
                     break;
                 case AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN:
+                    mFailedModalities.clear();
                     mConfig.mCallback.onTryAgainPressed();
                     break;
                 case AuthBiometricView.Callback.ACTION_ERROR:
@@ -255,10 +230,7 @@
                     mConfig.mCallback.onDeviceCredentialPressed();
                     mHandler.postDelayed(() -> {
                         addCredentialView(false /* animatePanel */, true /* animateContents */);
-                    }, mInjector.getAnimateCredentialStartDelayMs());
-                    break;
-                case AuthBiometricView.Callback.ACTION_START_DELAYED_FINGERPRINT_SENSOR:
-                    mConfig.mCallback.onStartFingerprintNow();
+                    }, mConfig.mSkipAnimation ? 0 : AuthDialog.ANIMATE_CREDENTIAL_START_DELAY_MS);
                     break;
                 default:
                     Log.e(TAG, "Unhandled action: " + action);
@@ -275,21 +247,19 @@
     }
 
     @VisibleForTesting
-    AuthContainerView(Config config, Injector injector,
+    AuthContainerView(Config config,
             @Nullable List<FingerprintSensorPropertiesInternal> fpProps,
             @Nullable List<FaceSensorPropertiesInternal> faceProps,
-            WakefulnessLifecycle wakefulnessLifecycle) {
+            @NonNull WakefulnessLifecycle wakefulnessLifecycle,
+            @NonNull UserManager userManager,
+            @NonNull LockPatternUtils lockPatternUtils,
+            @NonNull Handler mainHandler) {
         super(config.mContext);
 
         mConfig = config;
-        mInjector = injector;
-        mFpProps = fpProps;
-        mFaceProps = faceProps;
-
-        mEffectiveUserId = mInjector.getUserManager(mContext)
-                .getCredentialOwnerProfile(mConfig.mUserId);
-
-        mHandler = new Handler(Looper.getMainLooper());
+        mLockPatternUtils = lockPatternUtils;
+        mEffectiveUserId = userManager.getCredentialOwnerProfile(mConfig.mUserId);
+        mHandler = mainHandler;
         mWindowManager = mContext.getSystemService(WindowManager.class);
         mWakefulnessLifecycle = wakefulnessLifecycle;
 
@@ -299,100 +269,42 @@
         mBiometricCallback = new BiometricCallback();
         mCredentialCallback = new CredentialCallback();
 
-        final LayoutInflater factory = LayoutInflater.from(mContext);
-        mFrameLayout = mInjector.inflateContainerView(factory, this);
-
-        mPanelView = mInjector.getPanelView(mFrameLayout);
-        mPanelController = mInjector.getPanelController(mContext, mPanelView);
+        final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
+        mFrameLayout = (FrameLayout) layoutInflater.inflate(
+                R.layout.auth_container_view, this, false /* attachToRoot */);
+        addView(mFrameLayout);
+        mBiometricScrollView = mFrameLayout.findViewById(R.id.biometric_scrollview);
+        mBackgroundView = mFrameLayout.findViewById(R.id.background);
+        mPanelView = mFrameLayout.findViewById(R.id.panel);
+        mPanelController = new AuthPanelController(mContext, mPanelView);
 
         // Inflate biometric view only if necessary.
-        final int sensorCount = config.mSensorIds.length;
         if (Utils.isBiometricAllowed(mConfig.mPromptInfo)) {
-            if (sensorCount == 1) {
-                final int singleSensorAuthId = config.mSensorIds[0];
-                if (Utils.containsSensorId(mFpProps, singleSensorAuthId)) {
-                    FingerprintSensorPropertiesInternal sensorProps = null;
-                    for (FingerprintSensorPropertiesInternal prop : mFpProps) {
-                        if (prop.sensorId == singleSensorAuthId) {
-                            sensorProps = prop;
-                            break;
-                        }
-                    }
+            final FingerprintSensorPropertiesInternal fpProperties =
+                    Utils.findFirstSensorProperties(fpProps, mConfig.mSensorIds);
+            final FaceSensorPropertiesInternal faceProperties =
+                    Utils.findFirstSensorProperties(faceProps, mConfig.mSensorIds);
 
-                    if (sensorProps.isAnyUdfpsType()) {
-                        AuthBiometricUdfpsView udfpsView = (AuthBiometricUdfpsView) factory
-                                .inflate(R.layout.auth_biometric_udfps_view, null, false);
-                        udfpsView.setSensorProps(sensorProps);
-                        mBiometricView = udfpsView;
-                    } else {
-                        mBiometricView = (AuthBiometricFingerprintView) factory
-                                .inflate(R.layout.auth_biometric_fingerprint_view, null, false);
-                    }
-                } else if (Utils.containsSensorId(mFaceProps, singleSensorAuthId)) {
-                    mBiometricView = (AuthBiometricFaceView)
-                            factory.inflate(R.layout.auth_biometric_face_view, null, false);
-                } else {
-                    // Unknown sensorId
-                    Log.e(TAG, "Unknown sensorId: " + singleSensorAuthId);
-                    mBiometricView = null;
-                    mBackgroundView = null;
-                    mBiometricScrollView = null;
-                    return;
-                }
-            } else if (sensorCount == 2) {
-                final int[] allSensors = findFaceAndFingerprintSensors();
-                final int faceSensorId = allSensors[0];
-                final int fingerprintSensorId = allSensors[1];
-
-                if (fingerprintSensorId == -1 || faceSensorId == -1) {
-                    Log.e(TAG, "Missing fingerprint or face for dual-sensor config");
-                    mBiometricView = null;
-                    mBackgroundView = null;
-                    mBiometricScrollView = null;
-                    return;
-                }
-
-                FingerprintSensorPropertiesInternal fingerprintSensorProps = null;
-                for (FingerprintSensorPropertiesInternal prop : mFpProps) {
-                    if (prop.sensorId == fingerprintSensorId) {
-                        fingerprintSensorProps = prop;
-                        break;
-                    }
-                }
-
-                if (fingerprintSensorProps != null) {
-                    final AuthBiometricFaceToFingerprintView faceToFingerprintView =
-                            (AuthBiometricFaceToFingerprintView) factory.inflate(
-                                    R.layout.auth_biometric_face_to_fingerprint_view, null, false);
-                    faceToFingerprintView.setFingerprintSensorProps(fingerprintSensorProps);
-                    faceToFingerprintView.setModalityListener(new ModalityListener() {
-                        @Override
-                        public void onModalitySwitched(int oldModality, int newModality) {
-                            maybeUpdatePositionForUdfps(true /* invalidate */);
-                        }
-                    });
-                    mBiometricView = faceToFingerprintView;
-                } else {
-                    Log.e(TAG, "Fingerprint props not found for sensor ID: " + fingerprintSensorId);
-                    mBiometricView = null;
-                    mBackgroundView = null;
-                    mBiometricScrollView = null;
-                    return;
-                }
+            if (fpProperties != null && faceProperties != null) {
+                final AuthBiometricFingerprintAndFaceView fingerprintAndFaceView =
+                        (AuthBiometricFingerprintAndFaceView) layoutInflater.inflate(
+                                R.layout.auth_biometric_fingerprint_and_face_view, null, false);
+                fingerprintAndFaceView.setSensorProperties(fpProperties);
+                mBiometricView = fingerprintAndFaceView;
+            } else if (fpProperties != null) {
+                final AuthBiometricFingerprintView fpView =
+                        (AuthBiometricFingerprintView) layoutInflater.inflate(
+                                R.layout.auth_biometric_fingerprint_view, null, false);
+                fpView.setSensorProperties(fpProperties);
+                mBiometricView = fpView;
+            } else if (faceProperties != null) {
+                mBiometricView = (AuthBiometricFaceView) layoutInflater.inflate(
+                        R.layout.auth_biometric_face_view, null, false);
             } else {
-                Log.e(TAG, "Unsupported sensor array, length: " + sensorCount);
-                mBiometricView = null;
-                mBackgroundView = null;
-                mBiometricScrollView = null;
-                return;
+                Log.e(TAG, "No sensors found!");
             }
         }
 
-        mBiometricScrollView = mInjector.getBiometricScrollView(mFrameLayout);
-        mBackgroundView = mInjector.getBackgroundView(mFrameLayout);
-
-        addView(mFrameLayout);
-
         // init view before showing
         if (mBiometricView != null) {
             mBiometricView.setRequireConfirmation(mConfig.mRequireConfirmation);
@@ -431,10 +343,6 @@
         return Utils.isDeviceCredentialAllowed(mConfig.mPromptInfo);
     }
 
-    private void addBiometricView() {
-        mBiometricScrollView.addView(mBiometricView);
-    }
-
     /**
      * Adds the credential view. When going from biometric to credential view, the biometric
      * view starts the panel expansion animation. If the credential view is being shown first,
@@ -444,8 +352,8 @@
     private void addCredentialView(boolean animatePanel, boolean animateContents) {
         final LayoutInflater factory = LayoutInflater.from(mContext);
 
-        final @Utils.CredentialType int credentialType = mInjector.getCredentialType(
-                mContext, mEffectiveUserId);
+        @Utils.CredentialType final int credentialType = Utils.getCredentialType(
+                mLockPatternUtils, mEffectiveUserId);
 
         switch (credentialType) {
             case Utils.CREDENTIAL_PATTERN:
@@ -493,15 +401,11 @@
     @Override
     public void onAttachedToWindow() {
         super.onAttachedToWindow();
-        onAttachedToWindowInternal();
-    }
 
-    @VisibleForTesting
-    void onAttachedToWindowInternal() {
         mWakefulnessLifecycle.addObserver(this);
 
         if (Utils.isBiometricAllowed(mConfig.mPromptInfo)) {
-            addBiometricView();
+            mBiometricScrollView.addView(mBiometricView);
         } else if (Utils.isDeviceCredentialAllowed(mConfig.mPromptInfo)) {
             addCredentialView(true /* animatePanel */, false /* animateContents */);
         } else {
@@ -521,17 +425,18 @@
             mBiometricScrollView.setY(mTranslationY);
 
             setAlpha(0f);
+            final long animateDuration = mConfig.mSkipAnimation ? 0 : ANIMATION_DURATION_SHOW_MS;
             postOnAnimation(() -> {
                 mPanelView.animate()
                         .translationY(0)
-                        .setDuration(ANIMATION_DURATION_SHOW_MS)
+                        .setDuration(animateDuration)
                         .setInterpolator(mLinearOutSlowIn)
                         .withLayer()
                         .withEndAction(this::onDialogAnimatedIn)
                         .start();
                 mBiometricScrollView.animate()
                         .translationY(0)
-                        .setDuration(ANIMATION_DURATION_SHOW_MS)
+                        .setDuration(animateDuration)
                         .setInterpolator(mLinearOutSlowIn)
                         .withLayer()
                         .start();
@@ -539,14 +444,14 @@
                     mCredentialView.setY(mTranslationY);
                     mCredentialView.animate()
                             .translationY(0)
-                            .setDuration(ANIMATION_DURATION_SHOW_MS)
+                            .setDuration(animateDuration)
                             .setInterpolator(mLinearOutSlowIn)
                             .withLayer()
                             .start();
                 }
                 animate()
                         .alpha(1f)
-                        .setDuration(ANIMATION_DURATION_SHOW_MS)
+                        .setDuration(animateDuration)
                         .setInterpolator(mLinearOutSlowIn)
                         .withLayer()
                         .start();
@@ -555,15 +460,8 @@
     }
 
     private static boolean shouldUpdatePositionForUdfps(@NonNull View view) {
-        if (view instanceof AuthBiometricUdfpsView) {
-            return true;
-        }
-
-        if (view instanceof AuthBiometricFaceToFingerprintView) {
-            AuthBiometricFaceToFingerprintView faceToFingerprintView =
-                    (AuthBiometricFaceToFingerprintView) view;
-            return faceToFingerprintView.getActiveSensorType() == TYPE_FINGERPRINT
-                    && faceToFingerprintView.isFingerprintUdfps();
+        if (view instanceof AuthBiometricFingerprintView) {
+            return ((AuthBiometricFingerprintView) view).isUdfps();
         }
 
         return false;
@@ -652,12 +550,13 @@
     }
 
     @Override
-    public void onAuthenticationSucceeded() {
-        mBiometricView.onAuthenticationSucceeded();
+    public void onAuthenticationSucceeded(@Modality int modality) {
+        mBiometricView.onAuthenticationSucceeded(modality);
     }
 
     @Override
     public void onAuthenticationFailed(@Modality int modality, String failureReason) {
+        mFailedModalities.add(modality);
         mBiometricView.onAuthenticationFailed(modality, failureReason);
     }
 
@@ -672,8 +571,17 @@
     }
 
     @Override
+    public void onPointerDown() {
+        if (mBiometricView.onPointerDown(mFailedModalities)) {
+            Log.d(TAG, "retrying failed modalities (pointer down)");
+            mBiometricCallback.onAction(AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN);
+        }
+    }
+
+    @Override
     public void onSaveState(@NonNull Bundle outState) {
-        outState.putInt(AuthDialog.KEY_CONTAINER_STATE, mContainerState);
+        outState.putBoolean(AuthDialog.KEY_CONTAINER_GOING_AWAY,
+                mContainerState == STATE_ANIMATING_OUT);
         // In the case where biometric and credential are both allowed, we can assume that
         // biometric isn't showing if credential is showing since biometric is shown first.
         outState.putBoolean(AuthDialog.KEY_BIOMETRIC_SHOWING,
@@ -695,8 +603,7 @@
         mBiometricView.startTransitionToCredentialUI();
     }
 
-    @VisibleForTesting
-    void animateAway(int reason) {
+    void animateAway(@AuthDialogCallback.DismissedReason int reason) {
         animateAway(true /* sendReason */, reason);
     }
 
@@ -724,31 +631,32 @@
             removeWindowIfAttached();
         };
 
+        final long animateDuration = mConfig.mSkipAnimation ? 0 : ANIMATION_DURATION_AWAY_MS;
         postOnAnimation(() -> {
             mPanelView.animate()
                     .translationY(mTranslationY)
-                    .setDuration(ANIMATION_DURATION_AWAY_MS)
+                    .setDuration(animateDuration)
                     .setInterpolator(mLinearOutSlowIn)
                     .withLayer()
                     .withEndAction(endActionRunnable)
                     .start();
             mBiometricScrollView.animate()
                     .translationY(mTranslationY)
-                    .setDuration(ANIMATION_DURATION_AWAY_MS)
+                    .setDuration(animateDuration)
                     .setInterpolator(mLinearOutSlowIn)
                     .withLayer()
                     .start();
             if (mCredentialView != null && mCredentialView.isAttachedToWindow()) {
                 mCredentialView.animate()
                         .translationY(mTranslationY)
-                        .setDuration(ANIMATION_DURATION_AWAY_MS)
+                        .setDuration(animateDuration)
                         .setInterpolator(mLinearOutSlowIn)
                         .withLayer()
                         .start();
             }
             animate()
                     .alpha(0f)
-                    .setDuration(ANIMATION_DURATION_AWAY_MS)
+                    .setDuration(animateDuration)
                     .setInterpolator(mLinearOutSlowIn)
                     .withLayer()
                     .start();
@@ -773,8 +681,7 @@
         mWindowManager.removeView(this);
     }
 
-    @VisibleForTesting
-    void onDialogAnimatedIn() {
+    private void onDialogAnimatedIn() {
         if (mContainerState == STATE_PENDING_DISMISS) {
             Log.d(TAG, "onDialogAnimatedIn(): mPendingDismissDialog=true, dismissing now");
             animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
@@ -788,8 +695,7 @@
     }
 
     @VisibleForTesting
-    static WindowManager.LayoutParams getLayoutParams(IBinder windowToken,
-            CharSequence title) {
+    static WindowManager.LayoutParams getLayoutParams(IBinder windowToken, CharSequence title) {
         final int windowFlags = WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
                 | WindowManager.LayoutParams.FLAG_SECURE;
         final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
@@ -805,24 +711,4 @@
         lp.token = windowToken;
         return lp;
     }
-
-    // returns [face, fingerprint] sensor ids (id is -1 if not present)
-    private int[] findFaceAndFingerprintSensors() {
-        int faceSensorId = -1;
-        int fingerprintSensorId = -1;
-
-        for (final int sensorId : mConfig.mSensorIds) {
-            if (Utils.containsSensorId(mFpProps, sensorId)) {
-                fingerprintSensorId = sensorId;
-            } else if (Utils.containsSensorId(mFaceProps, sensorId)) {
-                faceSensorId = sensorId;
-            }
-
-            if (fingerprintSensorId != -1 && faceSensorId != -1) {
-                break;
-            }
-        }
-
-        return new int[] {faceSensorId, fingerprintSensorId};
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index dfb8c18..64c2d2e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -51,6 +51,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.RemoteException;
+import android.os.UserManager;
 import android.util.Log;
 import android.util.SparseBooleanArray;
 import android.view.MotionEvent;
@@ -59,6 +60,7 @@
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.SomeArgs;
+import com.android.internal.widget.LockPatternUtils;
 import com.android.systemui.CoreStartable;
 import com.android.systemui.assist.ui.DisplayUtils;
 import com.android.systemui.dagger.SysUISingleton;
@@ -123,8 +125,6 @@
     @Nullable private SidefpsController mSidefpsController;
     @Nullable private IBiometricContextListener mBiometricContextListener;
     @VisibleForTesting
-    TaskStackListener mTaskStackListener;
-    @VisibleForTesting
     IBiometricSysuiReceiver mReceiver;
     @VisibleForTesting
     @NonNull final BiometricDisplayListener mOrientationListener;
@@ -137,13 +137,16 @@
     @NonNull private final SensorPrivacyManager mSensorPrivacyManager;
     private final WakefulnessLifecycle mWakefulnessLifecycle;
     private boolean mAllAuthenticatorsRegistered;
+    @NonNull private final UserManager mUserManager;
+    @NonNull private final LockPatternUtils mLockPatternUtils;
 
-    private class BiometricTaskStackListener extends TaskStackListener {
+    @VisibleForTesting
+    final TaskStackListener mTaskStackListener = new TaskStackListener() {
         @Override
         public void onTaskStackChanged() {
             mHandler.post(AuthController.this::handleTaskStackChanged);
         }
-    }
+    };
 
     private final IFingerprintAuthenticatorsRegisteredCallback
             mFingerprintAuthenticatorsRegisteredCallback =
@@ -256,6 +259,17 @@
         mUdfpsProps = !udfpsProps.isEmpty() ? udfpsProps : null;
         if (mUdfpsProps != null) {
             mUdfpsController = mUdfpsControllerFactory.get();
+            mUdfpsController.addCallback(new UdfpsController.Callback() {
+                @Override
+                public void onFingerUp() {}
+
+                @Override
+                public void onFingerDown() {
+                    if (mCurrentDialog != null) {
+                        mCurrentDialog.onPointerDown();
+                    }
+                }
+            });
         }
         mSidefpsProps = !sidefpsProps.isEmpty() ? sidefpsProps : null;
         if (mSidefpsProps != null) {
@@ -360,20 +374,6 @@
     }
 
     @Override
-    public void onStartFingerprintNow() {
-        if (mReceiver == null) {
-            Log.e(TAG, "onStartUdfpsNow: Receiver is null");
-            return;
-        }
-
-        try {
-            mReceiver.onStartFingerprintNow();
-        } catch (RemoteException e) {
-            Log.e(TAG, "RemoteException when sending onDialogAnimatedIn", e);
-        }
-    }
-
-    @Override
     public void onDismissed(@DismissedReason int reason, @Nullable byte[] credentialAttestation) {
         switch (reason) {
             case AuthDialogCallback.DISMISSED_USER_CANCELED:
@@ -503,12 +503,16 @@
             Provider<UdfpsController> udfpsControllerFactory,
             Provider<SidefpsController> sidefpsControllerFactory,
             @NonNull DisplayManager displayManager,
-            WakefulnessLifecycle wakefulnessLifecycle,
+            @NonNull WakefulnessLifecycle wakefulnessLifecycle,
+            @NonNull UserManager userManager,
+            @NonNull LockPatternUtils lockPatternUtils,
             @NonNull StatusBarStateController statusBarStateController,
             @Main Handler handler) {
         super(context);
         mExecution = execution;
         mWakefulnessLifecycle = wakefulnessLifecycle;
+        mUserManager = userManager;
+        mLockPatternUtils = lockPatternUtils;
         mHandler = handler;
         mCommandQueue = commandQueue;
         mActivityTaskManager = activityTaskManager;
@@ -583,7 +587,6 @@
                     mFingerprintAuthenticatorsRegisteredCallback);
         }
 
-        mTaskStackListener = new BiometricTaskStackListener();
         mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
     }
 
@@ -668,11 +671,11 @@
      * example, KeyguardUpdateMonitor has its own {@link FingerprintManager.AuthenticationCallback}.
      */
     @Override
-    public void onBiometricAuthenticated() {
+    public void onBiometricAuthenticated(@Modality int modality) {
         if (DEBUG) Log.d(TAG, "onBiometricAuthenticated: ");
 
         if (mCurrentDialog != null) {
-            mCurrentDialog.onAuthenticationSucceeded();
+            mCurrentDialog.onAuthenticationSucceeded(modality);
         } else {
             Log.w(TAG, "onBiometricAuthenticated callback but dialog gone");
         }
@@ -827,7 +830,7 @@
         final String opPackageName = (String) args.arg6;
         final long operationId = args.argl1;
         final long requestId = args.argl2;
-        final @BiometricMultiSensorMode int multiSensorConfig = args.argi2;
+        @BiometricMultiSensorMode final int multiSensorConfig = args.argi2;
 
         // Create a new dialog but do not replace the current one yet.
         final AuthDialog newDialog = buildDialog(
@@ -835,13 +838,14 @@
                 requireConfirmation,
                 userId,
                 sensorIds,
-                credentialAllowed,
                 opPackageName,
                 skipAnimation,
                 operationId,
                 requestId,
                 multiSensorConfig,
-                mWakefulnessLifecycle);
+                mWakefulnessLifecycle,
+                mUserManager,
+                mLockPatternUtils);
 
         if (newDialog == null) {
             Log.e(TAG, "Unsupported type configuration");
@@ -902,8 +906,7 @@
 
             // Only show the dialog if necessary. If it was animating out, the dialog is supposed
             // to send its pending callback immediately.
-            if (savedState.getInt(AuthDialog.KEY_CONTAINER_STATE)
-                    != AuthContainerView.STATE_ANIMATING_OUT) {
+            if (!savedState.getBoolean(AuthDialog.KEY_CONTAINER_GOING_AWAY, false)) {
                 final boolean credentialShowing =
                         savedState.getBoolean(AuthDialog.KEY_CREDENTIAL_SHOWING);
                 if (credentialShowing) {
@@ -927,10 +930,12 @@
     }
 
     protected AuthDialog buildDialog(PromptInfo promptInfo, boolean requireConfirmation,
-            int userId, int[] sensorIds, boolean credentialAllowed, String opPackageName,
+            int userId, int[] sensorIds, String opPackageName,
             boolean skipIntro, long operationId, long requestId,
             @BiometricMultiSensorMode int multiSensorConfig,
-            WakefulnessLifecycle wakefulnessLifecycle) {
+            @NonNull WakefulnessLifecycle wakefulnessLifecycle,
+            @NonNull UserManager userManager,
+            @NonNull LockPatternUtils lockPatternUtils) {
         return new AuthContainerView.Builder(mContext)
                 .setCallback(this)
                 .setPromptInfo(promptInfo)
@@ -941,7 +946,8 @@
                 .setOperationId(operationId)
                 .setRequestId(requestId)
                 .setMultiSensorConfig(multiSensorConfig)
-                .build(sensorIds, credentialAllowed, mFpProps, mFaceProps, wakefulnessLifecycle);
+                .build(sensorIds, mFpProps, mFaceProps, wakefulnessLifecycle, userManager,
+                        lockPatternUtils);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
index fa5213e..59ed156 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
@@ -31,7 +31,7 @@
  */
 public interface AuthDialog {
 
-    String KEY_CONTAINER_STATE = "container_state";
+    String KEY_CONTAINER_GOING_AWAY = "container_going_away";
     String KEY_BIOMETRIC_SHOWING = "biometric_showing";
     String KEY_CREDENTIAL_SHOWING = "credential_showing";
 
@@ -64,7 +64,7 @@
     @interface DialogSize {}
 
     /**
-     * Parameters used when laying out {@link AuthBiometricView}, its sublclasses, and
+     * Parameters used when laying out {@link AuthBiometricView}, its subclasses, and
      * {@link AuthPanelController}.
      */
     class LayoutParams {
@@ -113,7 +113,7 @@
     /**
      * Biometric authenticated. May be pending user confirmation, or completed.
      */
-    void onAuthenticationSucceeded();
+    void onAuthenticationSucceeded(@Modality int modality);
 
     /**
      * Authentication failed (reject, timeout). Dialog stays showing.
@@ -136,6 +136,9 @@
      */
     void onError(@Modality int modality, String error);
 
+    /** UDFPS pointer down event. */
+    void onPointerDown();
+
     /**
      * Save the current state.
      * @param outState
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java
index 9f40ca7..a7d2901 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java
@@ -70,9 +70,4 @@
      * Notifies when the dialog has finished animating.
      */
     void onDialogAnimatedIn();
-
-    /**
-     * Notifies that the fingerprint sensor should be started now.
-     */
-    void onStartFingerprintNow();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpView.kt
index 6607915..242601d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpView.kt
@@ -23,7 +23,7 @@
  *
  * Currently doesn't draw anything.
  *
- * Note that [AuthBiometricUdfpsView] also shows UDFPS animations. At some point we should
+ * Note that [AuthBiometricFingerprintViewController] also shows UDFPS animations. At some point we should
  * de-dupe this if necessary.
  */
 class UdfpsBpView(context: Context, attrs: AttributeSet?) : UdfpsAnimationView(context, attrs) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 590963b..086894d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -300,7 +300,10 @@
         when (context.display!!.rotation) {
             Surface.ROTATION_90 -> {
                 if (!shouldRotate(animation)) {
-                    Log.v(TAG, "skip rotating udfps location ROTATION_90")
+                    Log.v(TAG, "skip rotating udfps location ROTATION_90" +
+                            " animation=$animation" +
+                            " isGoingToSleep=${keyguardUpdateMonitor.isGoingToSleep}" +
+                            " isOccluded=${keyguardStateController.isOccluded}")
                 } else {
                     Log.v(TAG, "rotate udfps location ROTATION_90")
                     x = (location.sensorLocationY - location.sensorRadius - paddingX)
@@ -309,7 +312,10 @@
             }
             Surface.ROTATION_270 -> {
                 if (!shouldRotate(animation)) {
-                    Log.v(TAG, "skip rotating udfps location ROTATION_270")
+                    Log.v(TAG, "skip rotating udfps location ROTATION_270" +
+                            " animation=$animation" +
+                            " isGoingToSleep=${keyguardUpdateMonitor.isGoingToSleep}" +
+                            " isOccluded=${keyguardStateController.isOccluded}")
                 } else {
                     Log.v(TAG, "rotate udfps location ROTATION_270")
                     x = (p.x - location.sensorLocationY - location.sensorRadius - paddingX)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java
deleted file mode 100644
index 6989547..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2019 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.biometrics;
-
-import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
-import static android.hardware.biometrics.BiometricManager.Authenticators;
-import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.admin.DevicePolicyManager;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.hardware.biometrics.PromptInfo;
-import android.hardware.biometrics.SensorPropertiesInternal;
-import android.os.UserManager;
-import android.util.DisplayMetrics;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-
-import com.android.internal.widget.LockPatternUtils;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.List;
-
-public class Utils {
-
-    public static final int CREDENTIAL_PIN = 1;
-    public static final int CREDENTIAL_PATTERN = 2;
-    public static final int CREDENTIAL_PASSWORD = 3;
-
-    /** Base set of layout flags for fingerprint overlay widgets. */
-    public static final int FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS =
-            WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
-                | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
-                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-                | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
-
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({CREDENTIAL_PIN, CREDENTIAL_PATTERN, CREDENTIAL_PASSWORD})
-    @interface CredentialType {}
-
-    static float dpToPixels(Context context, float dp) {
-        return dp * ((float) context.getResources().getDisplayMetrics().densityDpi
-                / DisplayMetrics.DENSITY_DEFAULT);
-    }
-
-    static void notifyAccessibilityContentChanged(AccessibilityManager am, ViewGroup view) {
-        if (!am.isEnabled()) {
-            return;
-        }
-        AccessibilityEvent event = AccessibilityEvent.obtain();
-        event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
-        event.setContentChangeTypes(CONTENT_CHANGE_TYPE_SUBTREE);
-        view.sendAccessibilityEventUnchecked(event);
-        view.notifySubtreeAccessibilityStateChanged(view, view, CONTENT_CHANGE_TYPE_SUBTREE);
-    }
-
-    static boolean isDeviceCredentialAllowed(PromptInfo promptInfo) {
-        @Authenticators.Types final int authenticators = promptInfo.getAuthenticators();
-        return (authenticators & Authenticators.DEVICE_CREDENTIAL) != 0;
-    }
-
-    static boolean isBiometricAllowed(PromptInfo promptInfo) {
-        @Authenticators.Types final int authenticators = promptInfo.getAuthenticators();
-        return (authenticators & Authenticators.BIOMETRIC_WEAK) != 0;
-    }
-
-    static @CredentialType int getCredentialType(Context context, int userId) {
-        final LockPatternUtils lpu = new LockPatternUtils(context);
-        switch (lpu.getKeyguardStoredPasswordQuality(userId)) {
-            case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING:
-                return CREDENTIAL_PATTERN;
-            case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
-            case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX:
-                return CREDENTIAL_PIN;
-            case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC:
-            case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC:
-            case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX:
-            case DevicePolicyManager.PASSWORD_QUALITY_MANAGED:
-                return CREDENTIAL_PASSWORD;
-            default:
-                return CREDENTIAL_PASSWORD;
-        }
-    }
-
-    static boolean isManagedProfile(Context context, int userId) {
-        final UserManager userManager = context.getSystemService(UserManager.class);
-        return userManager.isManagedProfile(userId);
-    }
-
-    static boolean containsSensorId(@Nullable List<? extends SensorPropertiesInternal> properties,
-            int sensorId) {
-        if (properties == null) {
-            return false;
-        }
-
-        for (SensorPropertiesInternal prop : properties) {
-            if (prop.sensorId == sensorId) {
-                return true;
-            }
-        }
-
-        return false;
-    }
-
-    static boolean isSystem(@NonNull Context context, @Nullable String clientPackage) {
-        final boolean hasPermission = context.checkCallingOrSelfPermission(USE_BIOMETRIC_INTERNAL)
-                == PackageManager.PERMISSION_GRANTED;
-        return hasPermission && "android".equals(clientPackage);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt
new file mode 100644
index 0000000..d0d6f4c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2019 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.biometrics
+
+import android.Manifest
+import android.annotation.IntDef
+import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC
+import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC
+import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX
+import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_MANAGED
+import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
+import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX
+import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
+import android.content.Context
+import android.content.pm.PackageManager
+import android.hardware.biometrics.BiometricManager.Authenticators
+import android.hardware.biometrics.PromptInfo
+import android.hardware.biometrics.SensorPropertiesInternal
+import android.os.UserManager
+import android.util.DisplayMetrics
+import android.view.ViewGroup
+import android.view.WindowManager
+import android.view.accessibility.AccessibilityEvent
+import android.view.accessibility.AccessibilityManager
+import com.android.internal.widget.LockPatternUtils
+import java.lang.annotation.Retention
+import java.lang.annotation.RetentionPolicy
+
+object Utils {
+    const val CREDENTIAL_PIN = 1
+    const val CREDENTIAL_PATTERN = 2
+    const val CREDENTIAL_PASSWORD = 3
+
+    /** Base set of layout flags for fingerprint overlay widgets.  */
+    const val FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS =
+        (WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+            or WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+            or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+            or WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED)
+
+    @JvmStatic
+    fun dpToPixels(context: Context, dp: Float): Float {
+        val density = context.resources.displayMetrics.densityDpi.toFloat()
+        return dp * (density / DisplayMetrics.DENSITY_DEFAULT)
+    }
+
+    @JvmStatic
+    fun notifyAccessibilityContentChanged(am: AccessibilityManager, view: ViewGroup) {
+        if (!am.isEnabled) {
+            return
+        }
+        val event = AccessibilityEvent.obtain()
+        event.eventType = AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
+        event.contentChangeTypes =
+            AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE
+        view.sendAccessibilityEventUnchecked(event)
+        view.notifySubtreeAccessibilityStateChanged(
+            view,
+            view,
+            AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE
+        )
+    }
+
+    @JvmStatic
+    fun isDeviceCredentialAllowed(promptInfo: PromptInfo): Boolean =
+        (promptInfo.authenticators and Authenticators.DEVICE_CREDENTIAL) != 0
+
+    @JvmStatic
+    fun isBiometricAllowed(promptInfo: PromptInfo): Boolean =
+        (promptInfo.authenticators and Authenticators.BIOMETRIC_WEAK) != 0
+
+    @JvmStatic
+    @CredentialType
+    fun getCredentialType(utils: LockPatternUtils, userId: Int): Int =
+        when (utils.getKeyguardStoredPasswordQuality(userId)) {
+            PASSWORD_QUALITY_SOMETHING -> CREDENTIAL_PATTERN
+            PASSWORD_QUALITY_NUMERIC,
+            PASSWORD_QUALITY_NUMERIC_COMPLEX -> CREDENTIAL_PIN
+            PASSWORD_QUALITY_ALPHABETIC,
+            PASSWORD_QUALITY_ALPHANUMERIC,
+            PASSWORD_QUALITY_COMPLEX,
+            PASSWORD_QUALITY_MANAGED -> CREDENTIAL_PASSWORD
+            else -> CREDENTIAL_PASSWORD
+        }
+
+    @JvmStatic
+    fun isManagedProfile(context: Context, userId: Int): Boolean =
+        context.getSystemService(UserManager::class.java)?.isManagedProfile(userId) ?: false
+
+    @JvmStatic
+    fun <T : SensorPropertiesInternal> findFirstSensorProperties(
+        properties: List<T>?,
+        sensorIds: IntArray
+    ): T? = properties?.firstOrNull { sensorIds.contains(it.sensorId) }
+
+    @JvmStatic
+    fun isSystem(context: Context, clientPackage: String?): Boolean {
+        val hasPermission =
+            (context.checkCallingOrSelfPermission(Manifest.permission.USE_BIOMETRIC_INTERNAL)
+                == PackageManager.PERMISSION_GRANTED)
+        return hasPermission && "android" == clientPackage
+    }
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(CREDENTIAL_PIN, CREDENTIAL_PATTERN, CREDENTIAL_PASSWORD)
+    internal annotation class CredentialType
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
index d2ded71..5b38e5b 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
@@ -97,7 +97,11 @@
                     }
                     bindTryCount++
                     try {
-                        context.bindServiceAsUser(intent, serviceConnection, BIND_FLAGS, user)
+                        val bound = context
+                            .bindServiceAsUser(intent, serviceConnection, BIND_FLAGS, user)
+                        if (!bound) {
+                            context.unbindService(serviceConnection)
+                        }
                     } catch (e: SecurityException) {
                         Log.e(TAG, "Failed to bind to service", e)
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
index 4819bf5..a4f9f3a 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
@@ -54,7 +54,7 @@
 import com.android.systemui.controls.ControlsMetricsLogger
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.util.concurrency.DelayableExecutor
-import kotlin.reflect.KClass
+import java.util.function.Supplier
 
 /**
  * Wraps the widgets that make up the UI representation of a {@link Control}. Updates to the view
@@ -90,20 +90,20 @@
             status: Int,
             template: ControlTemplate,
             deviceType: Int
-        ): KClass<out Behavior> {
+        ): Supplier<out Behavior> {
             return when {
-                status != Control.STATUS_OK -> StatusBehavior::class
-                template == ControlTemplate.NO_TEMPLATE -> TouchBehavior::class
-                template is ThumbnailTemplate -> ThumbnailBehavior::class
+                status != Control.STATUS_OK -> Supplier { StatusBehavior() }
+                template == ControlTemplate.NO_TEMPLATE -> Supplier { TouchBehavior() }
+                template is ThumbnailTemplate -> Supplier { ThumbnailBehavior() }
 
                 // Required for legacy support, or where cameras do not use the new template
-                deviceType == DeviceTypes.TYPE_CAMERA -> TouchBehavior::class
-                template is ToggleTemplate -> ToggleBehavior::class
-                template is StatelessTemplate -> TouchBehavior::class
-                template is ToggleRangeTemplate -> ToggleRangeBehavior::class
-                template is RangeTemplate -> ToggleRangeBehavior::class
-                template is TemperatureControlTemplate -> TemperatureControlBehavior::class
-                else -> DefaultBehavior::class
+                deviceType == DeviceTypes.TYPE_CAMERA -> Supplier { TouchBehavior() }
+                template is ToggleTemplate -> Supplier { ToggleBehavior() }
+                template is StatelessTemplate -> Supplier { TouchBehavior() }
+                template is ToggleRangeTemplate -> Supplier { ToggleRangeBehavior() }
+                template is RangeTemplate -> Supplier { ToggleRangeBehavior() }
+                template is TemperatureControlTemplate -> Supplier { TemperatureControlBehavior() }
+                else -> Supplier { DefaultBehavior() }
             }
         }
     }
@@ -253,13 +253,14 @@
 
     fun bindBehavior(
         existingBehavior: Behavior?,
-        clazz: KClass<out Behavior>,
+        supplier: Supplier<out Behavior>,
         offset: Int = 0
     ): Behavior {
-        val behavior = if (existingBehavior == null || existingBehavior!!::class != clazz) {
+        val newBehavior = supplier.get()
+        val behavior = if (existingBehavior == null ||
+                existingBehavior::class != newBehavior::class) {
             // Behavior changes can signal a change in template from the app or
             // first time setup
-            val newBehavior = clazz.java.newInstance()
             newBehavior.initialize(this)
 
             // let behaviors define their own, if necessary, and clear any existing ones
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 7e1fce2..ebc7666 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -19,6 +19,8 @@
 import android.content.Context;
 import android.graphics.drawable.ColorDrawable;
 import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
 import android.view.Window;
 import android.view.WindowInsets;
 import android.view.WindowManager;
@@ -177,9 +179,26 @@
         }
 
         mDreamOverlayContainerViewController.init();
+        // Make extra sure the container view has been removed from its old parent (otherwise we
+        // risk an IllegalStateException in some cases when setting the container view as the
+        // window's content view and the container view hasn't been properly removed previously).
+        removeContainerViewFromParent();
         mWindow.setContentView(mDreamOverlayContainerViewController.getContainerView());
 
         final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
         windowManager.addView(mWindow.getDecorView(), mWindow.getAttributes());
     }
+
+    private void removeContainerViewFromParent() {
+        View containerView = mDreamOverlayContainerViewController.getContainerView();
+        if (containerView == null) {
+            return;
+        }
+        ViewGroup parentView = (ViewGroup) containerView.getParent();
+        if (parentView == null) {
+            return;
+        }
+        Log.w(TAG, "Removing dream overlay container view parent!");
+        parentView.removeView(containerView);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 582965a..35f29b9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -23,6 +23,7 @@
 import android.graphics.Matrix
 import android.graphics.Rect
 import android.os.Handler
+import android.util.Log
 import android.view.RemoteAnimationTarget
 import android.view.SyncRtSurfaceTransactionApplier
 import android.view.View
@@ -47,6 +48,8 @@
 import javax.inject.Inject
 import kotlin.math.min
 
+const val TAG = "KeyguardUnlock"
+
 /**
  * Starting scale factor for the app/launcher surface behind the keyguard, when it's animating
  * in during keyguard exit.
@@ -584,8 +587,16 @@
      * animation.
      */
     fun hideKeyguardViewAfterRemoteAnimation() {
-        // Hide the keyguard, with no fade out since we animated it away during the unlock.
-        keyguardViewController.hide(surfaceBehindRemoteAnimationStartTime, 0 /* fadeOutDuration */)
+        if (keyguardViewController.isShowing) {
+            // Hide the keyguard, with no fade out since we animated it away during the unlock.
+            keyguardViewController.hide(
+                surfaceBehindRemoteAnimationStartTime,
+                0 /* fadeOutDuration */
+            )
+        } else {
+            Log.e(TAG, "#hideKeyguardViewAfterRemoteAnimation called when keyguard view is not " +
+                    "showing. Ignoring...")
+        }
     }
 
     private fun applyParamsToSurface(params: SyncRtSurfaceTransactionApplier.SurfaceParams) {
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index b323586..ae7a671 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -154,6 +154,28 @@
         return factory.create("SwipeStatusBarAwayLog", 30);
     }
 
+    /**
+     * Provides a logging buffer for logs related to the media tap-to-transfer chip on the sender
+     * device. See {@link com.android.systemui.media.taptotransfer.sender.MediaTttSenderLogger}.
+     */
+    @Provides
+    @SysUISingleton
+    @MediaTttSenderLogBuffer
+    public static LogBuffer provideMediaTttSenderLogBuffer(LogBufferFactory factory) {
+        return factory.create("MediaTttSender", 20);
+    }
+
+    /**
+     * Provides a logging buffer for logs related to the media tap-to-transfer chip on the receiver
+     * device. See {@link com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogger}.
+     */
+    @Provides
+    @SysUISingleton
+    @MediaTttReceiverLogBuffer
+    public static LogBuffer provideMediaTttReceiverLogBuffer(LogBufferFactory factory) {
+        return factory.create("MediaTttReceiver", 20);
+    }
+
     /** Allows logging buffers to be tweaked via adb on debug builds but not on prod builds. */
     @Provides
     @SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttReceiverLogBuffer.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttReceiverLogBuffer.java
new file mode 100644
index 0000000..5c572e8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttReceiverLogBuffer.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2022 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.log.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.log.LogBuffer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/**
+ * A {@link LogBuffer} for
+ * {@link com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogger}.
+ */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface MediaTttReceiverLogBuffer {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttSenderLogBuffer.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttSenderLogBuffer.java
new file mode 100644
index 0000000..edab8c3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttSenderLogBuffer.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2022 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.log.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.log.LogBuffer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/**
+ * A {@link LogBuffer} for
+ * {@link com.android.systemui.media.taptotransfer.sender.MediaTttSenderLogger}.
+ */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface MediaTttSenderLogBuffer {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
index c3b4354..66c036c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
@@ -17,6 +17,9 @@
 package com.android.systemui.media.dagger;
 
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.log.dagger.MediaTttReceiverLogBuffer;
+import com.android.systemui.log.dagger.MediaTttSenderLogBuffer;
 import com.android.systemui.media.MediaDataManager;
 import com.android.systemui.media.MediaFlags;
 import com.android.systemui.media.MediaHierarchyManager;
@@ -27,8 +30,11 @@
 import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
 import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper;
 import com.android.systemui.media.taptotransfer.MediaTttFlags;
+import com.android.systemui.media.taptotransfer.common.MediaTttLogger;
 import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver;
+import com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogger;
 import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender;
+import com.android.systemui.media.taptotransfer.sender.MediaTttSenderLogger;
 
 import java.util.Optional;
 
@@ -112,6 +118,24 @@
         return Optional.of(controllerReceiverLazy.get());
     }
 
+    @Provides
+    @SysUISingleton
+    @MediaTttSenderLogger
+    static MediaTttLogger providesMediaTttSenderLogger(
+            @MediaTttSenderLogBuffer LogBuffer buffer
+    ) {
+        return new MediaTttLogger("Sender", buffer);
+    }
+
+    @Provides
+    @SysUISingleton
+    @MediaTttReceiverLogger
+    static MediaTttLogger providesMediaTttReceiverLogger(
+            @MediaTttReceiverLogBuffer LogBuffer buffer
+    ) {
+        return new MediaTttLogger("Receiver", buffer);
+    }
+
     /** */
     @Provides
     @SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index 3cd3905..0b23ad5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -54,6 +54,7 @@
             MediaOutputDialog mediaOutputDialog) {
         super(controller);
         mMediaOutputDialog = mediaOutputDialog;
+        setHasStableIds(true);
     }
 
     @Override
@@ -79,6 +80,20 @@
     }
 
     @Override
+    public long getItemId(int position) {
+        final int size = mController.getMediaDevices().size();
+        if (position == size && mController.isZeroMode()) {
+            return -1;
+        } else if (position < size) {
+            return ((List<MediaDevice>) (mController.getMediaDevices()))
+                    .get(position).getId().hashCode();
+        } else if (DEBUG) {
+            Log.d(TAG, "Incorrect position for item id: " + position);
+        }
+        return position;
+    }
+
+    @Override
     public int getItemCount() {
         if (mController.isZeroMode()) {
             // Add extra one for "pair new" or dynamic group
@@ -159,7 +174,7 @@
                         onCheckBoxClicked(false, device);
                     });
                     setCheckBoxColor(mCheckBox, mController.getColorActiveItem());
-                    initSessionSeekbar();
+                    initSeekbar(device);
                 } else if (!mController.hasAdjustVolumeUserRestriction() && currentlyConnected) {
                     mStatusIcon.setImageDrawable(
                             mContext.getDrawable(R.drawable.media_output_status_check));
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index 1f11d0c..c96aca3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -249,7 +249,7 @@
             mSeekBar.setMin(0);
             final int currentVolume = device.getCurrentVolume();
             if (mSeekBar.getProgress() != currentVolume) {
-                mSeekBar.setProgress(currentVolume);
+                mSeekBar.setProgress(currentVolume, true);
             }
             mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
                 @Override
@@ -278,7 +278,7 @@
             mSeekBar.setMin(0);
             final int currentVolume = mController.getSessionVolume();
             if (mSeekBar.getProgress() != currentVolume) {
-                mSeekBar.setProgress(currentVolume);
+                mSeekBar.setProgress(currentVolume, true);
             }
             mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
                 @Override
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
index 15b8f13..9c4b39d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
@@ -43,6 +43,7 @@
  */
 abstract class MediaTttChipControllerCommon<T : MediaTttChipState>(
     internal val context: Context,
+    internal val logger: MediaTttLogger,
     private val windowManager: WindowManager,
     private val viewUtil: ViewUtil,
     @Main private val mainExecutor: DelayableExecutor,
@@ -93,18 +94,26 @@
 
         // Cancel and re-set the chip timeout each time we get a new state.
         cancelChipViewTimeout?.run()
-        cancelChipViewTimeout = mainExecutor.executeDelayed(this::removeChip, TIMEOUT_MILLIS)
+        cancelChipViewTimeout = mainExecutor.executeDelayed(
+            { removeChip(MediaTttRemovalReason.REASON_TIMEOUT) },
+            chipState.getTimeoutMs()
+        )
     }
 
-    /** Hides the chip. */
-    fun removeChip() {
-        // TODO(b/203800347): We may not want to hide the chip if we're currently in a
-        //  TransferTriggered state: Once the user has initiated the transfer, they should be able
-        //  to move away from the receiver device but still see the status of the transfer.
+    /**
+     * Hides the chip.
+     *
+     * @param removalReason a short string describing why the chip was removed (timeout, state
+     *     change, etc.)
+     */
+    open fun removeChip(removalReason: String) {
         if (chipView == null) { return }
+        logger.logChipRemoval(removalReason)
         tapGestureDetector.removeOnGestureDetectedCallback(TAG)
         windowManager.removeView(chipView)
         chipView = null
+        // No need to time the chip out since it's already gone
+        cancelChipViewTimeout?.run()
     }
 
     /**
@@ -136,7 +145,7 @@
         // If the tap is within the chip bounds, we shouldn't hide the chip (in case users think the
         // chip is tappable).
         if (!viewUtil.touchIsWithinView(view, e.x, e.y)) {
-            removeChip()
+            removeChip(MediaTttRemovalReason.REASON_SCREEN_TAP)
         }
     }
 }
@@ -145,5 +154,9 @@
 // UpdateMediaTapToTransferReceiverDisplayTest
 private const val WINDOW_TITLE = "Media Transfer Chip View"
 private val TAG = MediaTttChipControllerCommon::class.simpleName!!
-@VisibleForTesting
-const val TIMEOUT_MILLIS = 3000L
+
+object MediaTttRemovalReason {
+    const val REASON_TIMEOUT = "TIMEOUT"
+    const val REASON_SCREEN_TAP = "SCREEN_TAP"
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipState.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipState.kt
index 2da48ce..6f60181 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipState.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipState.kt
@@ -52,6 +52,14 @@
             null
         }
     }
+
+    /**
+     * Returns the amount of time this chip should display on the screen before it times out and
+     * disappears. [MediaTttChipControllerCommon] will ensure that the timeout resets each time we
+     * receive a new state.
+     */
+    open fun getTimeoutMs(): Long = DEFAULT_TIMEOUT_MILLIS
 }
 
+private const val DEFAULT_TIMEOUT_MILLIS = 3000L
 private val TAG = MediaTttChipState::class.simpleName!!
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
new file mode 100644
index 0000000..d3b5bc6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2022 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.media.taptotransfer.common
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.dagger.MediaTttSenderLogBuffer
+
+/**
+ * A logger for media tap-to-transfer events.
+ *
+ * @property deviceTypeTag the type of device triggering the logs -- "Sender" or "Receiver".
+ */
+class MediaTttLogger(
+    private val deviceTypeTag: String,
+    @MediaTttSenderLogBuffer private val buffer: LogBuffer
+){
+    /** Logs a change in the chip state for the given [mediaRouteId]. */
+    fun logStateChange(stateName: String, mediaRouteId: String) {
+        buffer.log(
+            BASE_TAG + deviceTypeTag,
+            LogLevel.DEBUG,
+            {
+                str1 = stateName
+                str2 = mediaRouteId
+            },
+            { "State changed to $str1 for ID=$str2" }
+        )
+    }
+
+    /** Logs that we removed the chip for the given [reason]. */
+    fun logChipRemoval(reason: String) {
+        buffer.log(
+            BASE_TAG + deviceTypeTag,
+            LogLevel.DEBUG,
+            { str1 = reason },
+            { "Chip removed due to $str1" }
+        )
+    }
+}
+
+private const val BASE_TAG = "MediaTtt"
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 3d43ebe..1a96ddf 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.media.taptotransfer.common.MediaTttChipControllerCommon
+import com.android.systemui.media.taptotransfer.common.MediaTttLogger
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.gesture.TapGestureDetector
 import com.android.systemui.util.concurrency.DelayableExecutor
@@ -43,6 +44,7 @@
 class MediaTttChipControllerReceiver @Inject constructor(
     commandQueue: CommandQueue,
     context: Context,
+    @MediaTttReceiverLogger logger: MediaTttLogger,
     windowManager: WindowManager,
     viewUtil: ViewUtil,
     mainExecutor: DelayableExecutor,
@@ -50,6 +52,7 @@
     @Main private val mainHandler: Handler,
 ) : MediaTttChipControllerCommon<ChipStateReceiver>(
     context,
+    logger,
     windowManager,
     viewUtil,
     mainExecutor,
@@ -79,6 +82,7 @@
         appIcon: Icon?,
         appName: CharSequence?
     ) {
+        logger.logStateChange(stateIntToString(displayState), routeInfo.id)
         when(displayState) {
             StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER -> {
                 val packageName = routeInfo.packageName
@@ -97,7 +101,8 @@
                     )
                 }
             }
-            StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER -> removeChip()
+            StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER ->
+                removeChip(removalReason = FAR_FROM_SENDER)
             else ->
                 Log.e(RECEIVER_TAG, "Unhandled MediaTransferReceiverState $displayState")
         }
@@ -106,6 +111,16 @@
     override fun updateChipView(chipState: ChipStateReceiver, currentChipView: ViewGroup) {
         setIcon(chipState, currentChipView)
     }
+
+    private fun stateIntToString(@StatusBarManager.MediaTransferReceiverState state: Int): String {
+        return when (state) {
+            StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER -> CLOSE_TO_SENDER
+            StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER -> FAR_FROM_SENDER
+            else -> "INVALID: $state"
+        }
+    }
 }
 
 private const val RECEIVER_TAG = "MediaTapToTransferRcvr"
+private const val CLOSE_TO_SENDER = "CLOSE_TO_SENDER"
+private const val FAR_FROM_SENDER = "FAR_FROM_SENDER"
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogger.kt
new file mode 100644
index 0000000..54fc48d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogger.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2022 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.media.taptotransfer.receiver
+
+import java.lang.annotation.Documented
+import java.lang.annotation.Retention
+import java.lang.annotation.RetentionPolicy
+import javax.inject.Qualifier
+
+@Qualifier
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+annotation class MediaTttReceiverLogger
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
index 9b537fb..22424a4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
@@ -97,6 +97,7 @@
     }
 
     override fun showLoading() = true
+    override fun getTimeoutMs() = TRANSFER_TRIGGERED_TIMEOUT_MILLIS
 }
 
 /**
@@ -111,6 +112,7 @@
     }
 
     override fun showLoading() = true
+    override fun getTimeoutMs() = TRANSFER_TRIGGERED_TIMEOUT_MILLIS
 }
 
 /**
@@ -194,3 +196,8 @@
         return context.getString(R.string.media_transfer_failed)
     }
 }
+
+// Give the Transfer*Triggered states a longer timeout since those states represent an active
+// process and we should keep the user informed about it as long as possible (but don't allow it to
+// continue indefinitely).
+private const val TRANSFER_TRIGGERED_TIMEOUT_MILLIS = 15000L
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
index 180e4ee..da2aac4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
@@ -29,6 +29,8 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.media.taptotransfer.common.MediaTttChipControllerCommon
+import com.android.systemui.media.taptotransfer.common.MediaTttLogger
+import com.android.systemui.media.taptotransfer.common.MediaTttRemovalReason
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.gesture.TapGestureDetector
 import com.android.systemui.util.concurrency.DelayableExecutor
@@ -43,13 +45,22 @@
 class MediaTttChipControllerSender @Inject constructor(
     commandQueue: CommandQueue,
     context: Context,
+    @MediaTttSenderLogger logger: MediaTttLogger,
     windowManager: WindowManager,
     viewUtil: ViewUtil,
     @Main mainExecutor: DelayableExecutor,
     tapGestureDetector: TapGestureDetector,
 ) : MediaTttChipControllerCommon<ChipStateSender>(
-    context, windowManager, viewUtil, mainExecutor,  tapGestureDetector, R.layout.media_ttt_chip
+    context,
+    logger,
+    windowManager,
+    viewUtil,
+    mainExecutor,
+    tapGestureDetector,
+    R.layout.media_ttt_chip
 ) {
+    private var currentlyDisplayedChipState: ChipStateSender? = null
+
     private val commandQueueCallbacks = object : CommandQueue.Callbacks {
         override fun updateMediaTapToTransferSenderDisplay(
                 @StatusBarManager.MediaTransferSenderState displayState: Int,
@@ -71,6 +82,7 @@
         routeInfo: MediaRoute2Info,
         undoCallback: IUndoMediaTransferCallback?
     ) {
+        logger.logStateChange(stateIntToString(displayState), routeInfo.id)
         val appPackageName = routeInfo.packageName
         val otherDeviceName = routeInfo.name.toString()
         val chipState = when(displayState) {
@@ -90,7 +102,7 @@
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED ->
                 TransferFailed(appPackageName)
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER -> {
-                removeChip()
+                removeChip(removalReason = FAR_FROM_RECEIVER)
                 null
             }
             else -> {
@@ -106,6 +118,8 @@
 
     /** Displays the chip view for the given state. */
     override fun updateChipView(chipState: ChipStateSender, currentChipView: ViewGroup) {
+        currentlyDisplayedChipState = chipState
+
         // App icon
         setIcon(chipState, currentChipView)
 
@@ -129,6 +143,43 @@
         currentChipView.requireViewById<View>(R.id.failure_icon).visibility =
             if (showFailure) { View.VISIBLE } else { View.GONE }
     }
+
+    override fun removeChip(removalReason: String) {
+        // Don't remove the chip if we're mid-transfer since the user should still be able to
+        // see the status of the transfer. (But do remove it if it's finally timed out.)
+        if ((currentlyDisplayedChipState is TransferToReceiverTriggered ||
+                currentlyDisplayedChipState is TransferToThisDeviceTriggered)
+            && removalReason != MediaTttRemovalReason.REASON_TIMEOUT) {
+            return
+        }
+        super.removeChip(removalReason)
+        currentlyDisplayedChipState = null
+    }
+
+    private fun stateIntToString(@StatusBarManager.MediaTransferSenderState state: Int): String {
+        return when(state) {
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST ->
+                "ALMOST_CLOSE_TO_START_CAST"
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST ->
+                "ALMOST_CLOSE_TO_END_CAST"
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED ->
+                "TRANSFER_TO_RECEIVER_TRIGGERED"
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED ->
+                "TRANSFER_TO_THIS_DEVICE_TRIGGERED"
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED ->
+                "TRANSFER_TO_RECEIVER_SUCCEEDED"
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED ->
+                "TRANSFER_TO_THIS_DEVICE_SUCCEEDED"
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED ->
+                "TRANSFER_TO_RECEIVER_FAILED"
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED ->
+                "TRANSFER_TO_THIS_DEVICE_FAILED"
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER ->
+                FAR_FROM_RECEIVER
+            else -> "INVALID: $state"
+        }
+    }
 }
 
 const val SENDER_TAG = "MediaTapToTransferSender"
+private const val FAR_FROM_RECEIVER = "FAR_FROM_RECEIVER"
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt
new file mode 100644
index 0000000..4393af9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2022 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.media.taptotransfer.sender
+
+import java.lang.annotation.Documented
+import java.lang.annotation.Retention
+import java.lang.annotation.RetentionPolicy
+import javax.inject.Qualifier
+
+@Qualifier
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+annotation class MediaTttSenderLogger
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 3ab1216..ec6094d 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -757,7 +757,8 @@
                         | WindowManager.LayoutParams.FLAG_SLIPPERY,
                 PixelFormat.TRANSLUCENT);
         mOrientationParams.setTitle("SecondaryHomeHandle" + mContext.getDisplayId());
-        mOrientationParams.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION;
+        mOrientationParams.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION
+                | WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
         mWindowManager.addView(mOrientationHandle, mOrientationParams);
         mOrientationHandle.setVisibility(View.GONE);
         mOrientationParams.setFitInsetsTypes(0 /* types*/);
@@ -1565,7 +1566,8 @@
         }
         lp.token = new Binder();
         lp.accessibilityTitle = mContext.getString(R.string.nav_bar);
-        lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
+        lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC
+                | WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
         lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
         lp.windowAnimations = 0;
         lp.setTitle("NavigationBar" + mContext.getDisplayId());
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 9ea2763..4dacf5d 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -263,6 +263,15 @@
                     // Notify FalsingManager that an intentional gesture has occurred.
                     // TODO(b/186519446): use a different method than isFalseTouch
                     mFalsingManager.isFalseTouch(BACK_GESTURE);
+                    // Only inject back keycodes when ahead-of-time back dispatching is disabled.
+                    if (mBackAnimation == null) {
+                        boolean sendDown = sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
+                        boolean sendUp = sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
+                        if (DEBUG_MISSING_GESTURE) {
+                            Log.d(DEBUG_MISSING_GESTURE_TAG, "Triggered back: down="
+                                    + sendDown + ", up=" + sendUp);
+                        }
+                    }
 
                     mOverviewProxyService.notifyBackAction(true, (int) mDownPoint.x,
                             (int) mDownPoint.y, false /* isButton */, !mIsOnLeftEdge);
@@ -936,6 +945,9 @@
 
     public void setBackAnimation(BackAnimation backAnimation) {
         mBackAnimation = backAnimation;
+        if (mEdgeBackPlugin != null && mEdgeBackPlugin instanceof NavigationBarEdgePanel) {
+            ((NavigationBarEdgePanel) mEdgeBackPlugin).setBackAnimation(backAnimation);
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
index a6bad15..a6919e8 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
@@ -280,7 +280,7 @@
                 }
             };
     private BackCallback mBackCallback;
-    private final BackAnimation mBackAnimation;
+    private BackAnimation mBackAnimation;
 
     public NavigationBarEdgePanel(Context context,
             BackAnimation backAnimation) {
@@ -385,6 +385,10 @@
         mShowProtection = !isPrimaryDisplay;
     }
 
+    public void setBackAnimation(BackAnimation backAnimation) {
+        mBackAnimation = backAnimation;
+    }
+
     @Override
     public void onDestroy() {
         cancelFailsafe();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
index 32e0805..a49d3fd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
@@ -204,6 +204,9 @@
                                 | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS
                                 | Context.BIND_WAIVE_PRIORITY,
                         mUser);
+                if (!mIsBound) {
+                    mContext.unbindService(this);
+                }
             } catch (SecurityException e) {
                 Log.e(TAG, "Failed to bind to service", e);
                 mIsBound = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 8a02e59..5932a64 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -310,11 +310,11 @@
                 long requestId, @BiometricMultiSensorMode int multiSensorConfig) {
         }
 
-        /** @see IStatusBar#onBiometricAuthenticated() */
-        default void onBiometricAuthenticated() {
+        /** @see IStatusBar#onBiometricAuthenticated(int) */
+        default void onBiometricAuthenticated(@Modality int modality) {
         }
 
-        /** @see IStatusBar#onBiometricHelp(String) */
+        /** @see IStatusBar#onBiometricHelp(int, String) */
         default void onBiometricHelp(@Modality int modality, String message) {
         }
 
@@ -963,9 +963,11 @@
     }
 
     @Override
-    public void onBiometricAuthenticated() {
+    public void onBiometricAuthenticated(@Modality int modality) {
         synchronized (mLock) {
-            mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED).sendToTarget();
+            SomeArgs args = SomeArgs.obtain();
+            args.argi1 = modality;
+            mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED, args).sendToTarget();
         }
     }
 
@@ -1465,9 +1467,11 @@
                     break;
                 }
                 case MSG_BIOMETRIC_AUTHENTICATED: {
+                    SomeArgs someArgs = (SomeArgs) msg.obj;
                     for (int i = 0; i < mCallbacks.size(); i++) {
-                        mCallbacks.get(i).onBiometricAuthenticated();
+                        mCallbacks.get(i).onBiometricAuthenticated(someArgs.argi1 /* modality */);
                     }
+                    someArgs.recycle();
                     break;
                 }
                 case MSG_BIOMETRIC_HELP: {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 0509a7c..ccec0c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -50,6 +50,7 @@
 import android.hardware.fingerprint.FingerprintManager;
 import android.os.BatteryManager;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -122,7 +123,7 @@
     private final Context mContext;
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final KeyguardStateController mKeyguardStateController;
-    private final StatusBarStateController mStatusBarStateController;
+    protected final StatusBarStateController mStatusBarStateController;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private ViewGroup mIndicationArea;
     private KeyguardIndicationTextView mTopIndicationView;
@@ -138,6 +139,7 @@
     private final IActivityManager mIActivityManager;
     private final FalsingManager mFalsingManager;
     private final KeyguardBypassController mKeyguardBypassController;
+    private final Handler mHandler;
 
     protected KeyguardIndicationRotateTextViewController mRotateTextViewController;
     private BroadcastReceiver mBroadcastReceiver;
@@ -194,7 +196,9 @@
      * Creates a new KeyguardIndicationController and registers callbacks.
      */
     @Inject
-    public KeyguardIndicationController(Context context,
+    public KeyguardIndicationController(
+            Context context,
+            @Main Looper mainLooper,
             WakeLock.Builder wakeLockBuilder,
             KeyguardStateController keyguardStateController,
             StatusBarStateController statusBarStateController,
@@ -230,6 +234,19 @@
         mKeyguardBypassController = keyguardBypassController;
         mScreenLifecycle = screenLifecycle;
         mScreenLifecycle.addObserver(mScreenObserver);
+
+        mHandler = new Handler(mainLooper) {
+            @Override
+            public void handleMessage(Message msg) {
+                if (msg.what == MSG_HIDE_TRANSIENT) {
+                    hideTransientIndication();
+                } else if (msg.what == MSG_SHOW_ACTION_TO_UNLOCK) {
+                    showActionToUnlock();
+                } else if (msg.what == MSG_HIDE_BIOMETRIC_MESSAGE) {
+                    hideBiometricMessage();
+                }
+            }
+        };
     }
 
     /** Call this after construction to finish setting up the instance. */
@@ -242,7 +259,6 @@
         mDockManager.addAlignmentStateListener(
                 alignState -> mHandler.post(() -> handleAlignStateChanged(alignState)));
         mKeyguardUpdateMonitor.registerCallback(getKeyguardCallback());
-        mKeyguardUpdateMonitor.registerCallback(mTickReceiver);
         mStatusBarStateController.addCallback(mStatusBarStateListener);
         mKeyguardStateController.addCallback(mKeyguardStateCallback);
 
@@ -260,7 +276,7 @@
             mLockScreenIndicationView,
             mExecutor,
             mStatusBarStateController);
-        updateIndication(false /* animate */);
+        updateDeviceEntryIndication(false /* animate */);
         updateOrganizedOwnedDevice();
         if (mBroadcastReceiver == null) {
             // Update the disclosure proactively to avoid IPC on the critical path.
@@ -288,7 +304,7 @@
         }
         if (!alignmentIndication.equals(mAlignmentIndication)) {
             mAlignmentIndication = alignmentIndication;
-            updateIndication(false);
+            updateDeviceEntryIndication(false);
         }
     }
 
@@ -309,28 +325,30 @@
         return mUpdateMonitorCallback;
     }
 
-    /**
-     * This method also doesn't update transient messages like biometrics since those messages
-     * are also updated separately.
-     */
-    private void updatePersistentIndications(boolean animate, int userId) {
-        updateDisclosure();
-        updateOwnerInfo();
-        updateBattery(animate);
-        updateUserLocked(userId);
-        updateTrust(userId, getTrustGrantedIndication(), getTrustManagedIndication());
-        updateAlignment();
-        updateLogoutView();
-        updateResting();
+    private void updateLockScreenIndications(boolean animate, int userId) {
+        // update transient messages:
+        updateBiometricMessage();
+        updateTransient();
+
+        // Update persistent messages. The following methods should only be called if we're on the
+        // lock screen:
+        updateLockScreenDisclosureMsg();
+        updateLockScreenOwnerInfo();
+        updateLockScreenBatteryMsg(animate);
+        updateLockScreenUserLockedMsg(userId);
+        updateLockScreenTrustMsg(userId, getTrustGrantedIndication(), getTrustManagedIndication());
+        updateLockScreenAlignmentMsg();
+        updateLockScreenLogoutView();
+        updateLockScreenRestingMsg();
     }
 
     private void updateOrganizedOwnedDevice() {
         // avoid calling this method since it has an IPC
         mOrganizationOwnedDevice = whitelistIpcs(this::isOrganizationOwnedDevice);
-        updatePersistentIndications(false, KeyguardUpdateMonitor.getCurrentUser());
+        updateDeviceEntryIndication(false);
     }
 
-    private void updateDisclosure() {
+    private void updateLockScreenDisclosureMsg() {
         if (mOrganizationOwnedDevice) {
             mBackgroundExecutor.execute(() -> {
                 final CharSequence organizationName = getOrganizationOwnedDeviceOrganizationName();
@@ -374,7 +392,7 @@
         }
     }
 
-    private void updateOwnerInfo() {
+    private void updateLockScreenOwnerInfo() {
         // Check device owner info on a bg thread.
         // It makes multiple IPCs that could block the thread it's run on.
         mBackgroundExecutor.execute(() -> {
@@ -406,7 +424,7 @@
         });
     }
 
-    private void updateBattery(boolean animate) {
+    private void updateLockScreenBatteryMsg(boolean animate) {
         if (mPowerPluggedIn || mEnableBatteryDefender) {
             String powerIndication = computePowerIndication();
             if (DEBUG_CHARGING_SPEED) {
@@ -426,7 +444,7 @@
         }
     }
 
-    private void updateUserLocked(int userId) {
+    private void updateLockScreenUserLockedMsg(int userId) {
         if (!mKeyguardUpdateMonitor.isUserUnlocked(userId)) {
             mRotateTextViewController.updateIndication(
                     INDICATION_TYPE_USER_LOCKED,
@@ -442,6 +460,11 @@
     }
 
     private void updateBiometricMessage() {
+        if (mDozing) {
+            updateDeviceEntryIndication(false);
+            return;
+        }
+
         if (!TextUtils.isEmpty(mBiometricMessage)) {
             mRotateTextViewController.updateIndication(
                     INDICATION_TYPE_BIOMETRIC_MESSAGE,
@@ -455,25 +478,22 @@
         } else {
             mRotateTextViewController.hideIndication(INDICATION_TYPE_BIOMETRIC_MESSAGE);
         }
-
-        if (mDozing) {
-            updateIndication(false);
-        }
     }
 
     private void updateTransient() {
+        if (mDozing) {
+            updateDeviceEntryIndication(false);
+            return;
+        }
+
         if (!TextUtils.isEmpty(mTransientIndication)) {
             mRotateTextViewController.showTransient(mTransientIndication);
         } else {
             mRotateTextViewController.hideTransient();
         }
-
-        if (mDozing) {
-            updateIndication(false);
-        }
     }
 
-    private void updateTrust(int userId, CharSequence trustGrantedIndication,
+    private void updateLockScreenTrustMsg(int userId, CharSequence trustGrantedIndication,
             CharSequence trustManagedIndication) {
         if (!TextUtils.isEmpty(trustGrantedIndication)
                 && mKeyguardUpdateMonitor.getUserHasTrust(userId)) {
@@ -499,7 +519,7 @@
         }
     }
 
-    private void updateAlignment() {
+    private void updateLockScreenAlignmentMsg() {
         if (!TextUtils.isEmpty(mAlignmentIndication)) {
             mRotateTextViewController.updateIndication(
                     INDICATION_TYPE_ALIGNMENT,
@@ -514,7 +534,7 @@
         }
     }
 
-    private void updateResting() {
+    private void updateLockScreenRestingMsg() {
         if (!TextUtils.isEmpty(mRestingIndication)
                 && !mRotateTextViewController.hasIndications()) {
             mRotateTextViewController.updateIndication(
@@ -529,7 +549,7 @@
         }
     }
 
-    private void updateLogoutView() {
+    private void updateLockScreenLogoutView() {
         final boolean shouldShowLogout = mKeyguardUpdateMonitor.isLogoutEnabled()
                 && KeyguardUpdateMonitor.getCurrentUser() != UserHandle.USER_SYSTEM;
         if (shouldShowLogout) {
@@ -608,7 +628,7 @@
             if (!mHandler.hasMessages(MSG_HIDE_TRANSIENT)) {
                 hideTransientIndication();
             }
-            updateIndication(false);
+            updateDeviceEntryIndication(false);
         } else if (!visible) {
             // If we unlock and return to keyguard quickly, previous error should not be shown
             hideTransientIndication();
@@ -620,7 +640,7 @@
      */
     public void setRestingIndication(String restingIndication) {
         mRestingIndication = restingIndication;
-        updateIndication(false);
+        updateDeviceEntryIndication(false);
     }
 
     /**
@@ -697,6 +717,10 @@
      * Shows {@param biometricMessage} until it is hidden by {@link #hideBiometricMessage}.
      */
     private void showBiometricMessage(CharSequence biometricMessage) {
+        if (TextUtils.equals(biometricMessage, mBiometricMessage)) {
+            return;
+        }
+
         mBiometricMessage = biometricMessage;
 
         mHandler.removeMessages(MSG_SHOW_ACTION_TO_UNLOCK);
@@ -725,7 +749,12 @@
         }
     }
 
-    protected final void updateIndication(boolean animate) {
+    /**
+     * Updates message shown to the user. If the device is dozing, a single message with the highest
+     * precedence is shown. If the device is not dozing (on the lock screen), then several messages
+     * may continuously be cycled through.
+     */
+    protected final void updateDeviceEntryIndication(boolean animate) {
         if (!mVisible) {
             return;
         }
@@ -734,44 +763,37 @@
         mIndicationArea.setVisibility(VISIBLE);
 
         // Walk down a precedence-ordered list of what indication
-        // should be shown based on user or device state
-        // AoD
+        // should be shown based on device state
         if (mDozing) {
             mLockScreenIndicationView.setVisibility(View.GONE);
             mTopIndicationView.setVisibility(VISIBLE);
             // When dozing we ignore any text color and use white instead, because
             // colors can be hard to read in low brightness.
             mTopIndicationView.setTextColor(Color.WHITE);
+
+            CharSequence newIndication = null;
             if (!TextUtils.isEmpty(mBiometricMessage)) {
-                mWakeLock.setAcquired(true);
-                mTopIndicationView.switchIndication(mBiometricMessage, null,
-                        true, () -> mWakeLock.setAcquired(false));
+                newIndication = mBiometricMessage;
             } else if (!TextUtils.isEmpty(mTransientIndication)) {
-                mWakeLock.setAcquired(true);
-                mTopIndicationView.switchIndication(mTransientIndication, null,
-                        true, () -> mWakeLock.setAcquired(false));
+                newIndication = mTransientIndication;
             } else if (!mBatteryPresent) {
                 // If there is no battery detected, hide the indication and bail
                 mIndicationArea.setVisibility(GONE);
+                return;
             } else if (!TextUtils.isEmpty(mAlignmentIndication)) {
-                mTopIndicationView.switchIndication(mAlignmentIndication, null,
-                        false /* animate */, null /* onAnimationEndCallback */);
+                newIndication = mAlignmentIndication;
                 mTopIndicationView.setTextColor(mContext.getColor(R.color.misalignment_text_color));
             } else if (mPowerPluggedIn || mEnableBatteryDefender) {
-                String indication = computePowerIndication();
-                if (animate) {
-                    mWakeLock.setAcquired(true);
-                    mTopIndicationView.switchIndication(indication, null, true /* animate */,
-                            () -> mWakeLock.setAcquired(false));
-                } else {
-                    mTopIndicationView.switchIndication(indication, null, false /* animate */,
-                            null /* onAnimationEndCallback */);
-                }
+                newIndication = computePowerIndication();
             } else {
-                String percentage = NumberFormat.getPercentInstance()
+                newIndication = NumberFormat.getPercentInstance()
                         .format(mBatteryLevel / 100f);
-                mTopIndicationView.switchIndication(percentage, null /* indication */,
-                        false /* animate */, null /* onAnimationEnd*/);
+            }
+
+            if (!TextUtils.equals(mTopIndicationView.getText(), newIndication)) {
+                mWakeLock.setAcquired(true);
+                mTopIndicationView.switchIndication(newIndication, null,
+                        true, () -> mWakeLock.setAcquired(false));
             }
             return;
         }
@@ -780,7 +802,7 @@
         mTopIndicationView.setVisibility(GONE);
         mTopIndicationView.setText(null);
         mLockScreenIndicationView.setVisibility(View.VISIBLE);
-        updatePersistentIndications(animate, KeyguardUpdateMonitor.getCurrentUser());
+        updateLockScreenIndications(animate, KeyguardUpdateMonitor.getCurrentUser());
     }
 
     protected String computePowerIndication() {
@@ -842,29 +864,6 @@
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
     }
 
-    private final KeyguardUpdateMonitorCallback mTickReceiver =
-            new KeyguardUpdateMonitorCallback() {
-                @Override
-                public void onTimeChanged() {
-                    if (mVisible) {
-                        updateIndication(false /* animate */);
-                    }
-                }
-            };
-
-    private final Handler mHandler = new Handler() {
-        @Override
-        public void handleMessage(Message msg) {
-            if (msg.what == MSG_HIDE_TRANSIENT) {
-                hideTransientIndication();
-            } else if (msg.what == MSG_SHOW_ACTION_TO_UNLOCK) {
-                showActionToUnlock();
-            } else if (msg.what == MSG_HIDE_BIOMETRIC_MESSAGE) {
-                hideBiometricMessage();
-            }
-        }
-    };
-
     /**
      * Show message on the keyguard for how the user can unlock/enter their device.
      */
@@ -929,7 +928,7 @@
         pw.println("  mBiometricMessage: " + mBiometricMessage);
         pw.println("  mBatteryLevel: " + mBatteryLevel);
         pw.println("  mBatteryPresent: " + mBatteryPresent);
-        pw.println("  mTextView.getText(): " + (
+        pw.println("  AOD text: " + (
                 mTopIndicationView == null ? null : mTopIndicationView.getText()));
         pw.println("  computePowerIndication(): " + computePowerIndication());
         pw.println("  trustGrantedIndication: " + getTrustGrantedIndication());
@@ -940,6 +939,13 @@
         public static final int HIDE_DELAY_MS = 5000;
 
         @Override
+        public void onTimeChanged() {
+            if (mVisible) {
+                updateDeviceEntryIndication(false /* animate */);
+            }
+        }
+
+        @Override
         public void onRefreshBatteryInfo(BatteryStatus status) {
             boolean isChargingOrFull = status.status == BatteryManager.BATTERY_STATUS_CHARGING
                     || status.isCharged();
@@ -962,7 +968,7 @@
                 Log.e(TAG, "Error calling IBatteryStats: ", e);
                 mChargingTimeRemaining = -1;
             }
-            updateIndication(!wasPluggedIn && mPowerPluggedInWired);
+            updateDeviceEntryIndication(!wasPluggedIn && mPowerPluggedInWired);
             if (mDozing) {
                 if (!wasPluggedIn && mPowerPluggedIn) {
                     showTransientIndication(computePowerIndication());
@@ -1084,14 +1090,13 @@
             if (KeyguardUpdateMonitor.getCurrentUser() != userId) {
                 return;
             }
-            updateTrust(userId, getTrustGrantedIndication(), getTrustManagedIndication());
+            updateDeviceEntryIndication(false);
         }
 
         @Override
         public void showTrustGrantedMessage(CharSequence message) {
             mTrustGrantedIndication = message;
-            updateTrust(KeyguardUpdateMonitor.getCurrentUser(), getTrustGrantedIndication(),
-                    getTrustManagedIndication());
+            updateDeviceEntryIndication(false);
         }
 
         @Override
@@ -1125,21 +1130,21 @@
         @Override
         public void onUserSwitchComplete(int userId) {
             if (mVisible) {
-                updateIndication(false);
+                updateDeviceEntryIndication(false);
             }
         }
 
         @Override
         public void onUserUnlocked() {
             if (mVisible) {
-                updateIndication(false);
+                updateDeviceEntryIndication(false);
             }
         }
 
         @Override
         public void onLogoutEnabledChanged() {
             if (mVisible) {
-                updateIndication(false);
+                updateDeviceEntryIndication(false);
             }
         }
 
@@ -1167,7 +1172,7 @@
             if (mDozing) {
                 hideBiometricMessage();
             }
-            updateIndication(false);
+            updateDeviceEntryIndication(false);
         }
     };
 
@@ -1175,7 +1180,7 @@
             new KeyguardStateController.Callback() {
         @Override
         public void onUnlockedChanged() {
-            updateIndication(false);
+            updateDeviceEntryIndication(false);
         }
 
         @Override
@@ -1185,7 +1190,7 @@
                 mTopIndicationView.clearMessages();
                 mRotateTextViewController.clearMessages();
             } else {
-                updatePersistentIndications(false, KeyguardUpdateMonitor.getCurrentUser());
+                updateDeviceEntryIndication(false);
             }
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index de2346a..cc16ce2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -3978,8 +3978,14 @@
 
                 mActivityLaunchAnimator.startPendingIntentWithAnimation(
                         controller, animate, intent.getCreatorPackage(),
-                        (animationAdapter) -> intent.sendAndReturnResult(null, 0, null, null, null,
-                                null, getActivityOptions(mDisplayId, animationAdapter)));
+                        (animationAdapter) -> {
+                            ActivityOptions options = new ActivityOptions(
+                                    getActivityOptions(mDisplayId, animationAdapter));
+                            // TODO b/221255671: restrict this to only be set for notifications
+                            options.setEligibleForLegacyPermissionPrompt(true);
+                            return intent.sendAndReturnResult(null, 0, null, null, null,
+                                    null, options.toBundle());
+                        });
             } catch (PendingIntent.CanceledException e) {
                 // the stack trace isn't very helpful here.
                 // Just log the exception message.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index ebe91c7..8b25c2b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -292,20 +292,11 @@
     }
 
     public void updateControlScreenOff() {
-        final boolean controlScreenOff = shouldControlUnlockedScreenOff()
-                || (!getDisplayNeedsBlanking() && getAlwaysOn() && mKeyguardShowing);
-        setControlScreenOffAnimation(controlScreenOff);
-    }
-
-    /**
-     * Whether we're capable of controlling the screen off animation if we want to. This isn't
-     * possible if AOD isn't even enabled or if the flag is disabled, or if the display needs
-     * blanking.
-     */
-    public boolean canControlUnlockedScreenOff() {
-        return getAlwaysOn()
-                && mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ANIMATIONS)
-                && !getDisplayNeedsBlanking();
+        if (!getDisplayNeedsBlanking()) {
+            final boolean controlScreenOff =
+                    getAlwaysOn() && (mKeyguardShowing || shouldControlUnlockedScreenOff());
+            setControlScreenOffAnimation(controlScreenOff);
+        }
     }
 
     /**
@@ -318,7 +309,8 @@
      * disabled for a11y.
      */
     public boolean shouldControlUnlockedScreenOff() {
-        return mUnlockedScreenOffAnimationController.shouldPlayUnlockedScreenOffAnimation();
+        return canControlUnlockedScreenOff()
+                && mUnlockedScreenOffAnimationController.shouldPlayUnlockedScreenOffAnimation();
     }
 
     public boolean shouldDelayKeyguardShow() {
@@ -350,6 +342,16 @@
         return getAlwaysOn() && mKeyguardShowing;
     }
 
+    /**
+     * Whether we're capable of controlling the screen off animation if we want to. This isn't
+     * possible if AOD isn't even enabled or if the flag is disabled.
+     */
+    public boolean canControlUnlockedScreenOff() {
+        return getAlwaysOn()
+                && mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ANIMATIONS)
+                && !getDisplayNeedsBlanking();
+    }
+
     private boolean getBoolean(String propName, int resId) {
         return SystemProperties.getBoolean(propName, mResources.getBoolean(resId));
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index 8b0eaec..c11d450 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -61,14 +61,6 @@
 ) : WakefulnessLifecycle.Observer, ScreenOffAnimation {
 
     private lateinit var mCentralSurfaces: CentralSurfaces
-
-    /**
-     * Whether or not [initialize] has been called to provide us with the StatusBar,
-     * NotificationPanelViewController, and LightRevealSrim so that we can run the unlocked screen
-     * off animation.
-     */
-    private var initialized = false
-
     private lateinit var lightRevealScrim: LightRevealScrim
 
     private var animatorDurationScale = 1f
@@ -124,7 +116,6 @@
         centralSurfaces: CentralSurfaces,
         lightRevealScrim: LightRevealScrim
     ) {
-        this.initialized = true
         this.lightRevealScrim = lightRevealScrim
         this.mCentralSurfaces = centralSurfaces
 
@@ -271,18 +262,6 @@
      * on the current state of the device.
      */
     fun shouldPlayUnlockedScreenOffAnimation(): Boolean {
-        // If we haven't been initialized yet, we don't have a StatusBar/LightRevealScrim yet, so we
-        // can't perform the animation.
-        if (!initialized) {
-            return false
-        }
-
-        // If the device isn't in a state where we can control unlocked screen off (no AOD enabled,
-        // power save, etc.) then we shouldn't try to do so.
-        if (!dozeParameters.get().canControlUnlockedScreenOff()) {
-            return false
-        }
-
         // If we explicitly already decided not to play the screen off animation, then never change
         // our mind.
         if (decidedToAnimateGoingToSleep == false) {
@@ -325,7 +304,7 @@
     }
 
     override fun shouldDelayDisplayDozeTransition(): Boolean =
-        shouldPlayUnlockedScreenOffAnimation()
+        dozeParameters.get().shouldControlUnlockedScreenOff()
 
     /**
      * Whether we're doing the light reveal animation or we're done with that and animating in the
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
index 796af11..58b4af4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
@@ -134,6 +134,16 @@
     }
 
     @Test
+    public void moveWindowMagnifierToPosition() throws RemoteException {
+        mIWindowMagnificationConnection.moveWindowMagnifierToPosition(TEST_DISPLAY,
+                100f, 200f, mAnimationCallback);
+        waitForIdleSync();
+
+        verify(mWindowMagnificationController).moveWindowMagnifierToPosition(
+                eq(100f), eq(200f), any(IRemoteMagnificationAnimationCallback.class));
+    }
+
+    @Test
     public void showMagnificationButton() throws RemoteException {
         mIWindowMagnificationConnection.showMagnificationButton(TEST_DISPLAY,
                 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MockMagnificationAnimationCallback.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MockMagnificationAnimationCallback.java
new file mode 100644
index 0000000..30bff09
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MockMagnificationAnimationCallback.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2022 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 android.os.RemoteException;
+import android.view.accessibility.IRemoteMagnificationAnimationCallback;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class MockMagnificationAnimationCallback extends IRemoteMagnificationAnimationCallback.Stub {
+
+    private final CountDownLatch mCountDownLatch;
+    private final AtomicInteger mSuccessCount;
+    private final AtomicInteger mFailedCount;
+
+    MockMagnificationAnimationCallback(CountDownLatch countDownLatch) {
+        mCountDownLatch = countDownLatch;
+        mSuccessCount = new AtomicInteger();
+        mFailedCount = new AtomicInteger();
+    }
+
+    public int getSuccessCount() {
+        return mSuccessCount.get();
+    }
+
+    public int getFailedCount() {
+        return mFailedCount.get();
+    }
+
+    @Override
+    public void onResult(boolean success) throws RemoteException {
+        mCountDownLatch.countDown();
+        if (success) {
+            mSuccessCount.getAndIncrement();
+        } else {
+            mFailedCount.getAndIncrement();
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
index 3cc177d..21c3d6e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
@@ -60,6 +60,8 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 
 @Ignore
@@ -218,6 +220,29 @@
     }
 
     @Test
+    public void enableWindowMagnificationWithUnchanged_enabling_expectedValuesToDefault()
+            throws InterruptedException {
+        final CountDownLatch countDownLatch = new CountDownLatch(2);
+        final MockMagnificationAnimationCallback animationCallback =
+                new MockMagnificationAnimationCallback(countDownLatch);
+
+        enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+                animationCallback);
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    mWindowMagnificationAnimationController.enableWindowMagnification(Float.NaN,
+                            Float.NaN, Float.NaN, animationCallback);
+                });
+
+        assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS));
+        // The callback in 2nd enableWindowMagnification will return true
+        assertEquals(1, animationCallback.getSuccessCount());
+        // The callback in 1st enableWindowMagnification will return false
+        assertEquals(1, animationCallback.getFailedCount());
+        verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X, DEFAULT_CENTER_Y);
+    }
+
+    @Test
     public void enableWindowMagnificationWithScaleOne_enabled_AnimationAndInvokeCallback()
             throws RemoteException {
         enableWindowMagnificationWithoutAnimation();
@@ -425,6 +450,102 @@
     }
 
     @Test
+    public void moveWindowMagnifierToPosition_enabled_expectedValues()
+            throws InterruptedException {
+        final CountDownLatch countDownLatch = new CountDownLatch(1);
+        final MockMagnificationAnimationCallback animationCallback =
+                new MockMagnificationAnimationCallback(countDownLatch);
+        final float targetCenterX = DEFAULT_CENTER_X + 100;
+        final float targetCenterY = DEFAULT_CENTER_Y + 100;
+        enableWindowMagnificationWithoutAnimation();
+
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
+                    targetCenterX, targetCenterY, animationCallback);
+        });
+
+        assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS));
+        assertEquals(1, animationCallback.getSuccessCount());
+        assertEquals(0, animationCallback.getFailedCount());
+        verifyFinalSpec(DEFAULT_SCALE, targetCenterX, targetCenterY);
+    }
+
+    @Test
+    public void moveWindowMagnifierToPositionMultipleTimes_enabled_expectedValuesToLastOne()
+            throws InterruptedException {
+        final CountDownLatch countDownLatch = new CountDownLatch(4);
+        final MockMagnificationAnimationCallback animationCallback =
+                new MockMagnificationAnimationCallback(countDownLatch);
+        enableWindowMagnificationWithoutAnimation();
+
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
+                    DEFAULT_CENTER_X + 10, DEFAULT_CENTER_Y + 10, animationCallback);
+            mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
+                    DEFAULT_CENTER_X + 20, DEFAULT_CENTER_Y + 20, animationCallback);
+            mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
+                    DEFAULT_CENTER_X + 30, DEFAULT_CENTER_Y + 30, animationCallback);
+            mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
+                    DEFAULT_CENTER_X + 40, DEFAULT_CENTER_Y + 40, animationCallback);
+        });
+
+        assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS));
+        // only the last one callback will return true
+        assertEquals(1, animationCallback.getSuccessCount());
+        // the others will return false
+        assertEquals(3, animationCallback.getFailedCount());
+        verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X + 40, DEFAULT_CENTER_Y + 40);
+    }
+
+    @Test
+    public void moveWindowMagnifierToPosition_enabling_expectedValuesToLastOne()
+            throws InterruptedException {
+        final CountDownLatch countDownLatch = new CountDownLatch(2);
+        final MockMagnificationAnimationCallback animationCallback =
+                new MockMagnificationAnimationCallback(countDownLatch);
+        final float targetCenterX = DEFAULT_CENTER_X + 100;
+        final float targetCenterY = DEFAULT_CENTER_Y + 100;
+
+        enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+                animationCallback);
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
+                            targetCenterX, targetCenterY, animationCallback);
+                });
+
+        assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS));
+        // The callback in moveWindowMagnifierToPosition will return true
+        assertEquals(1, animationCallback.getSuccessCount());
+        // The callback in enableWindowMagnification will return false
+        assertEquals(1, animationCallback.getFailedCount());
+        verifyFinalSpec(DEFAULT_SCALE, targetCenterX, targetCenterY);
+    }
+
+    @Test
+    public void moveWindowMagnifierToPositionWithCenterUnchanged_enabling_expectedValuesToDefault()
+            throws InterruptedException {
+        final CountDownLatch countDownLatch = new CountDownLatch(2);
+        final MockMagnificationAnimationCallback animationCallback =
+                new MockMagnificationAnimationCallback(countDownLatch);
+
+        enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+                animationCallback);
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
+                            Float.NaN, Float.NaN, animationCallback);
+                });
+
+        assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS));
+        // The callback in moveWindowMagnifierToPosition will return true
+        assertEquals(1, animationCallback.getSuccessCount());
+        // The callback in enableWindowMagnification will return false
+        assertEquals(1, animationCallback.getFailedCount());
+        verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X, DEFAULT_CENTER_Y);
+    }
+
+    @Test
     public void enableWindowMagnificationWithSameScale_enabled_doNothingButInvokeCallback()
             throws RemoteException {
         enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, null);
@@ -569,6 +690,20 @@
         verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X + 100f, DEFAULT_CENTER_Y + 100f);
     }
 
+    @Test
+    public void moveWindowMagnifierToPosition_enabled() {
+        final float targetCenterX = DEFAULT_CENTER_X + 100;
+        final float targetCenterY = DEFAULT_CENTER_Y + 100;
+        enableWindowMagnificationWithoutAnimation();
+
+        mInstrumentation.runOnMainSync(
+                () -> mController.moveWindowMagnifierToPosition(targetCenterX, targetCenterY,
+                        mAnimationCallback));
+        SystemClock.sleep(mWaitingAnimationPeriod);
+
+        verifyFinalSpec(DEFAULT_SCALE, targetCenterX, targetCenterY);
+    }
+
     private void verifyFinalSpec(float expectedScale, float expectedCenterX,
             float expectedCenterY) {
         assertEquals(expectedScale, mController.getScale(), 0f);
@@ -663,6 +798,13 @@
         }
 
         @Override
+        void moveWindowMagnifierToPosition(float positionX, float positionY,
+                IRemoteMagnificationAnimationCallback callback) {
+            super.moveWindowMagnifierToPosition(positionX, positionY, callback);
+            mSpyController.moveWindowMagnifierToPosition(positionX, positionY, callback);
+        }
+
+        @Override
         void setScale(float scale) {
             super.setScale(scale);
             mSpyController.setScale(scale);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index 6e5926d..a49c4d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -40,6 +40,7 @@
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -88,6 +89,8 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 
 @LargeTest
@@ -96,12 +99,16 @@
 public class WindowMagnificationControllerTest extends SysuiTestCase {
 
     private static final int LAYOUT_CHANGE_TIMEOUT_MS = 5000;
+    private static final long ANIMATION_DURATION_MS = 300;
+    private final long mWaitingAnimationPeriod = 2 * ANIMATION_DURATION_MS;
     @Mock
     private SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
     @Mock
     private MirrorWindowControl mMirrorWindowControl;
     @Mock
     private WindowMagnifierCallback mWindowMagnifierCallback;
+    @Mock
+    IRemoteMagnificationAnimationCallback mAnimationCallback;
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
 
@@ -287,6 +294,82 @@
     }
 
     @Test
+    public void moveWindowMagnifierToPositionWithAnimation_expectedValuesAndInvokeCallback()
+            throws InterruptedException {
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
+                    Float.NaN, 0, 0, null);
+        });
+        final CountDownLatch countDownLatch = new CountDownLatch(1);
+        final MockMagnificationAnimationCallback animationCallback =
+                new MockMagnificationAnimationCallback(countDownLatch);
+        final ArgumentCaptor<Rect> sourceBoundsCaptor = ArgumentCaptor.forClass(Rect.class);
+        verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
+                .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
+        final float targetCenterX = sourceBoundsCaptor.getValue().exactCenterX() + 10;
+        final float targetCenterY = sourceBoundsCaptor.getValue().exactCenterY() + 10;
+
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.moveWindowMagnifierToPosition(
+                    targetCenterX, targetCenterY, animationCallback);
+        });
+
+        assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS));
+        assertEquals(1, animationCallback.getSuccessCount());
+        assertEquals(0, animationCallback.getFailedCount());
+        verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
+                .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
+        assertEquals(mWindowMagnificationController.getCenterX(),
+                sourceBoundsCaptor.getValue().exactCenterX(), 0);
+        assertEquals(mWindowMagnificationController.getCenterY(),
+                sourceBoundsCaptor.getValue().exactCenterY(), 0);
+        assertEquals(mWindowMagnificationController.getCenterX(), targetCenterX, 0);
+        assertEquals(mWindowMagnificationController.getCenterY(), targetCenterY, 0);
+    }
+
+    @Test
+    public void moveWindowMagnifierToPositionMultipleTimes_expectedValuesAndInvokeCallback()
+            throws InterruptedException {
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
+                    Float.NaN, 0, 0, null);
+        });
+        final CountDownLatch countDownLatch = new CountDownLatch(4);
+        final MockMagnificationAnimationCallback animationCallback =
+                new MockMagnificationAnimationCallback(countDownLatch);
+        final ArgumentCaptor<Rect> sourceBoundsCaptor = ArgumentCaptor.forClass(Rect.class);
+        verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
+                .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
+        final float centerX = sourceBoundsCaptor.getValue().exactCenterX();
+        final float centerY = sourceBoundsCaptor.getValue().exactCenterY();
+
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.moveWindowMagnifierToPosition(
+                    centerX + 10, centerY + 10, animationCallback);
+            mWindowMagnificationController.moveWindowMagnifierToPosition(
+                    centerX + 20, centerY + 20, animationCallback);
+            mWindowMagnificationController.moveWindowMagnifierToPosition(
+                    centerX + 30, centerY + 30, animationCallback);
+            mWindowMagnificationController.moveWindowMagnifierToPosition(
+                    centerX + 40, centerY + 40, animationCallback);
+        });
+
+        assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS));
+        // only the last one callback will return true
+        assertEquals(1, animationCallback.getSuccessCount());
+        // the others will return false
+        assertEquals(3, animationCallback.getFailedCount());
+        verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
+                .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
+        assertEquals(mWindowMagnificationController.getCenterX(),
+                sourceBoundsCaptor.getValue().exactCenterX(), 0);
+        assertEquals(mWindowMagnificationController.getCenterY(),
+                sourceBoundsCaptor.getValue().exactCenterY(), 0);
+        assertEquals(mWindowMagnificationController.getCenterX(), centerX + 40, 0);
+        assertEquals(mWindowMagnificationController.getCenterY(), centerY + 40, 0);
+    }
+
+    @Test
     public void setScale_enabled_expectedValueAndUpdateStateDescription() {
         mInstrumentation.runOnMainSync(
                 () -> mWindowMagnificationController.enableWindowMagnificationInternal(2.0f,
@@ -484,6 +567,7 @@
                 mirrorView.performAccessibilityAction(R.id.accessibility_action_move_right, null));
         assertTrue(
                 mirrorView.performAccessibilityAction(R.id.accessibility_action_move_left, null));
+        verify(mWindowMagnifierCallback, times(4)).onMove(eq(displayId));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
index d3f30c50..ccf2f8b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
@@ -148,13 +148,13 @@
     }
 
     @Test
-    public void onDrag_enabled_notifyCallback() throws RemoteException {
+    public void onMove_enabled_notifyCallback() throws RemoteException {
         mCommandQueue.requestWindowMagnificationConnection(true);
         waitForIdleSync();
 
-        mWindowMagnification.onDrag(TEST_DISPLAY);
+        mWindowMagnification.onMove(TEST_DISPLAY);
 
-        verify(mConnectionCallback).onDrag(TEST_DISPLAY);
+        verify(mConnectionCallback).onMove(TEST_DISPLAY);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintViewTest.java
deleted file mode 100644
index 619d48d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintViewTest.java
+++ /dev/null
@@ -1,344 +0,0 @@
-/*
- * Copyright (C) 2021 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.biometrics;
-
-import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
-import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.verify;
-
-import android.content.Context;
-import android.hardware.biometrics.ComponentInfoInternal;
-import android.hardware.biometrics.SensorLocationInternal;
-import android.hardware.biometrics.SensorProperties;
-import android.hardware.fingerprint.FingerprintSensorProperties;
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
-import android.os.Bundle;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.view.View;
-import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-@SmallTest
-public class AuthBiometricFaceToFingerprintViewTest extends SysuiTestCase {
-
-    @Mock AuthBiometricView.Callback mCallback;
-
-    private AuthBiometricFaceToFingerprintView mFaceToFpView;
-
-    @Mock private Button mNegativeButton;
-    @Mock private Button mCancelButton;
-    @Mock private Button mConfirmButton;
-    @Mock private Button mUseCredentialButton;
-    @Mock private Button mTryAgainButton;
-
-    @Mock private TextView mTitleView;
-    @Mock private TextView mSubtitleView;
-    @Mock private TextView mDescriptionView;
-    @Mock private TextView mIndicatorView;
-    @Mock private ImageView mIconView;
-    @Mock private View mIconHolderView;
-    @Mock private AuthBiometricFaceView.IconController mFaceIconController;
-    @Mock private AuthBiometricFaceToFingerprintView.UdfpsIconController mUdfpsIconController;
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-
-        mFaceToFpView = new TestableView(mContext);
-        mFaceToFpView.mFaceIconController = mFaceIconController;
-        mFaceToFpView.mUdfpsIconController = mUdfpsIconController;
-        mFaceToFpView.setCallback(mCallback);
-
-        mFaceToFpView.mNegativeButton = mNegativeButton;
-        mFaceToFpView.mCancelButton = mCancelButton;
-        mFaceToFpView.mUseCredentialButton = mUseCredentialButton;
-        mFaceToFpView.mConfirmButton = mConfirmButton;
-        mFaceToFpView.mTryAgainButton = mTryAgainButton;
-        mFaceToFpView.mIndicatorView = mIndicatorView;
-    }
-
-    @Test
-    public void testStateUpdated_whenDialogAnimatedIn() {
-        mFaceToFpView.onDialogAnimatedIn();
-        verify(mFaceToFpView.mFaceIconController)
-                .updateState(anyInt(), eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING));
-        verify(mFaceToFpView.mUdfpsIconController, never()).updateState(anyInt());
-    }
-
-    @Test
-    public void testIconUpdatesState_whenDialogStateUpdated() {
-        mFaceToFpView.onDialogAnimatedIn();
-        verify(mFaceToFpView.mFaceIconController)
-                .updateState(anyInt(), eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING));
-        verify(mFaceToFpView.mUdfpsIconController, never()).updateState(anyInt());
-
-        mFaceToFpView.updateState(AuthBiometricFaceView.STATE_AUTHENTICATED);
-        verify(mFaceToFpView.mFaceIconController).updateState(
-                eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING),
-                eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATED));
-        verify(mFaceToFpView.mUdfpsIconController, never()).updateState(anyInt());
-
-        assertEquals(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATED, mFaceToFpView.mState);
-    }
-
-    @Test
-    public void testStateUpdated_whenSwitchToFingerprint() {
-        mFaceToFpView.onDialogAnimatedIn();
-        verify(mFaceToFpView.mFaceIconController)
-                .updateState(anyInt(), eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING));
-
-        mFaceToFpView.updateState(AuthBiometricFaceToFingerprintView.STATE_ERROR);
-
-        verify(mFaceToFpView.mFaceIconController).deactivate();
-        verify(mFaceToFpView.mUdfpsIconController).updateState(
-                eq(AuthBiometricFaceToFingerprintView.STATE_IDLE));
-        verify(mConfirmButton).setVisibility(eq(View.GONE));
-
-        mFaceToFpView.updateState(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING);
-
-        verify(mFaceToFpView.mUdfpsIconController).updateState(
-                eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING));
-    }
-
-    @Test
-    public void testStateUpdated_whenSwitchToFingerprint_invokesCallbacks() {
-        class TestModalityListener implements ModalityListener {
-            public int switchCount = 0;
-
-            @Override
-            public void onModalitySwitched(int oldModality, int newModality) {
-                assertEquals(TYPE_FINGERPRINT, newModality);
-                assertEquals(TYPE_FACE, oldModality);
-                switchCount++;
-            }
-        }
-        final TestModalityListener modalityListener = new TestModalityListener();
-
-        mFaceToFpView.onDialogAnimatedIn();
-        mFaceToFpView.setModalityListener(modalityListener);
-
-        assertEquals(0, modalityListener.switchCount);
-
-        mFaceToFpView.updateState(AuthBiometricFaceToFingerprintView.STATE_ERROR);
-        mFaceToFpView.updateState(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING);
-
-        assertEquals(1, modalityListener.switchCount);
-    }
-
-    @Test
-    @Ignore("flaky, b/189031816")
-    public void testModeUpdated_onSoftError_whenSwitchToFingerprint() {
-        mFaceToFpView.onDialogAnimatedIn();
-        mFaceToFpView.onAuthenticationFailed(TYPE_FACE, "no face");
-        waitForIdleSync();
-
-        verify(mIndicatorView).setText(
-                eq(mContext.getString(R.string.fingerprint_dialog_use_fingerprint_instead)));
-        verify(mCallback).onAction(
-                eq(AuthBiometricView.Callback.ACTION_START_DELAYED_FINGERPRINT_SENSOR));
-
-        // First we enter the error state, since we need to show the error animation/text. The
-        // error state is later cleared based on a timer, and we enter STATE_AUTHENTICATING.
-        assertEquals(AuthBiometricFaceToFingerprintView.STATE_ERROR, mFaceToFpView.mState);
-    }
-
-    @Test
-    @Ignore("flaky, b/189031816")
-    public void testModeUpdated_onHardError_whenSwitchToFingerprint() {
-        mFaceToFpView.onDialogAnimatedIn();
-        mFaceToFpView.onError(TYPE_FACE, "oh no!");
-        waitForIdleSync();
-
-        verify(mIndicatorView).setText(
-                eq(mContext.getString(R.string.fingerprint_dialog_use_fingerprint_instead)));
-        verify(mCallback).onAction(
-                eq(AuthBiometricView.Callback.ACTION_START_DELAYED_FINGERPRINT_SENSOR));
-
-        // First we enter the error state, since we need to show the error animation/text. The
-        // error state is later cleared based on a timer, and we enter STATE_AUTHENTICATING.
-        assertEquals(AuthBiometricFaceToFingerprintView.STATE_ERROR, mFaceToFpView.mState);
-    }
-
-    @Test
-    public void testFingerprintOnlyStartsOnFirstError() {
-        mFaceToFpView.onDialogAnimatedIn();
-        verify(mFaceToFpView.mFaceIconController)
-                .updateState(anyInt(), eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING));
-
-        mFaceToFpView.onDialogAnimatedIn();
-        mFaceToFpView.updateState(AuthBiometricFaceToFingerprintView.STATE_ERROR);
-        mFaceToFpView.updateState(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING);
-
-        reset(mCallback);
-
-        mFaceToFpView.onError(TYPE_FACE, "oh no!");
-        mFaceToFpView.onAuthenticationFailed(TYPE_FACE, "no face");
-
-        verify(mCallback, never()).onAction(
-                eq(AuthBiometricView.Callback.ACTION_START_DELAYED_FINGERPRINT_SENSOR));
-    }
-
-    @Test
-    public void testOnSaveState() {
-        final FingerprintSensorPropertiesInternal sensorProps = createFingerprintSensorProps();
-        mFaceToFpView.setFingerprintSensorProps(sensorProps);
-
-        final Bundle savedState = new Bundle();
-        mFaceToFpView.onSaveState(savedState);
-
-        assertEquals(savedState.getInt(AuthDialog.KEY_BIOMETRIC_SENSOR_TYPE),
-                mFaceToFpView.getActiveSensorType());
-        assertEquals(savedState.getParcelable(AuthDialog.KEY_BIOMETRIC_SENSOR_PROPS), sensorProps);
-    }
-
-    @Test
-    public void testRestoreState() {
-        final Bundle savedState = new Bundle();
-        savedState.putInt(AuthDialog.KEY_BIOMETRIC_SENSOR_TYPE, TYPE_FINGERPRINT);
-        savedState.putParcelable(AuthDialog.KEY_BIOMETRIC_SENSOR_PROPS,
-                createFingerprintSensorProps());
-
-        mFaceToFpView.restoreState(savedState);
-
-        assertEquals(mFaceToFpView.getActiveSensorType(), TYPE_FINGERPRINT);
-        assertTrue(mFaceToFpView.isFingerprintUdfps());
-    }
-
-    @NonNull
-    private static FingerprintSensorPropertiesInternal createFingerprintSensorProps() {
-        final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
-        componentInfo.add(new ComponentInfoInternal("componentId", "hardwareVersion",
-                "firmwareVersion", "serialNumber", "softwareVersion"));
-
-        return new FingerprintSensorPropertiesInternal(
-                0 /* sensorId */,
-                SensorProperties.STRENGTH_STRONG,
-                5 /* maxEnrollmentsPerUser */,
-                componentInfo,
-                FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
-                true /* resetLockoutRequiresHardwareAuthToken */,
-                List.of(new SensorLocationInternal("" /* displayId */,
-                        540 /* sensorLocationX */,
-                        1600 /* sensorLocationY */,
-                        100 /* sensorRadius */)));
-    }
-
-    public class TestableView extends AuthBiometricFaceToFingerprintView {
-        public TestableView(Context context) {
-            super(context, null, new MockInjector());
-        }
-
-        @Override
-        protected int getDelayAfterAuthenticatedDurationMs() {
-            return 0;
-        }
-    }
-
-    private class MockInjector extends AuthBiometricView.Injector {
-        @Override
-        public Button getNegativeButton() {
-            return mNegativeButton;
-        }
-
-        @Override
-        public Button getCancelButton() {
-            return mCancelButton;
-        }
-
-        @Override
-        public Button getUseCredentialButton() {
-            return mUseCredentialButton;
-        }
-
-        @Override
-        public Button getConfirmButton() {
-            return mConfirmButton;
-        }
-
-        @Override
-        public Button getTryAgainButton() {
-            return mTryAgainButton;
-        }
-
-        @Override
-        public TextView getTitleView() {
-            return mTitleView;
-        }
-
-        @Override
-        public TextView getSubtitleView() {
-            return mSubtitleView;
-        }
-
-        @Override
-        public TextView getDescriptionView() {
-            return mDescriptionView;
-        }
-
-        @Override
-        public TextView getIndicatorView() {
-            return mIndicatorView;
-        }
-
-        @Override
-        public ImageView getIconView() {
-            return mIconView;
-        }
-
-        @Override
-        public View getIconHolderView() {
-            return mIconHolderView;
-        }
-
-        @Override
-        public int getDelayAfterError() {
-            return 0;
-        }
-
-        @Override
-        public int getMediumToLargeAnimationDurationMs() {
-            return 0;
-        }
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceViewTest.java
deleted file mode 100644
index b93381d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceViewTest.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2019 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.biometrics;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-
-import android.content.Context;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper.RunWithLooper;
-import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import com.android.systemui.R;
-
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
-@SmallTest
-public class AuthBiometricFaceViewTest extends SysuiTestCase {
-
-    @Mock
-    AuthBiometricView.Callback mCallback;
-
-    private TestableFaceView mFaceView;
-
-    @Mock private Button mNegativeButton;
-    @Mock private Button mCancelButton;
-    @Mock private Button mUseCredentialButton;
-
-    @Mock private Button mConfirmButton;
-    @Mock private Button mTryAgainButton;
-
-    @Mock private TextView mErrorView;
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-        mFaceView = new TestableFaceView(mContext);
-        mFaceView.mFaceIconController = mock(TestableFaceView.TestableIconController.class);
-        mFaceView.setCallback(mCallback);
-
-        mFaceView.mNegativeButton = mNegativeButton;
-        mFaceView.mCancelButton = mCancelButton;
-        mFaceView.mUseCredentialButton = mUseCredentialButton;
-
-        mFaceView.mConfirmButton = mConfirmButton;
-        mFaceView.mTryAgainButton = mTryAgainButton;
-
-        mFaceView.mIndicatorView = mErrorView;
-    }
-
-    @Test
-    public void testStateUpdated_whenDialogAnimatedIn() {
-        mFaceView.onDialogAnimatedIn();
-        verify(mFaceView.mFaceIconController)
-                .updateState(anyInt(), eq(AuthBiometricFaceView.STATE_AUTHENTICATING));
-    }
-
-    @Test
-    public void testIconUpdatesState_whenDialogStateUpdated() {
-        mFaceView.updateState(AuthBiometricFaceView.STATE_AUTHENTICATING);
-        verify(mFaceView.mFaceIconController)
-                .updateState(anyInt(), eq(AuthBiometricFaceView.STATE_AUTHENTICATING));
-
-        mFaceView.updateState(AuthBiometricFaceView.STATE_AUTHENTICATED);
-        verify(mFaceView.mFaceIconController).updateState(
-                eq(AuthBiometricFaceView.STATE_AUTHENTICATING),
-                eq(AuthBiometricFaceView.STATE_AUTHENTICATED));
-    }
-
-    public class TestableFaceView extends AuthBiometricFaceView {
-
-        public class TestableIconController extends IconController {
-            TestableIconController(Context context, ImageView iconView) {
-                super(context, iconView, mock(TextView.class));
-            }
-
-            public void startPulsing() {
-                // Stub for testing
-            }
-        }
-
-        @Override
-        protected int getDelayAfterAuthenticatedDurationMs() {
-            return 0; // Keep this at 0 for tests to invoke callback immediately.
-        }
-
-        public TestableFaceView(Context context) {
-            super(context);
-        }
-    }
-
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java
index f8e38e4..9418b50 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java
@@ -20,137 +20,109 @@
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
 import static android.hardware.biometrics.BiometricManager.Authenticators;
 
+import static com.android.systemui.biometrics.AuthBiometricView.Callback.ACTION_AUTHENTICATED;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
-import android.content.Context;
-import android.hardware.biometrics.BiometricPrompt;
 import android.hardware.biometrics.PromptInfo;
 import android.os.Bundle;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
-import android.util.AttributeSet;
+import android.testing.ViewUtils;
+import android.view.LayoutInflater;
 import android.view.View;
-import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.TextView;
 
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 
-import org.junit.Before;
 import org.junit.Ignore;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 
+@Ignore
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
 @SmallTest
 public class AuthBiometricViewTest extends SysuiTestCase {
 
-    @Mock private AuthBiometricView.Callback mCallback;
-    @Mock private AuthPanelController mPanelController;
+    @Rule
+    public final MockitoRule mMockitoRule = MockitoJUnit.rule();
 
-    @Mock private Button mNegativeButton;
-    @Mock private Button mCancelButton;
-    @Mock private Button mUseCredentialButton;
+    @Mock
+    private AuthBiometricView.Callback mCallback;
+    @Mock
+    private AuthPanelController mPanelController;
 
-    @Mock private Button mPositiveButton;
-    @Mock private Button mTryAgainButton;
-
-    @Mock private TextView mTitleView;
-    @Mock private TextView mSubtitleView;
-    @Mock private TextView mDescriptionView;
-    @Mock private TextView mIndicatorView;
-    @Mock private ImageView mIconView;
-    @Mock private View mIconHolderView;
-
-    private TestableBiometricView mBiometricView;
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-    }
+    private AuthBiometricView mBiometricView;
 
     @Test
     public void testOnAuthenticationSucceeded_noConfirmationRequired_sendsActionAuthenticated() {
-        initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector());
+        initDialog(false /* allowDeviceCredential */, mCallback);
 
         // The onAuthenticated runnable is posted when authentication succeeds.
-        mBiometricView.onAuthenticationSucceeded();
+        mBiometricView.onAuthenticationSucceeded(TYPE_FINGERPRINT);
         waitForIdleSync();
         assertEquals(AuthBiometricView.STATE_AUTHENTICATED, mBiometricView.mState);
-        verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_AUTHENTICATED);
+        verify(mCallback).onAction(ACTION_AUTHENTICATED);
     }
 
     @Test
     public void testOnAuthenticationSucceeded_confirmationRequired_updatesDialogContents() {
-        final Button negativeButton = new Button(mContext);
-        final Button cancelButton = new Button(mContext);
-        initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector() {
-            @Override
-            public Button getNegativeButton() {
-                return negativeButton;
-            }
-
-            @Override
-            public Button getCancelButton() {
-                return cancelButton;
-            }
-        });
+        initDialog(false /* allowDeviceCredential */, mCallback);
 
         mBiometricView.setRequireConfirmation(true);
-        mBiometricView.onAuthenticationSucceeded();
+        mBiometricView.onAuthenticationSucceeded(TYPE_FINGERPRINT);
         waitForIdleSync();
-        assertEquals(AuthBiometricView.STATE_PENDING_CONFIRMATION, mBiometricView.mState);
-        verify(mCallback, never()).onAction(anyInt());
 
-        assertEquals(View.GONE, negativeButton.getVisibility());
-        assertEquals(View.VISIBLE, cancelButton.getVisibility());
-        assertTrue(cancelButton.isEnabled());
+        // TODO: this should be tested in the subclasses
+        if (mBiometricView.supportsRequireConfirmation()) {
+            assertEquals(AuthBiometricView.STATE_PENDING_CONFIRMATION, mBiometricView.mState);
 
-        verify(mBiometricView.mConfirmButton).setEnabled(eq(true));
-        verify(mIndicatorView).setText(eq(R.string.biometric_dialog_tap_confirm));
-        verify(mIndicatorView).setVisibility(eq(View.VISIBLE));
+            verify(mCallback, never()).onAction(anyInt());
+
+            assertEquals(View.GONE, mBiometricView.mNegativeButton.getVisibility());
+            assertEquals(View.VISIBLE, mBiometricView.mCancelButton.getVisibility());
+            assertTrue(mBiometricView.mCancelButton.isEnabled());
+
+            assertTrue(mBiometricView.mConfirmButton.isEnabled());
+            assertEquals(mContext.getText(R.string.biometric_dialog_tap_confirm),
+                    mBiometricView.mIndicatorView.getText());
+            assertEquals(View.VISIBLE, mBiometricView.mIndicatorView.getVisibility());
+        } else {
+            assertEquals(AuthBiometricView.STATE_AUTHENTICATED, mBiometricView.mState);
+            verify(mCallback).onAction(eq(ACTION_AUTHENTICATED));
+        }
+
     }
 
     @Test
     public void testPositiveButton_sendsActionAuthenticated() {
-        Button button = new Button(mContext);
-        initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector() {
-           @Override
-            public Button getConfirmButton() {
-               return button;
-           }
-        });
+        initDialog(false /* allowDeviceCredential */, mCallback);
 
-        button.performClick();
+        mBiometricView.mConfirmButton.performClick();
         waitForIdleSync();
 
-        verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_AUTHENTICATED);
+        verify(mCallback).onAction(ACTION_AUTHENTICATED);
         assertEquals(AuthBiometricView.STATE_AUTHENTICATED, mBiometricView.mState);
     }
 
     @Test
     public void testNegativeButton_beforeAuthentication_sendsActionButtonNegative() {
-        Button button = new Button(mContext);
-        initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector() {
-            @Override
-            public Button getNegativeButton() {
-                return button;
-            }
-        });
+        initDialog(false /* allowDeviceCredential */, mCallback);
 
         mBiometricView.onDialogAnimatedIn();
-        button.performClick();
+        mBiometricView.mNegativeButton.performClick();
         waitForIdleSync();
 
         verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_BUTTON_NEGATIVE);
@@ -158,25 +130,14 @@
 
     @Test
     public void testCancelButton_whenPendingConfirmation_sendsActionUserCanceled() {
-        Button cancelButton = new Button(mContext);
-        Button negativeButton = new Button(mContext);
-        initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector() {
-            @Override
-            public Button getNegativeButton() {
-                return negativeButton;
-            }
-            @Override
-            public Button getCancelButton() {
-                return cancelButton;
-            }
-        });
+        initDialog(false /* allowDeviceCredential */, mCallback);
 
         mBiometricView.setRequireConfirmation(true);
-        mBiometricView.onAuthenticationSucceeded();
+        mBiometricView.onAuthenticationSucceeded(TYPE_FINGERPRINT);
 
-        assertEquals(View.GONE, negativeButton.getVisibility());
+        assertEquals(View.GONE, mBiometricView.mNegativeButton.getVisibility());
 
-        cancelButton.performClick();
+        mBiometricView.mCancelButton.performClick();
         waitForIdleSync();
 
         verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_USER_CANCELED);
@@ -184,15 +145,9 @@
 
     @Test
     public void testTryAgainButton_sendsActionTryAgain() {
-        Button button = new Button(mContext);
-        initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector() {
-            @Override
-            public Button getTryAgainButton() {
-                return button;
-            }
-        });
+        initDialog(false /* allowDeviceCredential */, mCallback);
 
-        button.performClick();
+        mBiometricView.mTryAgainButton.performClick();
         waitForIdleSync();
 
         verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN);
@@ -202,7 +157,7 @@
     @Test
     @Ignore("flaky, b/189031816")
     public void testError_sendsActionError() {
-        initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector());
+        initDialog(false /* allowDeviceCredential */, mCallback);
         final String testError = "testError";
         mBiometricView.onError(TYPE_FACE, testError);
         waitForIdleSync();
@@ -213,7 +168,7 @@
 
     @Test
     public void testBackgroundClicked_sendsActionUserCanceled() {
-        initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector());
+        initDialog(false /* allowDeviceCredential */, mCallback);
 
         View view = new View(mContext);
         mBiometricView.setBackgroundView(view);
@@ -223,18 +178,18 @@
 
     @Test
     public void testBackgroundClicked_afterAuthenticated_neverSendsUserCanceled() {
-        initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector());
+        initDialog(false /* allowDeviceCredential */, mCallback);
 
         View view = new View(mContext);
         mBiometricView.setBackgroundView(view);
-        mBiometricView.onAuthenticationSucceeded();
+        mBiometricView.onAuthenticationSucceeded(TYPE_FINGERPRINT);
         view.performClick();
         verify(mCallback, never()).onAction(eq(AuthBiometricView.Callback.ACTION_USER_CANCELED));
     }
 
     @Test
     public void testBackgroundClicked_whenSmallDialog_neverSendsUserCanceled() {
-        initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector());
+        initDialog(false /* allowDeviceCredential */, mCallback);
         mBiometricView.mLayoutParams = new AuthDialog.LayoutParams(0, 0);
         mBiometricView.updateSize(AuthDialog.SIZE_SMALL);
 
@@ -246,7 +201,7 @@
 
     @Test
     public void testIgnoresUselessHelp() {
-        initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector());
+        initDialog(false /* allowDeviceCredential */, mCallback);
 
         mBiometricView.onDialogAnimatedIn();
         waitForIdleSync();
@@ -256,33 +211,16 @@
         mBiometricView.onHelp(TYPE_FINGERPRINT, "");
         waitForIdleSync();
 
-        verify(mIndicatorView, never()).setText(any());
+        assertEquals("", mBiometricView.mIndicatorView.getText());
         verify(mCallback, never()).onAction(eq(AuthBiometricView.Callback.ACTION_ERROR));
         assertEquals(AuthBiometricView.STATE_AUTHENTICATING, mBiometricView.mState);
     }
 
     @Test
     public void testRestoresState() {
-        final boolean requireConfirmation = true; // set/init from AuthController
+        final boolean requireConfirmation = true;
 
-        Button tryAgainButton = new Button(mContext);
-        TextView indicatorView = new TextView(mContext);
-        initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector() {
-            @Override
-            public Button getTryAgainButton() {
-                return tryAgainButton;
-            }
-            @Override
-            public TextView getIndicatorView() {
-                return indicatorView;
-            }
-
-            @Override
-            public int getDelayAfterError() {
-                // keep a real delay to test saving in the error state
-                return BiometricPrompt.HIDE_DIALOG_DELAY;
-            }
-        });
+        initDialog(false /* allowDeviceCredential */, mCallback, null, 10000);
 
         final String failureMessage = "testFailureMessage";
         mBiometricView.setRequireConfirmation(requireConfirmation);
@@ -292,8 +230,8 @@
         Bundle state = new Bundle();
         mBiometricView.onSaveState(state);
 
-        assertEquals(View.VISIBLE, tryAgainButton.getVisibility());
-        assertEquals(View.VISIBLE, state.getInt(AuthDialog.KEY_BIOMETRIC_TRY_AGAIN_VISIBILITY));
+        assertEquals(View.GONE, mBiometricView.mTryAgainButton.getVisibility());
+        assertEquals(View.GONE, state.getInt(AuthDialog.KEY_BIOMETRIC_TRY_AGAIN_VISIBILITY));
 
         assertEquals(AuthBiometricView.STATE_ERROR, mBiometricView.mState);
         assertEquals(AuthBiometricView.STATE_ERROR, state.getInt(AuthDialog.KEY_BIOMETRIC_STATE));
@@ -307,25 +245,12 @@
         // TODO: Test dialog size. Should move requireConfirmation to buildBiometricPromptBundle
 
         // Create new dialog and restore the previous state into it
-        Button tryAgainButton2 = new Button(mContext);
-        TextView indicatorView2 = new TextView(mContext);
-        initDialog(mContext, false /* allowDeviceCredential */, mCallback, state,
-                new MockInjector() {
-                    @Override
-                    public Button getTryAgainButton() {
-                        return tryAgainButton2;
-                    }
-
-                    @Override
-                    public TextView getIndicatorView() {
-                        return indicatorView2;
-                    }
-                });
+        initDialog(false /* allowDeviceCredential */, mCallback, state, 10000);
+        mBiometricView.mAnimationDurationHideDialog = 10000;
         mBiometricView.setRequireConfirmation(requireConfirmation);
         waitForIdleSync();
 
-        // Test restored state
-        assertEquals(View.VISIBLE, tryAgainButton.getVisibility());
+        assertEquals(View.GONE, mBiometricView.mTryAgainButton.getVisibility());
         assertEquals(AuthBiometricView.STATE_ERROR, mBiometricView.mState);
         assertEquals(View.VISIBLE, mBiometricView.mIndicatorView.getVisibility());
 
@@ -334,23 +259,12 @@
     }
 
     @Test
-    public void testCredentialButton_whenDeviceCredentialAllowed() {
-        final Button negativeButton = new Button(mContext);
-        final Button useCredentialButton = new Button(mContext);
-        initDialog(mContext, true /* allowDeviceCredential */, mCallback, new MockInjector() {
-            @Override
-            public Button getNegativeButton() {
-                return negativeButton;
-            }
+    public void testCredentialButton_whenDeviceCredentialAllowed() throws InterruptedException {
+        initDialog(true /* allowDeviceCredential */, mCallback);
 
-            @Override
-            public Button getUseCredentialButton() {
-                return useCredentialButton;
-            }
-        });
-
-        assertEquals(View.GONE, negativeButton.getVisibility());
-        useCredentialButton.performClick();
+        assertEquals(View.VISIBLE, mBiometricView.mUseCredentialButton.getVisibility());
+        assertEquals(View.GONE, mBiometricView.mNegativeButton.getVisibility());
+        mBiometricView.mUseCredentialButton.performClick();
         waitForIdleSync();
 
         verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_USE_DEVICE_CREDENTIAL);
@@ -369,120 +283,30 @@
         return promptInfo;
     }
 
-    private void initDialog(Context context, boolean allowDeviceCredential,
-            AuthBiometricView.Callback callback,
-            Bundle savedState, MockInjector injector) {
-        mBiometricView = new TestableBiometricView(context, null, injector);
+    private void initDialog(boolean allowDeviceCredential, AuthBiometricView.Callback callback) {
+        initDialog(allowDeviceCredential, callback,
+                null /* savedState */, 0 /* hideDelay */);
+    }
+
+    private void initDialog(boolean allowDeviceCredential,
+            AuthBiometricView.Callback callback, Bundle savedState, int hideDelay) {
+        final LayoutInflater inflater = LayoutInflater.from(mContext);
+        mBiometricView = (AuthBiometricView) inflater.inflate(
+                R.layout.auth_biometric_view, null, false);
+        mBiometricView.mAnimationDurationLong = 0;
+        mBiometricView.mAnimationDurationShort = 0;
+        mBiometricView.mAnimationDurationHideDialog = hideDelay;
         mBiometricView.setPromptInfo(buildPromptInfo(allowDeviceCredential));
         mBiometricView.setCallback(callback);
         mBiometricView.restoreState(savedState);
-        mBiometricView.onFinishInflateInternal();
-        mBiometricView.onAttachedToWindowInternal();
-
+        ViewUtils.attachView(mBiometricView);
         mBiometricView.setPanelController(mPanelController);
+        waitForIdleSync();
     }
 
-    private void initDialog(Context context, boolean allowDeviceCredential,
-            AuthBiometricView.Callback callback, MockInjector injector) {
-        initDialog(context, allowDeviceCredential, callback, null /* savedState */, injector);
-    }
-
-    private class MockInjector extends AuthBiometricView.Injector {
-        @Override
-        public Button getNegativeButton() {
-            return mNegativeButton;
-        }
-
-        @Override
-        public Button getCancelButton() {
-            return mCancelButton;
-        }
-
-        @Override
-        public Button getUseCredentialButton() {
-            return mUseCredentialButton;
-        }
-
-        @Override
-        public Button getConfirmButton() {
-            return mPositiveButton;
-        }
-
-        @Override
-        public Button getTryAgainButton() {
-            return mTryAgainButton;
-        }
-
-        @Override
-        public TextView getTitleView() {
-            return mTitleView;
-        }
-
-        @Override
-        public TextView getSubtitleView() {
-            return mSubtitleView;
-        }
-
-        @Override
-        public TextView getDescriptionView() {
-            return mDescriptionView;
-        }
-
-        @Override
-        public TextView getIndicatorView() {
-            return mIndicatorView;
-        }
-
-        @Override
-        public ImageView getIconView() {
-            return mIconView;
-        }
-
-        @Override
-        public View getIconHolderView() {
-            return mIconHolderView;
-        }
-
-        @Override
-        public int getDelayAfterError() {
-            return 0; // Keep this at 0 for tests to invoke callback immediately.
-        }
-
-        @Override
-        public int getMediumToLargeAnimationDurationMs() {
-            return 0;
-        }
-    }
-
-    private class TestableBiometricView extends AuthBiometricView {
-        TestableBiometricView(Context context, AttributeSet attrs,
-                Injector injector) {
-            super(context, attrs, injector);
-        }
-
-        @Override
-        protected int getDelayAfterAuthenticatedDurationMs() {
-            return 0; // Keep this at 0 for tests to invoke callback immediately.
-        }
-
-        @Override
-        protected int getStateForAfterError() {
-            return 0;
-        }
-
-        @Override
-        protected void handleResetAfterError() {
-
-        }
-
-        @Override
-        protected void handleResetAfterHelp() {
-
-        }
-
-        @Override
-        protected boolean supportsSmallDialog() {
-            return false;
-        }
+    @Override
+    protected void waitForIdleSync() {
+        TestableLooper.get(this).processAllMessages();
+        super.waitForIdleSync();
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
deleted file mode 100644
index ae1268d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
+++ /dev/null
@@ -1,329 +0,0 @@
-/*
- * Copyright (C) 2019 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.biometrics;
-
-import static android.hardware.biometrics.BiometricManager.Authenticators;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertTrue;
-
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.hardware.biometrics.BiometricConstants;
-import android.hardware.biometrics.ComponentInfoInternal;
-import android.hardware.biometrics.PromptInfo;
-import android.hardware.biometrics.SensorProperties;
-import android.hardware.face.FaceSensorPropertiesInternal;
-import android.hardware.fingerprint.FingerprintSensorProperties;
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
-import android.os.IBinder;
-import android.os.UserManager;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper.RunWithLooper;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowInsets;
-import android.view.WindowManager;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.ScrollView;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.keyguard.WakefulnessLifecycle;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
-@SmallTest
-public class AuthContainerViewTest extends SysuiTestCase {
-
-    private TestableAuthContainer mAuthContainer;
-
-    private @Mock AuthDialogCallback mCallback;
-    private @Mock UserManager mUserManager;
-    private @Mock WakefulnessLifecycle mWakefulnessLifecycle;
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-    }
-
-    @Test
-    public void testActionAuthenticated_sendsDismissedAuthenticated() {
-        initializeContainer(Authenticators.BIOMETRIC_WEAK);
-
-        mAuthContainer.mBiometricCallback.onAction(
-                AuthBiometricView.Callback.ACTION_AUTHENTICATED);
-        verify(mCallback).onDismissed(
-                eq(AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED),
-                eq(null) /* credentialAttestation */);
-    }
-
-    @Test
-    public void testActionUserCanceled_sendsDismissedUserCanceled() {
-        initializeContainer(Authenticators.BIOMETRIC_WEAK);
-
-        mAuthContainer.mBiometricCallback.onAction(
-                AuthBiometricView.Callback.ACTION_USER_CANCELED);
-        verify(mCallback).onSystemEvent(eq(
-                BiometricConstants.BIOMETRIC_SYSTEM_EVENT_EARLY_USER_CANCEL));
-        verify(mCallback).onDismissed(
-                eq(AuthDialogCallback.DISMISSED_USER_CANCELED),
-                eq(null) /* credentialAttestation */);
-    }
-
-    @Test
-    public void testActionButtonNegative_sendsDismissedButtonNegative() {
-        initializeContainer(Authenticators.BIOMETRIC_WEAK);
-
-        mAuthContainer.mBiometricCallback.onAction(
-                AuthBiometricView.Callback.ACTION_BUTTON_NEGATIVE);
-        verify(mCallback).onDismissed(
-                eq(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE),
-                eq(null) /* credentialAttestation */);
-    }
-
-    @Test
-    public void testActionTryAgain_sendsTryAgain() {
-        initializeContainer(Authenticators.BIOMETRIC_WEAK);
-
-        mAuthContainer.mBiometricCallback.onAction(
-                AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN);
-        verify(mCallback).onTryAgainPressed();
-    }
-
-    @Test
-    public void testActionError_sendsDismissedError() {
-        initializeContainer(Authenticators.BIOMETRIC_WEAK);
-
-        mAuthContainer.mBiometricCallback.onAction(
-                AuthBiometricView.Callback.ACTION_ERROR);
-        verify(mCallback).onDismissed(
-                eq(AuthDialogCallback.DISMISSED_ERROR),
-                eq(null) /* credentialAttestation */);
-    }
-
-    @Test
-    public void testActionUseDeviceCredential_sendsOnDeviceCredentialPressed() {
-        initializeContainer(
-                Authenticators.BIOMETRIC_WEAK | Authenticators.DEVICE_CREDENTIAL);
-
-        mAuthContainer.mBiometricCallback.onAction(
-                AuthBiometricView.Callback.ACTION_USE_DEVICE_CREDENTIAL);
-        verify(mCallback).onDeviceCredentialPressed();
-
-        // Credential view is attached to the frame layout
-        waitForIdleSync();
-        assertNotNull(mAuthContainer.mCredentialView);
-        verify(mAuthContainer.mFrameLayout).addView(eq(mAuthContainer.mCredentialView));
-    }
-
-    @Test
-    public void testAnimateToCredentialUI_invokesStartTransitionToCredentialUI() {
-        initializeContainer(
-                Authenticators.BIOMETRIC_WEAK | Authenticators.DEVICE_CREDENTIAL);
-
-        mAuthContainer.mBiometricView = mock(AuthBiometricView.class);
-        mAuthContainer.animateToCredentialUI();
-        verify(mAuthContainer.mBiometricView).startTransitionToCredentialUI();
-    }
-
-    @Test
-    public void testShowBiometricUI() {
-        initializeContainer(Authenticators.BIOMETRIC_WEAK);
-
-        assertNotEquals(null, mAuthContainer.mBiometricView);
-
-        mAuthContainer.onAttachedToWindowInternal();
-        verify(mAuthContainer.mBiometricScrollView).addView(mAuthContainer.mBiometricView);
-        // Credential view is not added
-        verify(mAuthContainer.mFrameLayout, never()).addView(any());
-    }
-
-    @Test
-    public void testShowCredentialUI_doesNotInflateBiometricUI() {
-        initializeContainer(Authenticators.DEVICE_CREDENTIAL);
-
-        mAuthContainer.onAttachedToWindowInternal();
-
-        assertNull(null, mAuthContainer.mBiometricView);
-        assertNotNull(mAuthContainer.mCredentialView);
-        verify(mAuthContainer.mFrameLayout).addView(mAuthContainer.mCredentialView);
-    }
-
-    @Test
-    public void testCredentialViewUsesEffectiveUserId() {
-        final int dummyEffectiveUserId = 200;
-        when(mUserManager.getCredentialOwnerProfile(anyInt())).thenReturn(dummyEffectiveUserId);
-
-        initializeContainer(Authenticators.DEVICE_CREDENTIAL);
-        mAuthContainer.onAttachedToWindowInternal();
-        assertTrue(mAuthContainer.mCredentialView instanceof AuthCredentialPatternView);
-        assertEquals(dummyEffectiveUserId, mAuthContainer.mCredentialView.mEffectiveUserId);
-        assertEquals(Utils.CREDENTIAL_PATTERN, mAuthContainer.mCredentialView.mCredentialType);
-    }
-
-    @Test
-    public void testCredentialUI_disablesClickingOnBackground() {
-        // In the credential view, clicking on the background (to cancel authentication) is not
-        // valid. Thus, the listener should be null, and it should not be in the accessibility
-        // hierarchy.
-        initializeContainer(Authenticators.DEVICE_CREDENTIAL);
-
-        mAuthContainer.onAttachedToWindowInternal();
-
-        verify(mAuthContainer.mBackgroundView).setOnClickListener(eq(null));
-        verify(mAuthContainer.mBackgroundView).setImportantForAccessibility(
-                eq(View.IMPORTANT_FOR_ACCESSIBILITY_NO));
-    }
-
-    @Test
-    public void testOnDialogAnimatedIn_sendsCancelReason_whenPendingDismiss() {
-        initializeContainer(Authenticators.BIOMETRIC_WEAK);
-        mAuthContainer.mContainerState = AuthContainerView.STATE_PENDING_DISMISS;
-        mAuthContainer.onDialogAnimatedIn();
-        verify(mCallback).onDismissed(
-                eq(AuthDialogCallback.DISMISSED_USER_CANCELED),
-                eq(null) /* credentialAttestation */);
-    }
-
-    @Test
-    public void testLayoutParams_hasSecureWindowFlag() {
-        final IBinder windowToken = mock(IBinder.class);
-        final WindowManager.LayoutParams layoutParams =
-                AuthContainerView.getLayoutParams(windowToken, "");
-        assertTrue((layoutParams.flags & WindowManager.LayoutParams.FLAG_SECURE) != 0);
-    }
-
-    @Test
-    public void testLayoutParams_excludesImeInsets() {
-        final IBinder windowToken = mock(IBinder.class);
-        final WindowManager.LayoutParams layoutParams =
-                AuthContainerView.getLayoutParams(windowToken, "");
-        assertTrue((layoutParams.getFitInsetsTypes() & WindowInsets.Type.ime()) == 0);
-    }
-
-    private void initializeContainer(int authenticators) {
-        AuthContainerView.Config config = new AuthContainerView.Config();
-        config.mContext = mContext;
-        config.mCallback = mCallback;
-        config.mSensorIds = new int[] {0};
-        config.mCredentialAllowed = false;
-
-        PromptInfo promptInfo = new PromptInfo();
-        promptInfo.setAuthenticators(authenticators);
-        config.mPromptInfo = promptInfo;
-
-        final List<FingerprintSensorPropertiesInternal> fpProps = new ArrayList<>();
-
-        final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
-        componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */,
-                "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */,
-                "00000001" /* serialNumber */, "" /* softwareVersion */));
-        componentInfo.add(new ComponentInfoInternal("matchingAlgorithm" /* componentId */,
-                "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
-                "vendor/version/revision" /* softwareVersion */));
-
-        fpProps.add(new FingerprintSensorPropertiesInternal(0,
-                SensorProperties.STRENGTH_STRONG,
-                5 /* maxEnrollmentsPerUser */,
-                componentInfo,
-                FingerprintSensorProperties.TYPE_REAR,
-                false /* resetLockoutRequiresHardwareAuthToken */));
-        mAuthContainer = new TestableAuthContainer(config, fpProps, null /* faceProps */,
-                mWakefulnessLifecycle);
-    }
-
-    private class TestableAuthContainer extends AuthContainerView {
-        TestableAuthContainer(AuthContainerView.Config config,
-                @Nullable List<FingerprintSensorPropertiesInternal> fpProps,
-                @Nullable List<FaceSensorPropertiesInternal> faceProps,
-                WakefulnessLifecycle wakefulnessLifecycle) {
-
-            super(config, new MockInjector(), fpProps, faceProps, wakefulnessLifecycle);
-        }
-
-        @Override
-        public void animateAway(int reason) {
-            // TODO: Credential attestation should be testable/tested
-            mConfig.mCallback.onDismissed(reason, null /* credentialAttestation */);
-        }
-    }
-
-    private final class MockInjector extends AuthContainerView.Injector {
-        @Override
-        public ScrollView getBiometricScrollView(FrameLayout parent) {
-            return mock(ScrollView.class);
-        }
-
-        @Override
-        public FrameLayout inflateContainerView(LayoutInflater factory, ViewGroup root) {
-            return mock(FrameLayout.class);
-        }
-
-        @Override
-        public AuthPanelController getPanelController(Context context, View view) {
-            return mock(AuthPanelController.class);
-        }
-
-        @Override
-        public ImageView getBackgroundView(FrameLayout parent) {
-            return mock(ImageView.class);
-        }
-
-        @Override
-        public View getPanelView(FrameLayout parent) {
-            return mock(View.class);
-        }
-
-        @Override
-        public int getAnimateCredentialStartDelayMs() {
-            return 0;
-        }
-
-        @Override
-        public UserManager getUserManager(Context context) {
-            return mUserManager;
-        }
-
-        @Override
-        public @Utils.CredentialType int getCredentialType(Context context, int effectiveUserId) {
-            return Utils.CREDENTIAL_PATTERN;
-        }
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
new file mode 100644
index 0000000..6f0a8a6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2022 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.biometrics
+
+import android.app.admin.DevicePolicyManager
+import android.hardware.biometrics.BiometricConstants
+import android.hardware.biometrics.BiometricManager
+import android.hardware.biometrics.ComponentInfoInternal
+import android.hardware.biometrics.PromptInfo
+import android.hardware.biometrics.SensorProperties
+import android.hardware.face.FaceSensorPropertiesInternal
+import android.hardware.fingerprint.FingerprintSensorProperties
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
+import android.os.Handler
+import android.os.IBinder
+import android.os.UserManager
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import android.testing.ViewUtils
+import android.view.View
+import android.view.WindowInsets
+import android.view.WindowManager
+import android.widget.ScrollView
+import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.google.common.truth.Truth.assertThat
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+import org.mockito.Mockito.`when` as whenever
+
+@Ignore
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+@SmallTest
+class AuthContainerViewTest : SysuiTestCase() {
+
+    @JvmField @Rule
+    var rule = MockitoJUnit.rule()
+
+    @Mock
+    lateinit var callback: AuthDialogCallback
+    @Mock
+    lateinit var userManager: UserManager
+    @Mock
+    lateinit var lockPatternUtils: LockPatternUtils
+    @Mock
+    lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+    @Mock
+    lateinit var windowToken: IBinder
+
+    private lateinit var authContainer: TestAuthContainerView
+
+    @Test
+    fun testActionAuthenticated_sendsDismissedAuthenticated() {
+        initializeContainer(BiometricManager.Authenticators.BIOMETRIC_WEAK)
+        authContainer.mBiometricCallback.onAction(
+            AuthBiometricView.Callback.ACTION_AUTHENTICATED
+        )
+        waitForIdleSync()
+
+        verify(callback).onDismissed(
+            eq(AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED),
+            eq<ByteArray?>(null) /* credentialAttestation */
+        )
+        assertThat(authContainer.parent).isNull()
+    }
+
+    @Test
+    fun testActionUserCanceled_sendsDismissedUserCanceled() {
+        initializeContainer(BiometricManager.Authenticators.BIOMETRIC_WEAK)
+        authContainer.mBiometricCallback.onAction(
+            AuthBiometricView.Callback.ACTION_USER_CANCELED
+        )
+        waitForIdleSync()
+
+        verify(callback).onSystemEvent(
+            eq(BiometricConstants.BIOMETRIC_SYSTEM_EVENT_EARLY_USER_CANCEL)
+        )
+        verify(callback).onDismissed(
+            eq(AuthDialogCallback.DISMISSED_USER_CANCELED),
+            eq<ByteArray?>(null) /* credentialAttestation */
+        )
+        assertThat(authContainer.parent).isNull()
+    }
+
+    @Test
+    fun testActionButtonNegative_sendsDismissedButtonNegative() {
+        initializeContainer(BiometricManager.Authenticators.BIOMETRIC_WEAK)
+        authContainer.mBiometricCallback.onAction(
+            AuthBiometricView.Callback.ACTION_BUTTON_NEGATIVE
+        )
+        waitForIdleSync()
+
+        verify(callback).onDismissed(
+            eq(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE),
+            eq<ByteArray?>(null) /* credentialAttestation */
+        )
+        assertThat(authContainer.parent).isNull()
+    }
+
+    @Test
+    fun testActionTryAgain_sendsTryAgain() {
+        initializeContainer(BiometricManager.Authenticators.BIOMETRIC_WEAK)
+        authContainer.mBiometricCallback.onAction(
+            AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN
+        )
+        waitForIdleSync()
+
+        verify(callback).onTryAgainPressed()
+    }
+
+    @Test
+    fun testActionError_sendsDismissedError() {
+        initializeContainer(BiometricManager.Authenticators.BIOMETRIC_WEAK)
+        authContainer.mBiometricCallback.onAction(
+            AuthBiometricView.Callback.ACTION_ERROR
+        )
+        waitForIdleSync()
+
+        verify(callback).onDismissed(
+            eq(AuthDialogCallback.DISMISSED_ERROR),
+            eq<ByteArray?>(null) /* credentialAttestation */
+        )
+        assertThat(authContainer.parent).isNull()
+    }
+
+    @Test
+    fun testActionUseDeviceCredential_sendsOnDeviceCredentialPressed() {
+        initializeContainer(
+            BiometricManager.Authenticators.BIOMETRIC_WEAK or
+                BiometricManager.Authenticators.DEVICE_CREDENTIAL
+        )
+        authContainer.mBiometricCallback.onAction(
+            AuthBiometricView.Callback.ACTION_USE_DEVICE_CREDENTIAL
+        )
+        waitForIdleSync()
+
+        verify(callback).onDeviceCredentialPressed()
+        assertThat(authContainer.hasCredentialView()).isTrue()
+    }
+
+    @Test
+    fun testAnimateToCredentialUI_invokesStartTransitionToCredentialUI() {
+        initializeContainer(
+            BiometricManager.Authenticators.BIOMETRIC_WEAK or
+                BiometricManager.Authenticators.DEVICE_CREDENTIAL
+        )
+        authContainer.animateToCredentialUI()
+        waitForIdleSync()
+
+        assertThat(authContainer.hasCredentialView()).isTrue()
+    }
+
+    @Test
+    fun testShowBiometricUI() {
+        initializeContainer(BiometricManager.Authenticators.BIOMETRIC_WEAK)
+
+        waitForIdleSync()
+
+        assertThat(authContainer.hasCredentialView()).isFalse()
+        assertThat(authContainer.hasBiometricPrompt()).isTrue()
+    }
+
+    @Test
+    fun testShowCredentialUI() {
+        initializeContainer(BiometricManager.Authenticators.DEVICE_CREDENTIAL)
+        waitForIdleSync()
+
+        assertThat(authContainer.hasCredentialView()).isTrue()
+        assertThat(authContainer.hasBiometricPrompt()).isFalse()
+    }
+
+    @Test
+    fun testCredentialViewUsesEffectiveUserId() {
+        whenever(userManager.getCredentialOwnerProfile(anyInt())).thenReturn(200)
+        whenever(lockPatternUtils.getKeyguardStoredPasswordQuality(eq(200))).thenReturn(
+            DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
+        )
+
+        initializeContainer(BiometricManager.Authenticators.DEVICE_CREDENTIAL)
+        waitForIdleSync()
+
+        assertThat(authContainer.hasCredentialPatternView()).isTrue()
+        assertThat(authContainer.hasBiometricPrompt()).isFalse()
+    }
+
+    @Test
+    fun testCredentialUI_disablesClickingOnBackground() {
+        whenever(userManager.getCredentialOwnerProfile(anyInt())).thenReturn(20)
+        whenever(lockPatternUtils.getKeyguardStoredPasswordQuality(eq(20))).thenReturn(
+            DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
+        )
+
+        // In the credential view, clicking on the background (to cancel authentication) is not
+        // valid. Thus, the listener should be null, and it should not be in the accessibility
+        // hierarchy.
+        initializeContainer(BiometricManager.Authenticators.DEVICE_CREDENTIAL)
+        waitForIdleSync()
+
+        assertThat(authContainer.hasCredentialPasswordView()).isTrue()
+        assertThat(authContainer.hasBiometricPrompt()).isFalse()
+        assertThat(
+            authContainer.findViewById<View>(R.id.background)?.isImportantForAccessibility
+        ).isFalse()
+
+        authContainer.findViewById<View>(R.id.background)?.performClick()
+        waitForIdleSync()
+
+        assertThat(authContainer.hasCredentialPasswordView()).isTrue()
+        assertThat(authContainer.hasBiometricPrompt()).isFalse()
+    }
+
+    @Test
+    fun testLayoutParams_hasSecureWindowFlag() {
+        val layoutParams = AuthContainerView.getLayoutParams(windowToken, "")
+        assertThat((layoutParams.flags and WindowManager.LayoutParams.FLAG_SECURE) != 0).isTrue()
+    }
+
+    @Test
+    fun testLayoutParams_excludesImeInsets() {
+        val layoutParams = AuthContainerView.getLayoutParams(windowToken, "")
+        assertThat((layoutParams.fitInsetsTypes and WindowInsets.Type.ime()) == 0).isTrue()
+    }
+
+    private fun initializeContainer(authenticators: Int) {
+        val config = AuthContainerView.Config()
+        config.mContext = mContext
+        config.mCallback = callback
+        config.mSensorIds = intArrayOf(0)
+        config.mSkipAnimation = true
+        config.mPromptInfo = PromptInfo()
+        config.mPromptInfo.authenticators = authenticators
+        val componentInfo = listOf(
+            ComponentInfoInternal(
+                "faceSensor" /* componentId */,
+                "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */,
+                "00000001" /* serialNumber */, "" /* softwareVersion */
+            ),
+            ComponentInfoInternal(
+                "matchingAlgorithm" /* componentId */,
+                "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
+                "vendor/version/revision" /* softwareVersion */
+            )
+        )
+        val fpProps = listOf(
+            FingerprintSensorPropertiesInternal(
+                0,
+                SensorProperties.STRENGTH_STRONG,
+                5 /* maxEnrollmentsPerUser */,
+                componentInfo,
+                FingerprintSensorProperties.TYPE_REAR,
+                false /* resetLockoutRequiresHardwareAuthToken */
+            )
+        )
+        authContainer = TestAuthContainerView(
+            config,
+            fpProps,
+            listOf(),
+            wakefulnessLifecycle,
+            userManager,
+            lockPatternUtils,
+            Handler(TestableLooper.get(this).looper)
+        )
+        ViewUtils.attachView(authContainer)
+    }
+
+    private inner class TestAuthContainerView(
+        config: Config,
+        fpProps: List<FingerprintSensorPropertiesInternal>,
+        faceProps: List<FaceSensorPropertiesInternal>,
+        wakefulnessLifecycle: WakefulnessLifecycle,
+        userManager: UserManager,
+        lockPatternUtils: LockPatternUtils,
+        mainHandler: Handler
+    ) : AuthContainerView(
+        config, fpProps, faceProps,
+        wakefulnessLifecycle, userManager, lockPatternUtils, mainHandler
+    ) {
+        override fun postOnAnimation(runnable: Runnable) {
+            runnable.run()
+        }
+    }
+
+    override fun waitForIdleSync() {
+        TestableLooper.get(this).processAllMessages()
+        super.waitForIdleSync()
+    }
+}
+
+private fun AuthContainerView.hasBiometricPrompt() =
+    (findViewById<ScrollView>(R.id.biometric_scrollview)?.childCount ?: 0) > 0
+
+private fun AuthContainerView.hasCredentialView() =
+    hasCredentialPatternView() || hasCredentialPasswordView()
+
+private fun AuthContainerView.hasCredentialPatternView() =
+    findViewById<View>(R.id.lockPattern) != null
+
+private fun AuthContainerView.hasCredentialPasswordView() =
+    findViewById<View>(R.id.lockPassword) != null
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index c37e966..cfac9651 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -16,8 +16,9 @@
 
 package com.android.systemui.biometrics;
 
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
 import static android.hardware.biometrics.BiometricManager.Authenticators;
-import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT;
+import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE;
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertNull;
@@ -62,6 +63,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.RemoteException;
+import android.os.UserManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableContext;
 import android.testing.TestableLooper;
@@ -71,6 +73,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.R;
+import com.android.internal.widget.LockPatternUtils;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -79,6 +82,8 @@
 import com.android.systemui.util.concurrency.FakeExecution;
 
 import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.AdditionalMatchers;
@@ -86,7 +91,8 @@
 import org.mockito.Captor;
 import org.mockito.InOrder;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -94,11 +100,15 @@
 
 import javax.inject.Provider;
 
+@Ignore
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
 @SmallTest
 public class AuthControllerTest extends SysuiTestCase {
 
+    @Rule
+    public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
     @Mock
     private PackageManager mPackageManager;
     @Mock
@@ -128,6 +138,10 @@
     @Mock
     private WakefulnessLifecycle mWakefulnessLifecycle;
     @Mock
+    private UserManager mUserManager;
+    @Mock
+    private LockPatternUtils mLockPatternUtils;
+    @Mock
     private StatusBarStateController mStatusBarStateController;
     @Captor
     ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> mAuthenticatorsRegisteredCaptor;
@@ -144,8 +158,6 @@
 
     @Before
     public void setup() throws RemoteException {
-        MockitoAnnotations.initMocks(this);
-
         mContextSpy = spy(mContext);
         mExecution = new FakeExecution();
         mTestableLooper = TestableLooper.get(this);
@@ -343,8 +355,8 @@
     @Test
     public void testOnAuthenticationSucceededInvoked_whenSystemRequested() {
         showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
-        mAuthController.onBiometricAuthenticated();
-        verify(mDialog1).onAuthenticationSucceeded();
+        mAuthController.onBiometricAuthenticated(TYPE_FINGERPRINT);
+        verify(mDialog1).onAuthenticationSucceeded(eq(TYPE_FINGERPRINT));
     }
 
     @Test
@@ -528,8 +540,7 @@
         doAnswer(invocation -> {
             Object[] args = invocation.getArguments();
             Bundle savedState = (Bundle) args[0];
-            savedState.putInt(
-                    AuthDialog.KEY_CONTAINER_STATE, AuthContainerView.STATE_SHOWING);
+            savedState.putBoolean(AuthDialog.KEY_CONTAINER_GOING_AWAY, false);
             return null; // onSaveState returns void
         }).when(mDialog1).onSaveState(any());
 
@@ -558,8 +569,7 @@
         doAnswer(invocation -> {
             Object[] args = invocation.getArguments();
             Bundle savedState = (Bundle) args[0];
-            savedState.putInt(
-                    AuthDialog.KEY_CONTAINER_STATE, AuthContainerView.STATE_SHOWING);
+            savedState.putBoolean(AuthDialog.KEY_CONTAINER_GOING_AWAY, false);
             savedState.putBoolean(AuthDialog.KEY_CREDENTIAL_SHOWING, true);
             return null; // onSaveState returns void
         }).when(mDialog1).onSaveState(any());
@@ -697,7 +707,7 @@
                 0 /* operationId */,
                 "testPackage",
                 1 /* requestId */,
-                BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT);
+                BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE);
     }
 
     private PromptInfo createTestPromptInfo() {
@@ -739,15 +749,16 @@
             super(context, execution, commandQueue, activityTaskManager, windowManager,
                     fingerprintManager, faceManager, udfpsControllerFactory,
                     sidefpsControllerFactory, mDisplayManager, mWakefulnessLifecycle,
-                    statusBarStateController, mHandler);
+                    mUserManager, mLockPatternUtils, statusBarStateController, mHandler);
         }
 
         @Override
         protected AuthDialog buildDialog(PromptInfo promptInfo,
-                boolean requireConfirmation, int userId, int[] sensorIds, boolean credentialAllowed,
+                boolean requireConfirmation, int userId, int[] sensorIds,
                 String opPackageName, boolean skipIntro, long operationId, long requestId,
                 @BiometricManager.BiometricMultiSensorMode int multiSensorConfig,
-                WakefulnessLifecycle wakefulnessLifecycle) {
+                WakefulnessLifecycle wakefulnessLifecycle, UserManager userManager,
+                LockPatternUtils lockPatternUtils) {
 
             mLastBiometricPromptInfo = promptInfo;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
index 254fc59..839c0ab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
@@ -17,10 +17,10 @@
 package com.android.systemui.biometrics
 
 import android.animation.Animator
-import android.graphics.Insets
 import android.app.ActivityManager
 import android.app.ActivityTaskManager
 import android.content.ComponentName
+import android.graphics.Insets
 import android.graphics.Rect
 import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
 import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
@@ -65,8 +65,8 @@
 import org.mockito.Mockito.`when`
 import org.mockito.Mockito.any
 import org.mockito.Mockito.anyFloat
-import org.mockito.Mockito.anyLong
 import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyLong
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.reset
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index 066a866..ef82c3e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -57,9 +57,9 @@
 import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mock
 import org.mockito.Mockito.mock
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.Mockito.verify
 import org.mockito.junit.MockitoJUnit
+import org.mockito.Mockito.`when` as whenever
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
index 2cd470e..3d8d128 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
@@ -38,11 +38,11 @@
 import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.nullable
 import org.mockito.Mockito.never
-import org.mockito.Mockito.`when` as whenever
+import org.mockito.Mockito.nullable
 import org.mockito.Mockito.verify
 import org.mockito.junit.MockitoJUnit
+import org.mockito.Mockito.`when` as whenever
 
 private const val DISPLAY_ID = "" // default display id
 private const val SENSOR_X = 50
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt
index 12096bc..af3f24a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt
@@ -210,4 +210,25 @@
                 eq(actionCallbackService))
         assertEquals(action, wrapperCaptor.getValue().getWrappedAction())
     }
+
+    @Test
+    fun testFalseBindCallsUnbind() {
+        val falseContext = mock(Context::class.java)
+        `when`(falseContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(false)
+        val manager = ControlsProviderLifecycleManager(
+            falseContext,
+            executor,
+            actionCallbackService,
+            UserHandle.of(0),
+            componentName
+        )
+        manager.bindService()
+        executor.runAllReady()
+
+        val captor = ArgumentCaptor.forClass(
+            ServiceConnection::class.java
+        )
+        verify(falseContext).bindServiceAsUser(any(), captor.capture(), anyInt(), any())
+        verify(falseContext).unbindService(captor.value)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index 2860b50..b3d5459 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -28,6 +28,7 @@
 import android.service.dreams.IDreamOverlay;
 import android.service.dreams.IDreamOverlayCallback;
 import android.testing.AndroidTestingRunner;
+import android.view.ViewGroup;
 import android.view.WindowManager;
 import android.view.WindowManagerImpl;
 
@@ -99,6 +100,9 @@
     @Mock
     DreamPreviewComplication mPreviewComplication;
 
+    @Mock
+    ViewGroup mDreamOverlayContainerViewParent;
+
     DreamOverlayService mService;
 
     @Before
@@ -152,6 +156,23 @@
     }
 
     @Test
+    public void testDreamOverlayContainerViewRemovedFromOldParentWhenInitialized()
+            throws Exception {
+        when(mDreamOverlayContainerView.getParent())
+                .thenReturn(mDreamOverlayContainerViewParent)
+                .thenReturn(null);
+
+        final IBinder proxy = mService.onBind(new Intent());
+        final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+
+        // Inform the overlay service of dream starting.
+        overlay.startDream(mWindowParams, mDreamOverlayCallback);
+        mMainExecutor.runAllReady();
+
+        verify(mDreamOverlayContainerViewParent).removeView(mDreamOverlayContainerView);
+    }
+
+    @Test
     public void testShouldShowComplicationsFalseByDefault() {
         mService.onBind(new Intent());
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
index 809b890..adb59ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
@@ -55,6 +55,8 @@
 
     private lateinit var appIconDrawable: Drawable
     @Mock
+    private lateinit var logger: MediaTttLogger
+    @Mock
     private lateinit var windowManager: WindowManager
     @Mock
     private lateinit var viewUtil: ViewUtil
@@ -69,7 +71,7 @@
         fakeExecutor = FakeExecutor(fakeClock)
 
         controllerCommon = TestControllerCommon(
-            context, windowManager, viewUtil, fakeExecutor, tapGestureDetector
+            context, logger, windowManager, viewUtil, fakeExecutor, tapGestureDetector
         )
     }
 
@@ -94,20 +96,22 @@
 
     @Test
     fun displayChip_chipDoesNotDisappearsBeforeTimeout() {
-        controllerCommon.displayChip(getState())
+        val state = getState()
+        controllerCommon.displayChip(state)
         reset(windowManager)
 
-        fakeClock.advanceTime(TIMEOUT_MILLIS - 1)
+        fakeClock.advanceTime(state.getTimeoutMs() - 1)
 
         verify(windowManager, never()).removeView(any())
     }
 
     @Test
     fun displayChip_chipDisappearsAfterTimeout() {
-        controllerCommon.displayChip(getState())
+        val state = getState()
+        controllerCommon.displayChip(state)
         reset(windowManager)
 
-        fakeClock.advanceTime(TIMEOUT_MILLIS + 1)
+        fakeClock.advanceTime(state.getTimeoutMs() + 1)
 
         verify(windowManager).removeView(any())
     }
@@ -115,7 +119,8 @@
     @Test
     fun displayChip_calledAgainBeforeTimeout_timeoutReset() {
         // First, display the chip
-        controllerCommon.displayChip(getState())
+        val state = getState()
+        controllerCommon.displayChip(state)
 
         // After some time, re-display the chip
         val waitTime = 1000L
@@ -123,7 +128,7 @@
         controllerCommon.displayChip(getState())
 
         // Wait until the timeout for the first display would've happened
-        fakeClock.advanceTime(TIMEOUT_MILLIS - waitTime + 1)
+        fakeClock.advanceTime(state.getTimeoutMs() - waitTime + 1)
 
         // Verify we didn't hide the chip
         verify(windowManager, never()).removeView(any())
@@ -132,33 +137,36 @@
     @Test
     fun displayChip_calledAgainBeforeTimeout_eventuallyTimesOut() {
         // First, display the chip
-        controllerCommon.displayChip(getState())
+        val state = getState()
+        controllerCommon.displayChip(state)
 
         // After some time, re-display the chip
         fakeClock.advanceTime(1000L)
         controllerCommon.displayChip(getState())
 
         // Ensure we still hide the chip eventually
-        fakeClock.advanceTime(TIMEOUT_MILLIS + 1)
+        fakeClock.advanceTime(state.getTimeoutMs() + 1)
 
         verify(windowManager).removeView(any())
     }
 
     @Test
-    fun removeChip_chipRemovedAndGestureDetectionStopped() {
+    fun removeChip_chipRemovedAndGestureDetectionStoppedAndRemovalLogged() {
         // First, add the chip
         controllerCommon.displayChip(getState())
 
         // Then, remove it
-        controllerCommon.removeChip()
+        val reason = "test reason"
+        controllerCommon.removeChip(reason)
 
         verify(windowManager).removeView(any())
         verify(tapGestureDetector).removeOnGestureDetectedCallback(any())
+        verify(logger).logChipRemoval(reason)
     }
 
     @Test
     fun removeChip_noAdd_viewNotRemoved() {
-        controllerCommon.removeChip()
+        controllerCommon.removeChip("reason")
 
         verify(windowManager, never()).removeView(any())
     }
@@ -222,12 +230,19 @@
 
     inner class TestControllerCommon(
         context: Context,
+        logger: MediaTttLogger,
         windowManager: WindowManager,
         viewUtil: ViewUtil,
         @Main mainExecutor: DelayableExecutor,
         tapGestureDetector: TapGestureDetector,
     ) : MediaTttChipControllerCommon<MediaTttChipState>(
-        context, windowManager, viewUtil, mainExecutor, tapGestureDetector, R.layout.media_ttt_chip
+        context,
+        logger,
+        windowManager,
+        viewUtil,
+        mainExecutor,
+        tapGestureDetector,
+        R.layout.media_ttt_chip
     ) {
         override fun updateChipView(chipState: MediaTttChipState, currentChipView: ViewGroup) {
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt
new file mode 100644
index 0000000..d95e5c4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2022 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.media.taptotransfer.common
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.log.LogcatEchoTracker
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mockito.mock
+import java.io.PrintWriter
+import java.io.StringWriter
+
+@SmallTest
+class MediaTttLoggerTest : SysuiTestCase() {
+
+    private lateinit var buffer: LogBuffer
+    private lateinit var logger: MediaTttLogger
+
+    @Before
+    fun setUp () {
+        buffer = LogBufferFactory(DumpManager(), mock(LogcatEchoTracker::class.java))
+            .create("buffer", 10)
+        logger = MediaTttLogger(DEVICE_TYPE_TAG, buffer)
+    }
+
+    @Test
+    fun logStateChange_bufferHasDeviceTypeTagAndStateNameAndId() {
+        val stateName = "test state name"
+        val id = "test id"
+
+        logger.logStateChange(stateName, id)
+
+        val stringWriter = StringWriter()
+        buffer.dump(PrintWriter(stringWriter), tailLength = 0)
+        val actualString = stringWriter.toString()
+
+        assertThat(actualString).contains(DEVICE_TYPE_TAG)
+        assertThat(actualString).contains(stateName)
+        assertThat(actualString).contains(id)
+    }
+
+    @Test
+    fun logChipRemoval_bufferHasDeviceTypeAndReason() {
+        val reason = "test reason"
+        logger.logChipRemoval(reason)
+
+        val stringWriter = StringWriter()
+        buffer.dump(PrintWriter(stringWriter), tailLength = 0)
+        val actualString = stringWriter.toString()
+
+        assertThat(actualString).contains(DEVICE_TYPE_TAG)
+        assertThat(actualString).contains(reason)
+    }
+}
+
+private const val DEVICE_TYPE_TAG = "TEST TYPE"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
index 86d4c1b..56f4589 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
@@ -31,6 +31,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.media.taptotransfer.common.MediaTttLogger
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.gesture.TapGestureDetector
 import com.android.systemui.util.concurrency.FakeExecutor
@@ -60,6 +61,8 @@
     @Mock
     private lateinit var applicationInfo: ApplicationInfo
     @Mock
+    private lateinit var logger: MediaTttLogger
+    @Mock
     private lateinit var windowManager: WindowManager
     @Mock
     private lateinit var viewUtil: ViewUtil
@@ -83,6 +86,7 @@
         controllerReceiver = MediaTttChipControllerReceiver(
             commandQueue,
             context,
+            logger,
             windowManager,
             viewUtil,
             FakeExecutor(FakeSystemClock()),
@@ -142,6 +146,18 @@
     }
 
     @Test
+    fun receivesNewStateFromCommandQueue_isLogged() {
+        commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
+            StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER,
+            routeInfo,
+            null,
+            null
+        )
+
+        verify(logger).logStateChange(any(), any())
+    }
+
+    @Test
     fun displayChip_nullAppIconDrawable_iconIsFromPackageName() {
         val state = ChipStateReceiver(PACKAGE_NAME, appIconDrawable = null, "appName")
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
index 88888f0..fd1d76a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
@@ -32,6 +32,7 @@
 import com.android.internal.statusbar.IUndoMediaTransferCallback
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.media.taptotransfer.common.MediaTttLogger
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.gesture.TapGestureDetector
 import com.android.systemui.util.concurrency.FakeExecutor
@@ -62,6 +63,8 @@
     @Mock
     private lateinit var applicationInfo: ApplicationInfo
     @Mock
+    private lateinit var logger: MediaTttLogger
+    @Mock
     private lateinit var windowManager: WindowManager
     @Mock
     private lateinit var viewUtil: ViewUtil
@@ -69,6 +72,8 @@
     private lateinit var commandQueue: CommandQueue
     private lateinit var commandQueueCallback: CommandQueue.Callbacks
     private lateinit var fakeAppIconDrawable: Drawable
+    private lateinit var fakeClock: FakeSystemClock
+    private lateinit var fakeExecutor: FakeExecutor
 
     @Before
     fun setUp() {
@@ -82,12 +87,16 @@
         )).thenReturn(applicationInfo)
         context.setMockPackageManager(packageManager)
 
+        fakeClock = FakeSystemClock()
+        fakeExecutor = FakeExecutor(fakeClock)
+
         controllerSender = MediaTttChipControllerSender(
             commandQueue,
             context,
+            logger,
             windowManager,
             viewUtil,
-            FakeExecutor(FakeSystemClock()),
+            fakeExecutor,
             TapGestureDetector(context)
         )
 
@@ -223,6 +232,17 @@
     }
 
     @Test
+    fun receivesNewStateFromCommandQueue_isLogged() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
+            routeInfo,
+            null
+        )
+
+        verify(logger).logStateChange(any(), any())
+    }
+
+    @Test
     fun almostCloseToStartCast_appIcon_deviceName_noLoadingIcon_noUndo_noFailureIcon() {
         val state = almostCloseToStartCast()
         controllerSender.displayChip(state)
@@ -460,6 +480,52 @@
         assertThat(getChipView().getFailureIcon().visibility).isEqualTo(View.VISIBLE)
     }
 
+    @Test
+    fun transferToReceiverTriggeredThenRemoveChip_chipStillDisplayed() {
+        controllerSender.displayChip(transferToReceiverTriggered())
+        fakeClock.advanceTime(1000L)
+
+        controllerSender.removeChip("fakeRemovalReason")
+        fakeExecutor.runAllReady()
+
+        verify(windowManager, never()).removeView(any())
+    }
+
+    @Test
+    fun transferToReceiverTriggeredThenFarFromReceiver_eventuallyTimesOut() {
+        val state = transferToReceiverTriggered()
+        controllerSender.displayChip(state)
+        fakeClock.advanceTime(1000L)
+        controllerSender.removeChip("fakeRemovalReason")
+
+        fakeClock.advanceTime(state.getTimeoutMs() + 1)
+
+        verify(windowManager).removeView(any())
+    }
+
+    @Test
+    fun transferToThisDeviceTriggeredThenRemoveChip_chipStillDisplayed() {
+        controllerSender.displayChip(transferToThisDeviceTriggered())
+        fakeClock.advanceTime(1000L)
+
+        controllerSender.removeChip("fakeRemovalReason")
+        fakeExecutor.runAllReady()
+
+        verify(windowManager, never()).removeView(any())
+    }
+
+    @Test
+    fun transferToThisDeviceTriggeredThenFarFromReceiver_eventuallyTimesOut() {
+        val state = transferToThisDeviceTriggered()
+        controllerSender.displayChip(state)
+        fakeClock.advanceTime(1000L)
+        controllerSender.removeChip("fakeRemovalReason")
+
+        fakeClock.advanceTime(state.getTimeoutMs() + 1)
+
+        verify(windowManager).removeView(any())
+    }
+
     private fun LinearLayout.getAppIconView() = this.requireViewById<ImageView>(R.id.app_icon)
 
     private fun LinearLayout.getChipText(): String =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
index b559d18..04b50d8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
@@ -23,6 +23,7 @@
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -34,6 +35,7 @@
 import android.content.ContextWrapper;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.ServiceConnection;
 import android.content.pm.PackageInfo;
 import android.content.pm.ServiceInfo;
 import android.net.Uri;
@@ -56,7 +58,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.Mockito;
+import org.mockito.ArgumentCaptor;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -64,10 +66,10 @@
     private static final int TEST_FAIL_TIMEOUT = 5000;
 
     private final PackageManagerAdapter mMockPackageManagerAdapter =
-            Mockito.mock(PackageManagerAdapter.class);
+            mock(PackageManagerAdapter.class);
     private final BroadcastDispatcher mMockBroadcastDispatcher =
-            Mockito.mock(BroadcastDispatcher.class);
-    private final IQSTileService.Stub mMockTileService = Mockito.mock(IQSTileService.Stub.class);
+            mock(BroadcastDispatcher.class);
+    private final IQSTileService.Stub mMockTileService = mock(IQSTileService.Stub.class);
     private ComponentName mTileServiceComponentName;
     private Intent mTileServiceIntent;
     private UserHandle mUser;
@@ -95,7 +97,7 @@
         mThread.start();
         mHandler = Handler.createAsync(mThread.getLooper());
         mStateManager = new TileLifecycleManager(mHandler, mWrappedContext,
-                Mockito.mock(IQSService.class),
+                mock(IQSService.class),
                 mMockPackageManagerAdapter,
                 mMockBroadcastDispatcher,
                 mTileServiceIntent,
@@ -269,6 +271,25 @@
         assertTrue(mStateManager.isToggleableTile());
     }
 
+    @Test
+    public void testFalseBindCallsUnbind() {
+        Context falseContext = mock(Context.class);
+        when(falseContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(false);
+        TileLifecycleManager manager = new TileLifecycleManager(mHandler, falseContext,
+                mock(IQSService.class),
+                mMockPackageManagerAdapter,
+                mMockBroadcastDispatcher,
+                mTileServiceIntent,
+                mUser);
+
+        manager.setBindService(true);
+
+        ArgumentCaptor<ServiceConnection> captor = ArgumentCaptor.forClass(ServiceConnection.class);
+        verify(falseContext).bindServiceAsUser(any(), captor.capture(), anyInt(), any());
+
+        verify(falseContext).unbindService(captor.getValue());
+    }
+
     private static class TestContextWrapper extends ContextWrapper {
         private IntentFilter mLastIntentFilter;
         private int mLastFlag;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index 6c29ecc..11f76a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -448,9 +448,10 @@
 
     @Test
     public void testOnBiometricAuthenticated() {
-        mCommandQueue.onBiometricAuthenticated();
+        final int id = 12;
+        mCommandQueue.onBiometricAuthenticated(id);
         waitForIdleSync();
-        verify(mCallbacks).onBiometricAuthenticated();
+        verify(mCallbacks).onBiometricAuthenticated(eq(id));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index 466d954..3c1a73e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -45,7 +45,6 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -66,15 +65,17 @@
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.UserManager;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 import android.view.ViewGroup;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.settingslib.fuelgauge.BatteryStatus;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
@@ -106,7 +107,8 @@
 import java.util.Collections;
 
 @SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class KeyguardIndicationControllerTest extends SysuiTestCase {
 
     private static final String ORGANIZATION_NAME = "organization";
@@ -164,11 +166,15 @@
     @Captor
     private ArgumentCaptor<KeyguardIndication> mKeyguardIndicationCaptor;
     @Captor
+    private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallbackCaptor;
+    @Captor
     private ArgumentCaptor<KeyguardStateController.Callback> mKeyguardStateControllerCallbackCaptor;
     private KeyguardStateController.Callback mKeyguardStateControllerCallback;
+    private KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback;
     private StatusBarStateController.StateListener mStatusBarStateListener;
     private BroadcastReceiver mBroadcastReceiver;
     private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
+    private TestableLooper mTestableLooper;
 
     private KeyguardIndicationTextView mTextView; // AOD text
 
@@ -181,6 +187,7 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mTestableLooper = TestableLooper.get(this);
         mTextView = new KeyguardIndicationTextView(mContext);
         mTextView.setAnimationsEnabled(false);
 
@@ -226,7 +233,10 @@
             Looper.prepare();
         }
 
-        mController = new KeyguardIndicationController(mContext, mWakeLockBuilder,
+        mController = new KeyguardIndicationController(
+                mContext,
+                mTestableLooper.getLooper(),
+                mWakeLockBuilder,
                 mKeyguardStateController, mStatusBarStateController, mKeyguardUpdateMonitor,
                 mDockManager, mBroadcastDispatcher, mDevicePolicyManager, mIBatteryStats,
                 mUserManager, mExecutor, mExecutor,  mFalsingManager, mLockPatternUtils,
@@ -245,6 +255,10 @@
                 mKeyguardStateControllerCallbackCaptor.capture());
         mKeyguardStateControllerCallback = mKeyguardStateControllerCallbackCaptor.getValue();
 
+        verify(mKeyguardUpdateMonitor).registerCallback(
+                mKeyguardUpdateMonitorCallbackCaptor.capture());
+        mKeyguardUpdateMonitorCallback = mKeyguardUpdateMonitorCallbackCaptor.getValue();
+
         mExecutor.runAllReady();
         reset(mRotateTextViewController);
     }
@@ -267,7 +281,7 @@
             mAlignmentListener.getValue().onAlignmentStateChanged(DockManager.ALIGN_STATE_POOR);
         });
         mInstrumentation.waitForIdleSync();
-
+        mTestableLooper.processAllMessages();
 
         verifyIndicationMessage(INDICATION_TYPE_ALIGNMENT,
                 mContext.getResources().getString(R.string.dock_alignment_slow_charging));
@@ -285,6 +299,7 @@
             mAlignmentListener.getValue().onAlignmentStateChanged(DockManager.ALIGN_STATE_TERRIBLE);
         });
         mInstrumentation.waitForIdleSync();
+        mTestableLooper.processAllMessages();
 
         verifyIndicationMessage(INDICATION_TYPE_ALIGNMENT,
                 mContext.getResources().getString(R.string.dock_alignment_not_charging));
@@ -303,6 +318,7 @@
             mAlignmentListener.getValue().onAlignmentStateChanged(DockManager.ALIGN_STATE_POOR);
         });
         mInstrumentation.waitForIdleSync();
+        mTestableLooper.processAllMessages();
 
         assertThat(mTextView.getText()).isEqualTo(
                 mContext.getResources().getString(R.string.dock_alignment_slow_charging));
@@ -321,6 +337,7 @@
             mAlignmentListener.getValue().onAlignmentStateChanged(DockManager.ALIGN_STATE_TERRIBLE);
         });
         mInstrumentation.waitForIdleSync();
+        mTestableLooper.processAllMessages();
 
         assertThat(mTextView.getText()).isEqualTo(
                 mContext.getResources().getString(R.string.dock_alignment_not_charging));
@@ -331,9 +348,12 @@
     @Test
     public void disclosure_unmanaged() {
         createController();
+        mController.setVisible(true);
         when(mKeyguardStateController.isShowing()).thenReturn(true);
         when(mDevicePolicyManager.isDeviceManaged()).thenReturn(false);
         when(mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()).thenReturn(false);
+        reset(mRotateTextViewController);
+
         sendUpdateDisclosureBroadcast();
         mExecutor.runAllReady();
 
@@ -347,6 +367,7 @@
         when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true);
         when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(null);
         sendUpdateDisclosureBroadcast();
+        mController.setVisible(true);
         mExecutor.runAllReady();
 
         verifyIndicationMessage(INDICATION_TYPE_DISCLOSURE, mDisclosureGeneric);
@@ -355,6 +376,7 @@
     @Test
     public void disclosure_orgOwnedDeviceWithManagedProfile_noOrganizationName() {
         createController();
+        mController.setVisible(true);
         when(mKeyguardStateController.isShowing()).thenReturn(true);
         when(mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()).thenReturn(true);
         when(mUserManager.getProfiles(anyInt())).thenReturn(Collections.singletonList(
@@ -369,6 +391,7 @@
     @Test
     public void disclosure_deviceOwner_withOrganizationName() {
         createController();
+        mController.setVisible(true);
         when(mKeyguardStateController.isShowing()).thenReturn(true);
         when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true);
         when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(ORGANIZATION_NAME);
@@ -381,6 +404,7 @@
     @Test
     public void disclosure_orgOwnedDeviceWithManagedProfile_withOrganizationName() {
         createController();
+        mController.setVisible(true);
         when(mKeyguardStateController.isShowing()).thenReturn(true);
         when(mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()).thenReturn(true);
         when(mUserManager.getProfiles(anyInt())).thenReturn(Collections.singletonList(
@@ -397,6 +421,7 @@
         when(mKeyguardStateController.isShowing()).thenReturn(true);
         when(mDevicePolicyManager.isDeviceManaged()).thenReturn(false);
         createController();
+        mController.setVisible(true);
 
         when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true);
         when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(null);
@@ -424,7 +449,7 @@
     @Test
     public void disclosure_deviceOwner_financedDeviceWithOrganizationName() {
         createController();
-
+        mController.setVisible(true);
         when(mKeyguardStateController.isShowing()).thenReturn(true);
         when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true);
         when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(ORGANIZATION_NAME);
@@ -432,6 +457,7 @@
                 .thenReturn(DEVICE_OWNER_TYPE_FINANCED);
         sendUpdateDisclosureBroadcast();
         mExecutor.runAllReady();
+        mController.setVisible(true);
 
         verifyIndicationMessage(INDICATION_TYPE_DISCLOSURE, mFinancedDisclosureWithOrganization);
     }
@@ -469,10 +495,10 @@
     @Test
     public void transientIndication_visibleWhenDozing() {
         createController();
-
         mController.setVisible(true);
-        mController.showTransientIndication(TEST_STRING_RES);
+
         mStatusBarStateListener.onDozingChanged(true);
+        mController.showTransientIndication(TEST_STRING_RES);
 
         assertThat(mTextView.getText()).isEqualTo(
                 mContext.getResources().getString(TEST_STRING_RES));
@@ -493,7 +519,7 @@
         reset(mRotateTextViewController);
         mStatusBarStateListener.onDozingChanged(true);
 
-        verifyHideIndication(INDICATION_TYPE_BIOMETRIC_MESSAGE);
+        assertThat(mTextView.getText()).isNotEqualTo(message);
     }
 
     @Test
@@ -604,10 +630,9 @@
     }
 
     @Test
-    public void updateMonitor_listener() {
+    public void registersKeyguardStateCallback() {
         createController();
         verify(mKeyguardStateController).addCallback(any());
-        verify(mKeyguardUpdateMonitor, times(2)).registerCallback(any());
     }
 
     @Test
@@ -695,13 +720,13 @@
     @Test
     public void onRefreshBatteryInfo_dozing_dischargingWithOverheat_presentBatteryPercentage() {
         createController();
+        mController.setVisible(true);
         BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_DISCHARGING,
                 90 /* level */, 0 /* plugged */, BatteryManager.BATTERY_HEALTH_OVERHEAT,
                 0 /* maxChargingWattage */, true /* present */);
 
         mController.getKeyguardCallback().onRefreshBatteryInfo(status);
         mStatusBarStateListener.onDozingChanged(true);
-        mController.setVisible(true);
 
         String percentage = NumberFormat.getPercentInstance().format(90 / 100f);
         assertThat(mTextView.getText()).isEqualTo(percentage);
@@ -710,9 +735,9 @@
     @Test
     public void onRequireUnlockForNfc_showsRequireUnlockForNfcIndication() {
         createController();
+        mController.setVisible(true);
         String message = mContext.getString(R.string.require_unlock_for_nfc);
         mController.getKeyguardCallback().onRequireUnlockForNfc();
-        mController.setVisible(true);
 
         verifyTransientMessage(message);
     }
@@ -778,6 +803,9 @@
     @Test
     public void testOnKeyguardShowingChanged_showing_updatesPersistentMessages() {
         createController();
+        mController.setVisible(true);
+        mExecutor.runAllReady();
+        reset(mRotateTextViewController);
 
         // GIVEN keyguard is showing
         when(mKeyguardStateController.isShowing()).thenReturn(true);
@@ -799,6 +827,8 @@
     @Test
     public void onTrustGrantedMessageDoesNotShowUntilTrustGranted() {
         createController();
+        mController.setVisible(true);
+        reset(mRotateTextViewController);
 
         // GIVEN a trust granted message but trust isn't granted
         final String trustGrantedMsg = "testing trust granted message";
@@ -808,7 +838,7 @@
 
         // WHEN trust is granted
         when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
-        mController.setVisible(true);
+        mKeyguardUpdateMonitorCallback.onTrustChanged(KeyguardUpdateMonitor.getCurrentUser());
 
         // THEN verify the trust granted message shows
         verifyIndicationMessage(
@@ -819,6 +849,7 @@
     @Test
     public void onTrustGrantedMessageDoesShowsOnTrustGranted() {
         createController();
+        mController.setVisible(true);
 
         // GIVEN trust is granted
         when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
index 077b41a..5f2bbd3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
@@ -126,12 +126,6 @@
         setAodEnabledForTest(true);
         setShouldControlUnlockedScreenOffForTest(true);
         setDisplayNeedsBlankingForTest(false);
-
-        // Default to false here (with one test to make sure that when it returns true, we respect
-        // that). We'll test the specific conditions for this to return true/false in the
-        // UnlockedScreenOffAnimationController's tests.
-        when(mUnlockedScreenOffAnimationController.shouldPlayUnlockedScreenOffAnimation())
-                .thenReturn(false);
     }
 
     @Test
@@ -180,12 +174,9 @@
      */
     @Test
     public void testControlUnlockedScreenOffAnimation_dozeAfterScreenOff_false() {
-        mDozeParameters.mKeyguardVisibilityCallback.onKeyguardVisibilityChanged(true);
-
         // If AOD is disabled, we shouldn't want to control screen off. Also, let's double check
         // that when that value is updated, we called through to PowerManager.
         setAodEnabledForTest(false);
-
         assertFalse(mDozeParameters.shouldControlScreenOff());
         assertTrue(mPowerManagerDozeAfterScreenOff);
 
@@ -197,6 +188,7 @@
 
     @Test
     public void testControlUnlockedScreenOffAnimationDisabled_dozeAfterScreenOff() {
+        setShouldControlUnlockedScreenOffForTest(true);
         when(mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ANIMATIONS)).thenReturn(false);
 
         assertFalse(mDozeParameters.shouldControlUnlockedScreenOff());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
index 0936b77..050563a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
@@ -31,7 +31,6 @@
 import com.android.systemui.statusbar.StatusBarStateControllerImpl
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.settings.GlobalSettings
-import junit.framework.Assert.assertFalse
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
@@ -134,7 +133,7 @@
      */
     @Test
     fun testAodUiShownIfNotInteractive() {
-        `when`(dozeParameters.canControlUnlockedScreenOff()).thenReturn(true)
+        `when`(dozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true)
         `when`(powerManager.isInteractive).thenReturn(false)
 
         val callbackCaptor = ArgumentCaptor.forClass(Runnable::class.java)
@@ -157,7 +156,7 @@
      */
     @Test
     fun testAodUiNotShownIfInteractive() {
-        `when`(dozeParameters.canControlUnlockedScreenOff()).thenReturn(true)
+        `when`(dozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true)
         `when`(powerManager.isInteractive).thenReturn(true)
 
         val callbackCaptor = ArgumentCaptor.forClass(Runnable::class.java)
@@ -168,13 +167,4 @@
 
         verify(notificationPanelViewController, never()).showAodUi()
     }
-
-    @Test
-    fun testNoAnimationPlaying_dozeParamsCanNotControlScreenOff() {
-        `when`(dozeParameters.canControlUnlockedScreenOff()).thenReturn(false)
-
-        assertFalse(controller.shouldPlayUnlockedScreenOffAnimation())
-        controller.startAnimation()
-        assertFalse(controller.isAnimationPlaying())
-    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java
index 9f9cb40..12cfb32 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java
@@ -14,9 +14,12 @@
 
 package com.android.systemui.statusbar.policy;
 
+
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -24,6 +27,7 @@
 
 import android.app.AppOpsManager;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.location.LocationManager;
 import android.os.Handler;
@@ -67,6 +71,7 @@
     private DeviceConfigProxy mDeviceConfigProxy;
     private UiEventLoggerFake mUiEventLogger;
 
+    @Mock private PackageManager mPackageManager;
     @Mock private AppOpsController mAppOpsController;
     @Mock private UserTracker mUserTracker;
     @Mock private SecureSettings mSecureSettings;
@@ -82,6 +87,7 @@
         mUiEventLogger = new UiEventLoggerFake();
 
         mTestableLooper = TestableLooper.get(this);
+
         mLocationController = new LocationControllerImpl(mContext,
                 mAppOpsController,
                 mDeviceConfigProxy,
@@ -90,7 +96,7 @@
                 mock(BroadcastDispatcher.class),
                 mock(BootCompleteCache.class),
                 mUserTracker,
-                mContext.getPackageManager(),
+                mPackageManager,
                 mUiEventLogger,
                 mSecureSettings);
 
@@ -178,6 +184,8 @@
 
     @Test
     public void testCallbackNotified_additionalOps() {
+        // Return -1 for non system apps
+        when(mPackageManager.getPermissionFlags(any(), any(), any())).thenReturn(-1);
         LocationChangeCallback callback = mock(LocationChangeCallback.class);
         mLocationController.addCallback(callback);
         mDeviceConfigProxy.setProperty(
@@ -190,10 +198,10 @@
         when(mAppOpsController.getActiveAppOps())
                 .thenReturn(ImmutableList.of(
                         new AppOpItem(AppOpsManager.OP_FINE_LOCATION, 0,
-                                "com.google.android.googlequicksearchbox",
+                                "com.third.party.app",
                                 System.currentTimeMillis())));
         mLocationController.onActiveStateChanged(AppOpsManager.OP_FINE_LOCATION, 0,
-                "com.google.android.googlequicksearchbox", true);
+                "ccom.third.party.app", true);
 
         mTestableLooper.processAllMessages();
 
@@ -201,14 +209,14 @@
 
         when(mAppOpsController.getActiveAppOps()).thenReturn(ImmutableList.of());
         mLocationController.onActiveStateChanged(AppOpsManager.OP_FINE_LOCATION, 0,
-                "com.google.android.googlequicksearchbox", false);
+                "com.third.party.app", false);
         mTestableLooper.processAllMessages();
 
         verify(callback, times(1)).onLocationActiveChanged(false);
 
         when(mAppOpsController.getActiveAppOps()).thenReturn(ImmutableList.of());
         mLocationController.onActiveStateChanged(AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION, 0,
-                "com.google.android.googlequicksearchbox", true);
+                "com.third.party.app", true);
         mTestableLooper.processAllMessages();
         verify(callback, times(1)).onLocationActiveChanged(true);
     }
@@ -227,10 +235,10 @@
         when(mAppOpsController.getActiveAppOps())
                 .thenReturn(ImmutableList.of(
                         new AppOpItem(AppOpsManager.OP_FINE_LOCATION, 0,
-                                "com.google.android.gms",
+                                "com.system.app",
                                 System.currentTimeMillis())));
         mLocationController.onActiveStateChanged(AppOpsManager.OP_FINE_LOCATION, 0,
-                "com.google.android.gms", true);
+                "com.system.app", true);
 
         mTestableLooper.processAllMessages();
 
@@ -258,10 +266,10 @@
 
         when(mAppOpsController.getActiveAppOps())
                 .thenReturn(ImmutableList.of(
-                        new AppOpItem(AppOpsManager.OP_FINE_LOCATION, 0, "com.google.android.gms",
+                        new AppOpItem(AppOpsManager.OP_FINE_LOCATION, 0, "com.system.app",
                                 System.currentTimeMillis())));
         mLocationController.onActiveStateChanged(AppOpsManager.OP_FINE_LOCATION, 0,
-                "com.google.android.gms", true);
+                "com.system.app", true);
 
         mTestableLooper.processAllMessages();
 
@@ -269,7 +277,7 @@
 
         when(mAppOpsController.getActiveAppOps()).thenReturn(ImmutableList.of());
         mLocationController.onActiveStateChanged(AppOpsManager.OP_FINE_LOCATION, 0,
-                "com.google.android.gms", false);
+                "com.system.app", false);
         mTestableLooper.processAllMessages();
 
         verify(callback, times(1)).onLocationActiveChanged(false);
@@ -277,7 +285,7 @@
         when(mSecureSettings.getInt(Settings.Secure.LOCATION_SHOW_SYSTEM_OPS, 0))
                 .thenReturn(0);
         mLocationController.onActiveStateChanged(AppOpsManager.OP_FINE_LOCATION, 0,
-                "com.google.android.gms", true);
+                "com.system.app", true);
 
         mTestableLooper.processAllMessages();
 
@@ -287,6 +295,11 @@
 
     @Test
     public void testCallbackNotified_verifyMetrics() {
+        // Return -1 for non system apps and 0 for system apps.
+        when(mPackageManager.getPermissionFlags(any(),
+                eq("com.system.app"), any())).thenReturn(0);
+        when(mPackageManager.getPermissionFlags(any(),
+                eq("com.third.party.app"), any())).thenReturn(-1);
         LocationChangeCallback callback = mock(LocationChangeCallback.class);
         mLocationController.addCallback(callback);
         mDeviceConfigProxy.setProperty(
@@ -298,16 +311,16 @@
 
         when(mAppOpsController.getActiveAppOps())
                 .thenReturn(ImmutableList.of(
-                        new AppOpItem(AppOpsManager.OP_FINE_LOCATION, 0, "com.google.android.gms",
+                        new AppOpItem(AppOpsManager.OP_FINE_LOCATION, 0, "com.system.app",
                                 System.currentTimeMillis()),
                         new AppOpItem(AppOpsManager.OP_COARSE_LOCATION, 0,
-                                "com.google.android.googlequicksearchbox",
+                                "com.third.party.app",
                                 System.currentTimeMillis()),
                         new AppOpItem(AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION, 0,
-                                "com.google.android.apps.maps",
+                                "com.another.developer.app",
                                 System.currentTimeMillis())));
         mLocationController.onActiveStateChanged(AppOpsManager.OP_FINE_LOCATION, 0,
-                "com.google.android.gms", true);
+                "com.system.app", true);
 
         mTestableLooper.processAllMessages();
 
@@ -328,7 +341,7 @@
 
         when(mAppOpsController.getActiveAppOps()).thenReturn(ImmutableList.of());
         mLocationController.onActiveStateChanged(AppOpsManager.OP_FINE_LOCATION, 0,
-                "com.google.android.gms", false);
+                "com.system.app", false);
         mTestableLooper.processAllMessages();
 
         verify(callback, times(1)).onLocationActiveChanged(false);
@@ -354,4 +367,4 @@
         // No new callbacks
         verify(callback).onLocationSettingsChanged(anyBoolean());
     }
-}
+}
\ No newline at end of file
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 0e99265..0ea087d 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -2749,6 +2749,9 @@
     }
 
     private void updateWindowMagnificationConnectionIfNeeded(AccessibilityUserState userState) {
+        if (!mMagnificationController.supportWindowMagnification()) {
+            return;
+        }
         final boolean connect = (userState.isShortcutMagnificationEnabledLocked()
                 || userState.isDisplayMagnificationEnabledLocked())
                 && (userState.getMagnificationCapabilitiesLocked()
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index a958209..fa32452 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -303,7 +303,7 @@
         public void onImeWindowVisibilityChanged(boolean shown) {
             final Message m = PooledLambda.obtainMessage(
                     FullScreenMagnificationController::notifyImeWindowVisibilityChanged,
-                    FullScreenMagnificationController.this, shown);
+                    FullScreenMagnificationController.this, mDisplayId, shown);
             mControllerCtx.getHandler().sendMessage(m);
         }
 
@@ -1215,11 +1215,12 @@
     /**
      * Notifies that the IME window visibility changed.
      *
+     * @param displayId the logical display id
      * @param shown {@code true} means the IME window shows on the screen. Otherwise it's
      *                           hidden.
      */
-    void notifyImeWindowVisibilityChanged(boolean shown) {
-        mMagnificationInfoChangedCallback.onImeWindowVisibilityChanged(shown);
+    void notifyImeWindowVisibilityChanged(int displayId, boolean shown) {
+        mMagnificationInfoChangedCallback.onImeWindowVisibilityChanged(displayId, shown);
     }
 
     /**
@@ -1609,17 +1610,19 @@
          * Called when the state of the magnification activation is changed.
          * It is for the logging data of the magnification activation state.
          *
-         * @param displayId The logical display id.
+         * @param displayId the logical display id
          * @param activated {@code true} if the magnification is activated, otherwise {@code false}.
          */
         void onFullScreenMagnificationActivationState(int displayId, boolean activated);
 
         /**
          * Called when the IME window visibility changed.
+         *
+         * @param displayId the logical display id
          * @param shown {@code true} means the IME window shows on the screen. Otherwise it's
          *                           hidden.
          */
-        void onImeWindowVisibilityChanged(boolean shown);
+        void onImeWindowVisibilityChanged(int displayId, boolean shown);
 
         /**
          * Called when the magnification spec changed.
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index 09e82c7..62ba0c8 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -17,6 +17,7 @@
 package com.android.server.accessibility.magnification;
 
 import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_WINDOW;
+import static android.content.pm.PackageManager.FEATURE_WINDOW_MAGNIFICATION;
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
@@ -38,6 +39,7 @@
 import android.provider.Settings;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 import android.view.accessibility.MagnificationAnimationCallback;
 
 import com.android.internal.accessibility.util.AccessibilityStatsLogUtils;
@@ -91,6 +93,8 @@
     private FullScreenMagnificationController mFullScreenMagnificationController;
     private WindowMagnificationManager mWindowMagnificationMgr;
     private int mMagnificationCapabilities = ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
+    /** Whether the platform supports window magnification feature. */
+    private final boolean mSupportWindowMagnification;
 
     @GuardedBy("mLock")
     private int mActivatedMode = ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
@@ -99,7 +103,7 @@
     // Track the active user to reset the magnification and get the associated user settings.
     private @UserIdInt int mUserId = UserHandle.USER_SYSTEM;
     @GuardedBy("mLock")
-    private boolean mImeWindowVisible = false;
+    private final SparseBooleanArray mIsImeVisibleArray = new SparseBooleanArray();
     private long mWindowModeEnabledTime = 0;
     private long mFullScreenModeEnabledTime = 0;
 
@@ -129,6 +133,8 @@
         mScaleProvider = scaleProvider;
         LocalServices.getService(WindowManagerInternal.class)
                 .getAccessibilityController().setUiChangesForAccessibilityCallbacks(this);
+        mSupportWindowMagnification = context.getPackageManager().hasSystemFeature(
+                FEATURE_WINDOW_MAGNIFICATION);
     }
 
     @VisibleForTesting
@@ -185,6 +191,11 @@
         }
     }
 
+    /** Returns {@code true} if the platform supports window magnification feature. */
+    public boolean supportWindowMagnification() {
+        return mSupportWindowMagnification;
+    }
+
     /**
      * Transitions to the target Magnification mode with current center of the magnification mode
      * if it is available.
@@ -377,7 +388,7 @@
                 setActivatedModeAndSwitchDelegate(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
                 mLastActivatedMode = mActivatedMode;
             }
-            logMagnificationModeWithImeOnIfNeeded();
+            logMagnificationModeWithImeOnIfNeeded(displayId);
             disableFullScreenMagnificationIfNeeded(displayId);
         } else {
             logMagnificationUsageState(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
@@ -432,7 +443,7 @@
                 setActivatedModeAndSwitchDelegate(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
                 mLastActivatedMode = mActivatedMode;
             }
-            logMagnificationModeWithImeOnIfNeeded();
+            logMagnificationModeWithImeOnIfNeeded(displayId);
             disableWindowMagnificationIfNeeded(displayId);
         } else {
             logMagnificationUsageState(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN,
@@ -454,12 +465,12 @@
     }
 
     @Override
-    public void onImeWindowVisibilityChanged(boolean shown) {
+    public void onImeWindowVisibilityChanged(int displayId, boolean shown) {
         synchronized (mLock) {
-            mImeWindowVisible = shown;
+            mIsImeVisibleArray.put(displayId, shown);
         }
-        getWindowMagnificationMgr().onImeWindowVisibilityChanged(shown);
-        logMagnificationModeWithImeOnIfNeeded();
+        getWindowMagnificationMgr().onImeWindowVisibilityChanged(displayId, shown);
+        logMagnificationModeWithImeOnIfNeeded(displayId);
     }
 
     /**
@@ -575,11 +586,12 @@
         }
     }
 
-    private void logMagnificationModeWithImeOnIfNeeded() {
+    private void logMagnificationModeWithImeOnIfNeeded(int displayId) {
         final int mode;
 
         synchronized (mLock) {
-            if (!mImeWindowVisible || mActivatedMode == ACCESSIBILITY_MAGNIFICATION_MODE_NONE) {
+            if (!mIsImeVisibleArray.get(displayId, false)
+                    || mActivatedMode == ACCESSIBILITY_MAGNIFICATION_MODE_NONE) {
                 return;
             }
             mode = mActivatedMode;
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
index 175182c..3e07b09 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
@@ -396,6 +396,10 @@
 
             dumpTrackingTypingFocusEnabledState(pw, displayId, config.getMode());
         }
+        pw.append("    SupportWindowMagnification="
+                + mController.supportWindowMagnification()).println();
+        pw.append("    WindowMagnificationConnectionState="
+                + mController.getWindowMagnificationMgr().getConnectionState()).println();
     }
 
     private int getIdOfLastServiceToMagnify(int mode, int displayId) {
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java
index 25dcc2a..041eece 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java
@@ -133,6 +133,25 @@
         return true;
     }
 
+    boolean moveWindowMagnifierToPosition(int displayId, float positionX, float positionY,
+            @Nullable MagnificationAnimationCallback callback) {
+        if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) {
+            mTrace.logTrace(TAG + ".moveWindowMagnifierToPosition",
+                    FLAGS_WINDOW_MAGNIFICATION_CONNECTION, "displayId=" + displayId
+                            + ";positionX=" + positionX + ";positionY=" + positionY);
+        }
+        try {
+            mConnection.moveWindowMagnifierToPosition(displayId, positionX, positionY,
+                    transformToRemoteCallback(callback, mTrace));
+        } catch (RemoteException e) {
+            if (DBG) {
+                Slog.e(TAG, "Error calling moveWindowMagnifierToPosition()", e);
+            }
+            return false;
+        }
+        return true;
+    }
+
     boolean showMagnificationButton(int displayId, int magnificationMode) {
         if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) {
             mTrace.logTrace(TAG + ".showMagnificationButton",
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 89910ea..aeb1112 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
@@ -36,8 +36,10 @@
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 import android.view.MotionEvent;
 import android.view.accessibility.IWindowMagnificationConnection;
 import android.view.accessibility.IWindowMagnificationConnectionCallback;
@@ -87,6 +89,30 @@
     })
     public @interface WindowPosition {}
 
+    /** Window magnification connection is connecting. */
+    private static final int CONNECTING = 0;
+    /** Window magnification connection is connected. */
+    private static final int CONNECTED = 1;
+    /** Window magnification connection is disconnecting. */
+    private static final int DISCONNECTING = 2;
+    /** Window magnification connection is disconnected. */
+    private static final int DISCONNECTED = 3;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"CONNECTION_STATE"}, value = {
+            CONNECTING,
+            CONNECTED,
+            DISCONNECTING,
+            DISCONNECTED
+    })
+    private @interface ConnectionState {
+    }
+
+    @ConnectionState
+    private int mConnectionState = DISCONNECTED;
+
+    private static final int WAIT_CONNECTION_TIMEOUT_MILLIS = 100;
+
     private final Object mLock;
     private final Context mContext;
     @VisibleForTesting
@@ -99,6 +125,7 @@
     private SparseArray<WindowMagnifier> mWindowMagnifiers = new SparseArray<>();
     // Whether the following typing focus feature for magnification is enabled.
     private boolean mMagnificationFollowTypingEnabled = true;
+    private final SparseBooleanArray mIsImeVisibleArray = new SparseBooleanArray();
 
     private boolean mReceiverRegistered = false;
     @VisibleForTesting
@@ -178,7 +205,7 @@
      */
     public void setConnection(@Nullable IWindowMagnificationConnection connection) {
         if (DBG) {
-            Slog.d(TAG, "setConnection :" + connection);
+            Slog.d(TAG, "setConnection :" + connection + " ,mConnectionState=" + mConnectionState);
         }
         synchronized (mLock) {
             // Reset connectionWrapper.
@@ -189,6 +216,13 @@
                 }
                 mConnectionWrapper.unlinkToDeath(mConnectionCallback);
                 mConnectionWrapper = null;
+                // The connection is still connecting so it is no need to reset the
+                // connection state to disconnected.
+                // TODO b/220086369 will reset the connection immediately when requestConnection
+                //  is called
+                if (mConnectionState != CONNECTING) {
+                    setConnectionState(DISCONNECTED);
+                }
             }
             if (connection != null) {
                 mConnectionWrapper = new WindowMagnificationConnectionWrapper(connection, mTrace);
@@ -199,9 +233,13 @@
                     mConnectionCallback = new ConnectionCallback();
                     mConnectionWrapper.linkToDeath(mConnectionCallback);
                     mConnectionWrapper.setConnectionCallback(mConnectionCallback);
+                    setConnectionState(CONNECTED);
                 } catch (RemoteException e) {
                     Slog.e(TAG, "setConnection failed", e);
                     mConnectionWrapper = null;
+                    setConnectionState(DISCONNECTED);
+                } finally {
+                    mLock.notify();
                 }
             }
         }
@@ -229,10 +267,20 @@
         if (DBG) {
             Slog.d(TAG, "requestConnection :" + connect);
         }
+        if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) {
+            mTrace.logTrace(TAG + ".requestWindowMagnificationConnection",
+                    FLAGS_WINDOW_MAGNIFICATION_CONNECTION, "connect=" + connect);
+        }
         synchronized (mLock) {
-            if (connect == isConnected()) {
+            if ((connect && (mConnectionState == CONNECTED || mConnectionState == CONNECTING))
+                    || (!connect && (mConnectionState == DISCONNECTED
+                    || mConnectionState == DISCONNECTING))) {
+                Slog.w(TAG,
+                        "requestConnection duplicated request: connect=" + connect
+                                + " ,mConnectionState=" + mConnectionState);
                 return false;
             }
+
             if (connect) {
                 final IntentFilter intentFilter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
                 if (!mReceiverRegistered) {
@@ -247,19 +295,42 @@
                 }
             }
         }
-        if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) {
-            mTrace.logTrace(TAG + ".requestWindowMagnificationConnection",
-                    FLAGS_WINDOW_MAGNIFICATION_CONNECTION, "connect=" + connect);
+        if (requestConnectionInternal(connect)) {
+            setConnectionState(connect ? CONNECTING : DISCONNECTING);
+            return true;
+        } else {
+            setConnectionState(DISCONNECTED);
+            return false;
         }
+    }
+
+    private boolean requestConnectionInternal(boolean connect) {
         final long identity = Binder.clearCallingIdentity();
         try {
             final StatusBarManagerInternal service = LocalServices.getService(
                     StatusBarManagerInternal.class);
-            service.requestWindowMagnificationConnection(connect);
+            if (service != null) {
+                return service.requestWindowMagnificationConnection(connect);
+            }
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
-        return true;
+        return false;
+    }
+
+    /**
+     * Returns window magnification connection state.
+     */
+    public int getConnectionState() {
+        return mConnectionState;
+    }
+
+    private void setConnectionState(@ConnectionState int state) {
+        if (DBG) {
+            Slog.d(TAG, "setConnectionState : state=" + state + " ,mConnectionState="
+                    + mConnectionState);
+        }
+        mConnectionState = state;
     }
 
     /**
@@ -314,9 +385,13 @@
         float toCenterX = (float) (left + right) / 2;
         float toCenterY = (float) (top + bottom) / 2;
 
-        if (!isPositionInSourceBounds(displayId, toCenterX, toCenterY)
-                && isTrackingTypingFocusEnabled(displayId)) {
-            enableWindowMagnification(displayId, Float.NaN, toCenterX, toCenterY);
+        synchronized (mLock) {
+            if (mIsImeVisibleArray.get(displayId, false)
+                    && !isPositionInSourceBounds(displayId, toCenterX, toCenterY)
+                    && isTrackingTypingFocusEnabled(displayId)) {
+                moveWindowMagnifierToPositionInternal(displayId, toCenterX, toCenterY,
+                        STUB_ANIMATION_CALLBACK);
+            }
         }
     }
 
@@ -357,7 +432,7 @@
      * @param displayId The logical display id.
      * @param trackingTypingFocusEnabled Enabled or disable the function of tracking typing focus.
      */
-    private void setTrackingTypingFocusEnabled(int displayId, boolean trackingTypingFocusEnabled) {
+    void setTrackingTypingFocusEnabled(int displayId, boolean trackingTypingFocusEnabled) {
         synchronized (mLock) {
             WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
             if (magnifier == null) {
@@ -384,7 +459,8 @@
      *
      * @param shown {@code true} means the IME window shows on the screen. Otherwise, it's hidden.
      */
-    void onImeWindowVisibilityChanged(boolean shown) {
+    void onImeWindowVisibilityChanged(int displayId, boolean shown) {
+        mIsImeVisibleArray.put(displayId, shown);
         if (shown) {
             enableAllTrackingTypingFocus();
         }
@@ -500,8 +576,11 @@
                     animationCallback, windowPosition, id);
         }
 
-        if (enabled && !previousEnabled) {
-            mCallback.onWindowMagnificationActivationState(displayId, true);
+        if (enabled) {
+            setTrackingTypingFocusEnabled(displayId, true);
+            if (!previousEnabled) {
+                mCallback.onWindowMagnificationActivationState(displayId, true);
+            }
         }
         return enabled;
     }
@@ -563,14 +642,13 @@
         }
     }
 
+    @GuardedBy("mLock")
     boolean isPositionInSourceBounds(int displayId, float x, float y) {
-        synchronized (mLock) {
-            WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
-            if (magnifier == null) {
-                return false;
-            }
-            return magnifier.isPositionInSourceBounds(x, y);
+        WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
+        if (magnifier == null) {
+            return false;
         }
+        return magnifier.isPositionInSourceBounds(x, y);
     }
 
     /**
@@ -829,10 +907,10 @@
         }
 
         @Override
-        public void onDrag(int displayId) {
+        public void onMove(int displayId) {
             if (mTrace.isA11yTracingEnabledForTypes(
                     FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK)) {
-                mTrace.logTrace(TAG + "ConnectionCallback.onDrag",
+                mTrace.logTrace(TAG + "ConnectionCallback.onMove",
                         FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK,
                         "displayId=" + displayId);
             }
@@ -849,6 +927,7 @@
                 mConnectionWrapper.unlinkToDeath(this);
                 mConnectionWrapper = null;
                 mConnectionCallback = null;
+                setConnectionState(DISCONNECTED);
                 resetWindowMagnifiers();
             }
         }
@@ -884,7 +963,6 @@
             mWindowMagnificationManager = windowMagnificationManager;
         }
 
-        @GuardedBy("mLock")
         boolean enableWindowMagnificationInternal(float scale, float centerX, float centerY,
                 @Nullable MagnificationAnimationCallback animationCallback,
                 @WindowPosition int windowPosition, int id) {
@@ -962,7 +1040,6 @@
             return mIdOfLastServiceToControl;
         }
 
-        @GuardedBy("mLock")
         int pointersInWindow(MotionEvent motionEvent) {
             int count = 0;
             final int pointerCount = motionEvent.getPointerCount();
@@ -1025,11 +1102,22 @@
             float centerY, float magnificationFrameOffsetRatioX,
             float magnificationFrameOffsetRatioY,
             MagnificationAnimationCallback animationCallback) {
+        // Wait for the connection with a timeout.
+        final long endMillis = SystemClock.uptimeMillis() + WAIT_CONNECTION_TIMEOUT_MILLIS;
+        while (mConnectionState == CONNECTING && (SystemClock.uptimeMillis() < endMillis)) {
+            try {
+                mLock.wait(endMillis - SystemClock.uptimeMillis());
+            } catch (InterruptedException ie) {
+                /* ignore */
+            }
+        }
         if (mConnectionWrapper == null) {
-            Slog.w(TAG, "enableWindowMagnificationInternal mConnectionWrapper is null");
+            Slog.w(TAG,
+                    "enableWindowMagnificationInternal mConnectionWrapper is null. "
+                            + "mConnectionState=" + mConnectionState);
             return false;
         }
-        return  mConnectionWrapper.enableWindowMagnification(
+        return mConnectionWrapper.enableWindowMagnification(
                 displayId, scale, centerX, centerY,
                 magnificationFrameOffsetRatioX, magnificationFrameOffsetRatioY,
                 animationCallback);
@@ -1050,8 +1138,16 @@
                 displayId, animationCallback);
     }
 
+    @GuardedBy("mLock")
     private boolean moveWindowMagnifierInternal(int displayId, float offsetX, float offsetY) {
         return mConnectionWrapper != null && mConnectionWrapper.moveWindowMagnifier(
                 displayId, offsetX, offsetY);
     }
+
+    @GuardedBy("mLock")
+    private boolean moveWindowMagnifierToPositionInternal(int displayId, float positionX,
+            float positionY, MagnificationAnimationCallback animationCallback) {
+        return mConnectionWrapper != null && mConnectionWrapper.moveWindowMagnifierToPosition(
+                displayId, positionX, positionY, animationCallback);
+    }
 }
diff --git a/services/api/current.txt b/services/api/current.txt
index e46c972..440f66a 100644
--- a/services/api/current.txt
+++ b/services/api/current.txt
@@ -38,7 +38,7 @@
 package com.android.server.am {
 
   public interface ActivityManagerLocal {
-    method public boolean bindSupplementalProcessService(@NonNull android.content.Intent, @NonNull android.content.ServiceConnection, int, @NonNull String, int) throws android.os.RemoteException;
+    method public boolean bindSdkSandboxService(@NonNull android.content.Intent, @NonNull android.content.ServiceConnection, int, @NonNull String, int) throws android.os.RemoteException;
     method public boolean canStartForegroundService(int, int, @NonNull String);
   }
 
diff --git a/services/core/Android.bp b/services/core/Android.bp
index a2b289c..0241c1e 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -133,7 +133,7 @@
         "app-compat-annotations",
         "framework-tethering.stubs.module_lib",
         "service-permission.stubs.system_server",
-        "service-supplementalprocess.stubs.system_server",
+        "service-sdksandbox.stubs.system_server",
     ],
 
     required: [
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 111bd34..e6953f0 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -657,6 +657,11 @@
     public abstract void notifyPackageUse(String packageName, int reason);
 
     /**
+     * Notify the package is force stopped.
+     */
+    public abstract void onPackageProcessKilledForUninstall(String packageName);
+
+    /**
      * Returns a package object for the given package name.
      */
     public abstract @Nullable AndroidPackage getPackage(@NonNull String packageName);
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index 6986d3b..1f8ef82 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -18,14 +18,22 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ModuleInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.os.Build;
+import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
+import android.os.ServiceManager;
 import android.os.ShellCallback;
 import android.os.ShellCommand;
 import android.os.SystemProperties;
@@ -42,6 +50,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.Executors;
 import java.util.stream.Collectors;
 
 /**
@@ -49,6 +58,7 @@
  */
 public class BinaryTransparencyService extends SystemService {
     private static final String TAG = "TransparencyService";
+    private static final String EXTRA_SERVICE = "service";
 
     @VisibleForTesting
     static final String VBMETA_DIGEST_UNINITIALIZED = "vbmeta-digest-uninitialized";
@@ -365,10 +375,80 @@
 
         // we are only interested in doing things at PHASE_BOOT_COMPLETED
         if (phase == PHASE_BOOT_COMPLETED) {
-            // due to potentially long computation that holds up boot time, apex sha computations
-            // are deferred to first call
             Slog.i(TAG, "Boot completed. Getting VBMeta Digest.");
             getVBMetaDigestInformation();
+
+            // due to potentially long computation that holds up boot time, computations for
+            // SHA256 digests of APEX and Module packages are scheduled here,
+            // but only executed when device is idle.
+            Slog.i(TAG, "Scheduling APEX and Module measurements to be updated.");
+            UpdateMeasurementsJobService.scheduleBinaryMeasurements(mContext,
+                    BinaryTransparencyService.this);
+        }
+    }
+
+    /**
+     * JobService to update binary measurements and update internal cache.
+     */
+    public static class UpdateMeasurementsJobService extends JobService {
+        private static final int COMPUTE_APEX_MODULE_SHA256_JOB_ID =
+                BinaryTransparencyService.UpdateMeasurementsJobService.class.hashCode();
+
+        @Override
+        public boolean onStartJob(JobParameters params) {
+            Slog.d(TAG, "Job to update binary measurements started.");
+            if (params.getJobId() != COMPUTE_APEX_MODULE_SHA256_JOB_ID) {
+                return false;
+            }
+
+            // we'll still update the measurements via threads to be mindful of low-end devices
+            // where this operation might take longer than expected, and so that we don't block
+            // system_server's main thread.
+            Executors.defaultThreadFactory().newThread(() -> {
+                // since we can't call updateBinaryMeasurements() directly, calling
+                // getApexInfo() achieves the same effect, and we simply discard the return
+                // value
+
+                IBinder b = ServiceManager.getService(Context.BINARY_TRANSPARENCY_SERVICE);
+                IBinaryTransparencyService iBtsService =
+                        IBinaryTransparencyService.Stub.asInterface(b);
+                try {
+                    iBtsService.getApexInfo();
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Updating binary measurements was interrupted.", e);
+                    return;
+                }
+                jobFinished(params, false);
+            }).start();
+
+            return true;
+        }
+
+        @Override
+        public boolean onStopJob(JobParameters params) {
+            return false;
+        }
+
+        @SuppressLint("DefaultLocale")
+        static void scheduleBinaryMeasurements(Context context, BinaryTransparencyService service) {
+            Slog.i(TAG, "Scheduling APEX & Module SHA256 digest computation job");
+            final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+            if (jobScheduler == null) {
+                Slog.e(TAG, "Failed to obtain an instance of JobScheduler.");
+                return;
+            }
+
+            final JobInfo jobInfo = new JobInfo.Builder(COMPUTE_APEX_MODULE_SHA256_JOB_ID,
+                    new ComponentName(context, UpdateMeasurementsJobService.class))
+                    .setRequiresDeviceIdle(true)
+                    .build();
+            if (jobScheduler.schedule(jobInfo) != JobScheduler.RESULT_SUCCESS) {
+                Slog.e(TAG, "Failed to schedule job to update binary measurements.");
+                return;
+            }
+            Slog.d(TAG, String.format(
+                    "Job %d to update binary measurements scheduled successfully.",
+                    COMPUTE_APEX_MODULE_SHA256_JOB_ID));
         }
     }
 
@@ -380,7 +460,7 @@
 
     @NonNull
     private List<PackageInfo> getInstalledApexs() {
-        List<PackageInfo> results = new ArrayList<PackageInfo>();
+        List<PackageInfo> results = new ArrayList<>();
         PackageManager pm = mContext.getPackageManager();
         if (pm == null) {
             Slog.e(TAG, "Error obtaining an instance of PackageManager.");
diff --git a/services/core/java/com/android/server/NetworkTimeUpdateService.java b/services/core/java/com/android/server/NetworkTimeUpdateService.java
index fcde533..1e534b7 100644
--- a/services/core/java/com/android/server/NetworkTimeUpdateService.java
+++ b/services/core/java/com/android/server/NetworkTimeUpdateService.java
@@ -36,12 +36,15 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.PowerManager;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
 import android.os.SystemClock;
 import android.os.TimestampedValue;
 import android.provider.Settings;
 import android.util.LocalLog;
 import android.util.Log;
 import android.util.NtpTrustedTime;
+import android.util.NtpTrustedTime.TimeResult;
 import android.util.TimeUtils;
 
 import com.android.internal.util.DumpUtils;
@@ -152,6 +155,42 @@
                 }, new IntentFilter(ACTION_POLL));
     }
 
+    /**
+     * Clears the cached NTP time. For use during tests to simulate when no NTP time is available.
+     *
+     * <p>This operation takes place in the calling thread rather than the service's handler thread.
+     */
+    void clearTimeForTests() {
+        mContext.enforceCallingPermission(
+                android.Manifest.permission.SET_TIME, "clear latest network time");
+
+        mTime.clearCachedTimeResult();
+
+        mLocalLog.log("clearTimeForTests");
+    }
+
+    /**
+     * Forces the service to refresh the NTP time.
+     *
+     * <p>This operation takes place in the calling thread rather than the service's handler thread.
+     * This method does not affect currently scheduled refreshes. If the NTP request is successful
+     * it will make an (asynchronously handled) suggestion to the time detector.
+     */
+    boolean forceRefreshForTests() {
+        mContext.enforceCallingPermission(
+                android.Manifest.permission.SET_TIME, "force network time refresh");
+
+        boolean success = mTime.forceRefresh();
+        mLocalLog.log("forceRefreshForTests: success=" + success);
+
+        if (success) {
+            makeNetworkTimeSuggestion(mTime.getCachedTimeResult(),
+                    "Origin: NetworkTimeUpdateService: forceRefreshForTests");
+        }
+
+        return success;
+    }
+
     private void onPollNetworkTime(int event) {
         // If we don't have any default network, don't bother.
         if (mDefaultNetwork == null) return;
@@ -193,12 +232,8 @@
             resetAlarm(mPollingIntervalMs
                     - cachedNtpResult.getAgeMillis(currentElapsedRealtimeMillis));
 
-            // Suggest the time to the time detector. It may choose use it to set the system clock.
-            TimestampedValue<Long> timeSignal = new TimestampedValue<>(
-                    cachedNtpResult.getElapsedRealtimeMillis(), cachedNtpResult.getTimeMillis());
-            NetworkTimeSuggestion timeSuggestion = new NetworkTimeSuggestion(timeSignal);
-            timeSuggestion.addDebugInfo("Origin: NetworkTimeUpdateService. event=" + event);
-            mTimeDetector.suggestNetworkTime(timeSuggestion);
+            makeNetworkTimeSuggestion(cachedNtpResult,
+                    "Origin: NetworkTimeUpdateService. event=" + event);
         } else {
             // No fresh fix; schedule retry
             mTryAgainCounter++;
@@ -217,6 +252,15 @@
         }
     }
 
+    /** Suggests the time to the time detector. It may choose use it to set the system clock. */
+    private void makeNetworkTimeSuggestion(TimeResult ntpResult, String debugInfo) {
+        TimestampedValue<Long> timeSignal = new TimestampedValue<>(
+                ntpResult.getElapsedRealtimeMillis(), ntpResult.getTimeMillis());
+        NetworkTimeSuggestion timeSuggestion = new NetworkTimeSuggestion(timeSignal);
+        timeSuggestion.addDebugInfo(debugInfo);
+        mTimeDetector.suggestNetworkTime(timeSuggestion);
+    }
+
     /**
      * Cancel old alarm and starts a new one for the specified interval.
      *
@@ -320,4 +364,11 @@
         mLocalLog.dump(fd, pw, args);
         pw.println();
     }
+
+    @Override
+    public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+            String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
+        new NetworkTimeUpdateServiceShellCommand(this).exec(
+                this, in, out, err, args, callback, resultReceiver);
+    }
 }
diff --git a/services/core/java/com/android/server/NetworkTimeUpdateServiceShellCommand.java b/services/core/java/com/android/server/NetworkTimeUpdateServiceShellCommand.java
new file mode 100644
index 0000000..dc93023
--- /dev/null
+++ b/services/core/java/com/android/server/NetworkTimeUpdateServiceShellCommand.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2022 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;
+
+import android.annotation.NonNull;
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+
+/** Implements the shell command interface for {@link NetworkTimeUpdateService}. */
+class NetworkTimeUpdateServiceShellCommand extends ShellCommand {
+
+    /**
+     * The name of the service.
+     */
+    private static final String SHELL_COMMAND_SERVICE_NAME = "network_time_update_service";
+
+    /**
+     * A shell command that clears the time signal received from the network.
+     */
+    private static final String SHELL_COMMAND_CLEAR_TIME = "clear_time";
+
+    /**
+     * A shell command that forces the time signal to be refreshed from the network.
+     */
+    private static final String SHELL_COMMAND_FORCE_REFRESH = "force_refresh";
+
+    @NonNull
+    private final NetworkTimeUpdateService mNetworkTimeUpdateService;
+
+    NetworkTimeUpdateServiceShellCommand(NetworkTimeUpdateService networkTimeUpdateService) {
+        mNetworkTimeUpdateService = Objects.requireNonNull(networkTimeUpdateService);
+    }
+
+    @Override
+    public int onCommand(String cmd) {
+        if (cmd == null) {
+            return handleDefaultCommands(cmd);
+        }
+
+        switch (cmd) {
+            case SHELL_COMMAND_CLEAR_TIME:
+                return runClearTime();
+            case SHELL_COMMAND_FORCE_REFRESH:
+                return runForceRefresh();
+            default: {
+                return handleDefaultCommands(cmd);
+            }
+        }
+    }
+
+    private int runClearTime() {
+        mNetworkTimeUpdateService.clearTimeForTests();
+        return 0;
+    }
+
+    private int runForceRefresh() {
+        boolean success = mNetworkTimeUpdateService.forceRefreshForTests();
+        getOutPrintWriter().println(success);
+        return 0;
+    }
+
+    @Override
+    public void onHelp() {
+        final PrintWriter pw = getOutPrintWriter();
+        pw.printf("Network Time Update Service (%s) commands:\n", SHELL_COMMAND_SERVICE_NAME);
+        pw.printf("  help\n");
+        pw.printf("    Print this help text.\n");
+        pw.printf("  %s\n", SHELL_COMMAND_CLEAR_TIME);
+        pw.printf("    Clears the latest time.\n");
+        pw.printf("  %s\n", SHELL_COMMAND_FORCE_REFRESH);
+        pw.printf("    Refreshes the latest time. Prints whether it was successful.\n");
+        pw.println();
+    }
+}
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index 4129feb..6ff8e36 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -21,14 +21,13 @@
 per-file *Battery* = file:/BATTERY_STATS_OWNERS
 per-file *BinaryTransparency* = file:/core/java/android/transparency/OWNERS
 per-file *Binder* = file:/core/java/com/android/internal/os/BINDER_OWNERS
-per-file *Bluetooth* = file:/core/java/android/bluetooth/OWNERS
 per-file *Gnss* = file:/services/core/java/com/android/server/location/OWNERS
 per-file **IpSec* = file:/services/core/java/com/android/server/net/OWNERS
 per-file **IpSec* = file:/services/core/java/com/android/server/vcn/OWNERS
 per-file *Location* = file:/services/core/java/com/android/server/location/OWNERS
 per-file *Network* = file:/services/core/java/com/android/server/net/OWNERS
 per-file *Storage* = file:/core/java/android/os/storage/OWNERS
-per-file *TimeUpdate* = file:/core/java/android/app/timezone/OWNERS
+per-file *TimeUpdate* = file:/services/core/java/com/android/server/timezonedetector/OWNERS
 per-file DynamicSystemService.java = file:/packages/DynamicSystemInstallationService/OWNERS
 per-file GestureLauncherService.java = file:platform/packages/apps/EmergencyInfo:/OWNERS
 per-file MmsServiceBroker.java = file:/telephony/OWNERS
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index c194527..d4764a6 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -4747,7 +4747,7 @@
     private int getMountModeInternal(int uid, String packageName) {
         try {
             // Get some easy cases out of the way first
-            if (Process.isIsolated(uid) || Process.isSupplemental(uid)) {
+            if (Process.isIsolated(uid) || Process.isSdkSandboxUid(uid)) {
                 return StorageManager.MOUNT_MODE_EXTERNAL_NONE;
             }
 
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 092172a..d4ad718 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -2721,7 +2721,7 @@
 
     int bindServiceLocked(IApplicationThread caller, IBinder token, Intent service,
             String resolvedType, final IServiceConnection connection, int flags,
-            String instanceName, boolean isSupplementalProcessService, int supplementedAppUid,
+            String instanceName, boolean isSdkSandboxService, int sdkSandboxClientAppUid,
             String callingPackage, final int userId)
             throws TransactionTooLargeException {
         if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "bindService: " + service
@@ -2807,7 +2807,7 @@
         final boolean allowInstant = (flags & Context.BIND_ALLOW_INSTANT) != 0;
 
         ServiceLookupResult res = retrieveServiceLocked(service, instanceName,
-                isSupplementalProcessService, supplementedAppUid, resolvedType, callingPackage,
+                isSdkSandboxService, sdkSandboxClientAppUid, resolvedType, callingPackage,
                 callingPid, callingUid, userId, true, callerFg, isBindExternal, allowInstant);
         if (res == null) {
             return 0;
@@ -3234,13 +3234,13 @@
     }
 
     private ServiceLookupResult retrieveServiceLocked(Intent service,
-            String instanceName, boolean isSupplementalProcessService, int supplementedAppUid,
+            String instanceName, boolean isSdkSandboxService, int sdkSandboxClientAppUid,
             String resolvedType,
             String callingPackage, int callingPid, int callingUid, int userId,
             boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal,
             boolean allowInstant) {
-        if (isSupplementalProcessService && instanceName == null) {
-            throw new IllegalArgumentException("No instanceName provided for supplemental process");
+        if (isSdkSandboxService && instanceName == null) {
+            throw new IllegalArgumentException("No instanceName provided for sdk sandbox process");
         }
 
         ServiceRecord r = null;
@@ -3319,13 +3319,13 @@
                 }
                 if (instanceName != null
                         && (sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) == 0
-                        && !isSupplementalProcessService) {
+                        && !isSdkSandboxService) {
                     throw new IllegalArgumentException("Can't use instance name '" + instanceName
-                            + "' with non-isolated non-supplemental service '" + sInfo.name + "'");
+                            + "' with non-isolated non-sdk sandbox service '" + sInfo.name + "'");
                 }
-                if (isSupplementalProcessService
+                if (isSdkSandboxService
                         && (sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0) {
-                    throw new IllegalArgumentException("Service cannot be both supplemental and "
+                    throw new IllegalArgumentException("Service cannot be both sdk sandbox and "
                             + "isolated");
                 }
 
@@ -3412,11 +3412,11 @@
                     final Intent.FilterComparison filter
                             = new Intent.FilterComparison(service.cloneFilter());
                     final ServiceRestarter res = new ServiceRestarter();
-                    String supplementalProcessName = isSupplementalProcessService ? instanceName
+                    String sdkSandboxProcessName = isSdkSandboxService ? instanceName
                                                                                   : null;
                     r = new ServiceRecord(mAm, className, name, definingPackageName,
                             definingUid, filter, sInfo, callingFromFg, res,
-                            supplementalProcessName, supplementedAppUid);
+                            sdkSandboxProcessName, sdkSandboxClientAppUid);
                     res.setService(r);
                     smap.mServicesByInstanceName.put(name, r);
                     smap.mServicesByIntent.put(filter, r);
@@ -4139,7 +4139,8 @@
 
         final boolean isolated = (r.serviceInfo.flags&ServiceInfo.FLAG_ISOLATED_PROCESS) != 0;
         final String procName = r.processName;
-        HostingRecord hostingRecord = new HostingRecord("service", r.instanceName);
+        HostingRecord hostingRecord = new HostingRecord("service", r.instanceName,
+                r.definingPackageName, r.definingUid, r.serviceInfo.processName);
         ProcessRecord app;
 
         if (!isolated) {
@@ -4177,11 +4178,12 @@
             app = r.isolationHostProc;
             if (WebViewZygote.isMultiprocessEnabled()
                     && r.serviceInfo.packageName.equals(WebViewZygote.getPackageName())) {
-                hostingRecord = HostingRecord.byWebviewZygote(r.instanceName);
+                hostingRecord = HostingRecord.byWebviewZygote(r.instanceName, r.definingPackageName,
+                        r.definingUid, r.serviceInfo.processName);
             }
             if ((r.serviceInfo.flags & ServiceInfo.FLAG_USE_APP_ZYGOTE) != 0) {
                 hostingRecord = HostingRecord.byAppZygote(r.instanceName, r.definingPackageName,
-                        r.definingUid);
+                        r.definingUid, r.serviceInfo.processName);
             }
         }
 
@@ -4190,9 +4192,9 @@
         if (app == null && !permissionsReviewRequired && !packageFrozen) {
             // TODO (chriswailes): Change the Zygote policy flags based on if the launch-for-service
             //  was initiated from a notification tap or not.
-            if (r.supplemental) {
-                final int uid = Process.toSupplementalUid(r.supplementedAppUid);
-                app = mAm.startSupplementalProcessLocked(procName, r.appInfo, true, intentFlags,
+            if (r.isSdkSandbox) {
+                final int uid = Process.toSdkSandboxUid(r.sdkSandboxClientAppUid);
+                app = mAm.startSdkSandboxProcessLocked(procName, r.appInfo, true, intentFlags,
                         hostingRecord, ZYGOTE_POLICY_FLAG_EMPTY, uid);
                 r.isolationHostProc = app;
             } else {
diff --git a/services/core/java/com/android/server/am/ActivityManagerLocal.java b/services/core/java/com/android/server/am/ActivityManagerLocal.java
index 535340b..3226a2b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerLocal.java
+++ b/services/core/java/com/android/server/am/ActivityManagerLocal.java
@@ -65,15 +65,15 @@
     void tempAllowWhileInUsePermissionInFgs(int uid, long durationMs);
 
     /**
-     * Binds to a supplemental process service, creating it if needed. You can through the arguments
+     * Binds to a sdk sandbox service, creating it if needed. You can through the arguments
      * here have the system bring up multiple concurrent processes hosting their own instance of
      * that service. The {@code processName} you provide here identifies the different instances.
      *
-     * @param service Identifies the supplemental process service to connect to. The Intent must
+     * @param service Identifies the sdk sandbox process service to connect to. The Intent must
      *        specify an explicit component name. This value cannot be null.
      * @param conn Receives information as the service is started and stopped.
      *        This must be a valid ServiceConnection object; it must not be null.
-     * @param userAppUid Uid of the app for which the supplemental process needs to be spawned.
+     * @param clientAppUid Uid of the app for which the sdk sandbox process needs to be spawned.
      * @param processName Unique identifier for the service instance. Each unique name here will
      *        result in a different service instance being created. Identifiers must only contain
      *        ASCII letters, digits, underscores, and periods.
@@ -86,7 +86,7 @@
      * @see Context#bindService(Intent, ServiceConnection, int)
      */
     @SuppressLint("RethrowRemoteException")
-    boolean bindSupplementalProcessService(@NonNull Intent service, @NonNull ServiceConnection conn,
-            int userAppUid, @NonNull String processName, @Context.BindServiceFlags int flags)
+    boolean bindSdkSandboxService(@NonNull Intent service, @NonNull ServiceConnection conn,
+            int clientAppUid, @NonNull String processName, @Context.BindServiceFlags int flags)
             throws RemoteException;
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 20a8509..47f9fd5 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2789,13 +2789,13 @@
     }
 
     @GuardedBy("this")
-    final ProcessRecord startSupplementalProcessLocked(String processName,
+    final ProcessRecord startSdkSandboxProcessLocked(String processName,
             ApplicationInfo info, boolean knownToBeDead, int intentFlags,
-            HostingRecord hostingRecord, int zygotePolicyFlags, int supplementalUid) {
+            HostingRecord hostingRecord, int zygotePolicyFlags, int sdkSandboxUid) {
         return mProcessList.startProcessLocked(processName, info, knownToBeDead, intentFlags,
                 hostingRecord, zygotePolicyFlags, false /* allowWhileBooting */,
                 false /* isolated */, 0 /* isolatedUid */,
-                true /* supplemental */, supplementalUid,
+                true /* isSdkSandbox */, sdkSandboxUid,
                 null /* ABI override */, null /* entryPoint */,
                 null /* entryPointArgs */, null /* crashHandler */);
     }
@@ -2807,7 +2807,7 @@
             boolean isolated) {
         return mProcessList.startProcessLocked(processName, info, knownToBeDead, intentFlags,
                 hostingRecord, zygotePolicyFlags, allowWhileBooting, isolated, 0 /* isolatedUid */,
-                false /* supplemental */, 0 /* supplementalUid */,
+                false /* isSdkSandbox */, 0 /* sdkSandboxClientdAppUid */,
                 null /* ABI override */, null /* entryPoint */,
                 null /* entryPointArgs */, null /* crashHandler */);
     }
@@ -12396,7 +12396,7 @@
 
     private int bindServiceInstance(IApplicationThread caller, IBinder token, Intent service,
             String resolvedType, IServiceConnection connection, int flags, String instanceName,
-            boolean isSupplementalProcessService, int supplementedAppUid, String callingPackage,
+            boolean isSdkSandboxService, int sdkSandboxClientdAppUid, String callingPackage,
             int userId)
             throws TransactionTooLargeException {
         enforceNotIsolatedCaller("bindService");
@@ -12410,7 +12410,7 @@
             throw new IllegalArgumentException("callingPackage cannot be null");
         }
 
-        if (isSupplementalProcessService && instanceName == null) {
+        if (isSdkSandboxService && instanceName == null) {
             throw new IllegalArgumentException("No instance name provided for isolated process");
         }
 
@@ -12426,10 +12426,19 @@
             }
         }
 
-        synchronized(this) {
-            return mServices.bindServiceLocked(caller, token, service, resolvedType, connection,
-                    flags, instanceName, isSupplementalProcessService, supplementedAppUid,
-                    callingPackage, userId);
+        try {
+            if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+                final ComponentName cn = service.getComponent();
+                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindService:"
+                        + (cn != null ? cn.toShortString() : service.getAction()));
+            }
+            synchronized (this) {
+                return mServices.bindServiceLocked(caller, token, service, resolvedType, connection,
+                        flags, instanceName, isSdkSandboxService, sdkSandboxClientdAppUid,
+                        callingPackage, userId);
+            }
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
         }
     }
 
@@ -13586,6 +13595,8 @@
                                                 intent.getIntExtra(Intent.EXTRA_UID, -1)),
                                                 false, true, true, false, fullUninstall, userId,
                                                 removed ? "pkg removed" : "pkg changed");
+                                        getPackageManagerInternal()
+                                                .onPackageProcessKilledForUninstall(ssp);
                                     } else {
                                         // Kill any app zygotes always, since they can't fork new
                                         // processes with references to the old code
@@ -16006,7 +16017,7 @@
         }
 
         @Override
-        public boolean bindSupplementalProcessService(Intent service, ServiceConnection conn,
+        public boolean bindSdkSandboxService(Intent service, ServiceConnection conn,
                 int userAppUid, String processName, int flags) throws RemoteException {
             if (service == null) {
                 throw new IllegalArgumentException("intent is null");
diff --git a/services/core/java/com/android/server/am/HostingRecord.java b/services/core/java/com/android/server/am/HostingRecord.java
index 6bb5def..bbf5861 100644
--- a/services/core/java/com/android/server/am/HostingRecord.java
+++ b/services/core/java/com/android/server/am/HostingRecord.java
@@ -56,19 +56,27 @@
     private final String mDefiningPackageName;
     private final int mDefiningUid;
     private final boolean mIsTopApp;
+    private final String mDefiningProcessName;
 
     public HostingRecord(String hostingType) {
         this(hostingType, null /* hostingName */, REGULAR_ZYGOTE, null /* definingPackageName */,
-                -1 /* mDefiningUid */, false /* isTopApp */);
+                -1 /* mDefiningUid */, false /* isTopApp */, null /* definingProcessName */);
     }
 
     public HostingRecord(String hostingType, ComponentName hostingName) {
         this(hostingType, hostingName, REGULAR_ZYGOTE);
     }
 
+    public HostingRecord(String hostingType, ComponentName hostingName, String definingPackageName,
+            int definingUid, String definingProcessName) {
+        this(hostingType, hostingName.toShortString(), REGULAR_ZYGOTE, definingPackageName,
+                definingUid, false /* isTopApp */, definingProcessName);
+    }
+
     public HostingRecord(String hostingType, ComponentName hostingName, boolean isTopApp) {
         this(hostingType, hostingName.toShortString(), REGULAR_ZYGOTE,
-                null /* definingPackageName */, -1 /* mDefiningUid */, isTopApp /* isTopApp */);
+                null /* definingPackageName */, -1 /* mDefiningUid */, isTopApp /* isTopApp */,
+                null /* definingProcessName */);
     }
 
     public HostingRecord(String hostingType, String hostingName) {
@@ -81,17 +89,19 @@
 
     private HostingRecord(String hostingType, String hostingName, int hostingZygote) {
         this(hostingType, hostingName, hostingZygote, null /* definingPackageName */,
-                -1 /* mDefiningUid */, false /* isTopApp */);
+                -1 /* mDefiningUid */, false /* isTopApp */, null /* definingProcessName */);
     }
 
     private HostingRecord(String hostingType, String hostingName, int hostingZygote,
-            String definingPackageName, int definingUid, boolean isTopApp) {
+            String definingPackageName, int definingUid, boolean isTopApp,
+            String definingProcessName) {
         mHostingType = hostingType;
         mHostingName = hostingName;
         mHostingZygote = hostingZygote;
         mDefiningPackageName = definingPackageName;
         mDefiningUid = definingUid;
         mIsTopApp = isTopApp;
+        mDefiningProcessName = definingProcessName;
     }
 
     public String getType() {
@@ -127,12 +137,24 @@
     }
 
     /**
+     * Returns the processName of the component we want to start as specified in the defining app's
+     * manifest.
+     *
+     * @return the processName of the process in the hosting application
+     */
+    public String getDefiningProcessName() {
+        return mDefiningProcessName;
+    }
+
+    /**
      * Creates a HostingRecord for a process that must spawn from the webview zygote
      * @param hostingName name of the component to be hosted in this process
      * @return The constructed HostingRecord
      */
-    public static HostingRecord byWebviewZygote(ComponentName hostingName) {
-        return new HostingRecord("", hostingName.toShortString(), WEBVIEW_ZYGOTE);
+    public static HostingRecord byWebviewZygote(ComponentName hostingName,
+            String definingPackageName, int definingUid, String definingProcessName) {
+        return new HostingRecord("", hostingName.toShortString(), WEBVIEW_ZYGOTE,
+                definingPackageName, definingUid, false /* isTopApp */, definingProcessName);
     }
 
     /**
@@ -143,9 +165,9 @@
      * @return The constructed HostingRecord
      */
     public static HostingRecord byAppZygote(ComponentName hostingName, String definingPackageName,
-            int definingUid) {
+            int definingUid, String definingProcessName) {
         return new HostingRecord("", hostingName.toShortString(), APP_ZYGOTE,
-                definingPackageName, definingUid, false /* isTopApp */);
+                definingPackageName, definingUid, false /* isTopApp */, definingProcessName);
     }
 
     /**
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 763bbee..2c2e7c4 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -70,7 +70,6 @@
 import android.app.IProcessObserver;
 import android.app.IUidObserver;
 import android.compat.annotation.ChangeId;
-import android.compat.annotation.Disabled;
 import android.compat.annotation.EnabledAfter;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -363,59 +362,6 @@
     private static final long LMKD_RECONNECT_DELAY_MS = 1000;
 
     /**
-     * Native heap allocations will now have a non-zero tag in the most significant byte.
-     * @see <a href="https://source.android.com/devices/tech/debug/tagged-pointers">Tagged
-     * Pointers</a>
-     */
-    @ChangeId
-    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
-    private static final long NATIVE_HEAP_POINTER_TAGGING = 135754954; // This is a bug id.
-
-    /**
-     * Native heap allocations in AppZygote process and its descendants will now have a
-     * non-zero tag in the most significant byte.
-     * @see <a href="https://source.android.com/devices/tech/debug/tagged-pointers">Tagged
-     * Pointers</a>
-     */
-    @ChangeId
-    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S)
-    private static final long NATIVE_HEAP_POINTER_TAGGING_APP_ZYGOTE = 207557677;
-
-    /**
-     * Enable asynchronous (ASYNC) memory tag checking in this process. This
-     * flag will only have an effect on hardware supporting the ARM Memory
-     * Tagging Extension (MTE).
-     */
-    @ChangeId
-    @Disabled
-    private static final long NATIVE_MEMTAG_ASYNC = 135772972; // This is a bug id.
-
-    /**
-     * Enable synchronous (SYNC) memory tag checking in this process. This flag
-     * will only have an effect on hardware supporting the ARM Memory Tagging
-     * Extension (MTE). If both NATIVE_MEMTAG_ASYNC and this option is selected,
-     * this option takes preference and MTE is enabled in SYNC mode.
-     */
-    @ChangeId
-    @Disabled
-    private static final long NATIVE_MEMTAG_SYNC = 177438394; // This is a bug id.
-
-    /**
-     * Enable automatic zero-initialization of native heap memory allocations.
-     */
-    @ChangeId
-    @Disabled
-    private static final long NATIVE_HEAP_ZERO_INIT = 178038272; // This is a bug id.
-
-    /**
-     * Enable sampled memory bug detection in the app.
-     * @see <a href="https://source.android.com/devices/tech/debug/gwp-asan">GWP-ASan</a>.
-     */
-    @ChangeId
-    @Disabled
-    private static final long GWP_ASAN = 135634846; // This is a bug id.
-
-    /**
      * Apps have no access to the private data directories of any other app, even if the other
      * app has made them world-readable.
      */
@@ -1681,136 +1627,6 @@
         return gidArray;
     }
 
-    private int memtagModeToZygoteMemtagLevel(int memtagMode) {
-        switch (memtagMode) {
-            case ApplicationInfo.MEMTAG_ASYNC:
-                return Zygote.MEMORY_TAG_LEVEL_ASYNC;
-            case ApplicationInfo.MEMTAG_SYNC:
-                return Zygote.MEMORY_TAG_LEVEL_SYNC;
-            default:
-                return Zygote.MEMORY_TAG_LEVEL_NONE;
-        }
-    }
-
-    // Returns the requested memory tagging level.
-    private int getRequestedMemtagLevel(ProcessRecord app) {
-        // Look at the process attribute first.
-        if (app.processInfo != null
-                && app.processInfo.memtagMode != ApplicationInfo.MEMTAG_DEFAULT) {
-            return memtagModeToZygoteMemtagLevel(app.processInfo.memtagMode);
-        }
-
-        // Then at the application attribute.
-        if (app.info.getMemtagMode() != ApplicationInfo.MEMTAG_DEFAULT) {
-            return memtagModeToZygoteMemtagLevel(app.info.getMemtagMode());
-        }
-
-        if (mPlatformCompat.isChangeEnabled(NATIVE_MEMTAG_SYNC, app.info)) {
-            return Zygote.MEMORY_TAG_LEVEL_SYNC;
-        }
-
-        if (mPlatformCompat.isChangeEnabled(NATIVE_MEMTAG_ASYNC, app.info)) {
-            return Zygote.MEMORY_TAG_LEVEL_ASYNC;
-        }
-
-        // Check to ensure the app hasn't explicitly opted-out of TBI via. the manifest attribute.
-        if (!app.info.allowsNativeHeapPointerTagging()) {
-            return Zygote.MEMORY_TAG_LEVEL_NONE;
-        }
-
-        String defaultLevel = SystemProperties.get("persist.arm64.memtag.app_default");
-        if ("sync".equals(defaultLevel)) {
-            return Zygote.MEMORY_TAG_LEVEL_SYNC;
-        } else if ("async".equals(defaultLevel)) {
-            return Zygote.MEMORY_TAG_LEVEL_ASYNC;
-        }
-
-        // Check to see that the compat feature for TBI is enabled.
-        if (mPlatformCompat.isChangeEnabled(NATIVE_HEAP_POINTER_TAGGING, app.info)) {
-            return Zygote.MEMORY_TAG_LEVEL_TBI;
-        }
-
-        return Zygote.MEMORY_TAG_LEVEL_NONE;
-    }
-
-    private int decideTaggingLevel(ProcessRecord app) {
-        // Get the desired tagging level (app manifest + compat features).
-        int level = getRequestedMemtagLevel(app);
-
-        // Take into account the hardware capabilities.
-        if (Zygote.nativeSupportsMemoryTagging()) {
-            // MTE devices can not do TBI, because the Zygote process already has live MTE
-            // allocations. Downgrade TBI to NONE.
-            if (level == Zygote.MEMORY_TAG_LEVEL_TBI) {
-                level = Zygote.MEMORY_TAG_LEVEL_NONE;
-            }
-        } else if (Zygote.nativeSupportsTaggedPointers()) {
-            // TBI-but-not-MTE devices downgrade MTE modes to TBI.
-            // The idea is that if an app opts into full hardware tagging (MTE), it must be ok with
-            // the "fake" pointer tagging (TBI).
-            if (level == Zygote.MEMORY_TAG_LEVEL_ASYNC || level == Zygote.MEMORY_TAG_LEVEL_SYNC) {
-                level = Zygote.MEMORY_TAG_LEVEL_TBI;
-            }
-        } else {
-            // Otherwise disable all tagging.
-            level = Zygote.MEMORY_TAG_LEVEL_NONE;
-        }
-
-        return level;
-    }
-
-    private int decideTaggingLevelForAppZygote(ProcessRecord app) {
-        int level = decideTaggingLevel(app);
-        // TBI ("fake" pointer tagging) in AppZygote is controlled by a separate compat feature.
-        if (!mPlatformCompat.isChangeEnabled(NATIVE_HEAP_POINTER_TAGGING_APP_ZYGOTE, app.info)
-                && level == Zygote.MEMORY_TAG_LEVEL_TBI) {
-            level = Zygote.MEMORY_TAG_LEVEL_NONE;
-        }
-        return level;
-    }
-
-    private int decideGwpAsanLevel(ProcessRecord app) {
-        // Look at the process attribute first.
-       if (app.processInfo != null
-                && app.processInfo.gwpAsanMode != ApplicationInfo.GWP_ASAN_DEFAULT) {
-            return app.processInfo.gwpAsanMode == ApplicationInfo.GWP_ASAN_ALWAYS
-                    ? Zygote.GWP_ASAN_LEVEL_ALWAYS
-                    : Zygote.GWP_ASAN_LEVEL_NEVER;
-        }
-        // Then at the application attribute.
-        if (app.info.getGwpAsanMode() != ApplicationInfo.GWP_ASAN_DEFAULT) {
-            return app.info.getGwpAsanMode() == ApplicationInfo.GWP_ASAN_ALWAYS
-                    ? Zygote.GWP_ASAN_LEVEL_ALWAYS
-                    : Zygote.GWP_ASAN_LEVEL_NEVER;
-        }
-        // If the app does not specify gwpAsanMode, the default behavior is lottery among the
-        // system apps, and disabled for user apps, unless overwritten by the compat feature.
-        if (mPlatformCompat.isChangeEnabled(GWP_ASAN, app.info)) {
-            return Zygote.GWP_ASAN_LEVEL_ALWAYS;
-        }
-        if ((app.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
-            return Zygote.GWP_ASAN_LEVEL_LOTTERY;
-        }
-        return Zygote.GWP_ASAN_LEVEL_NEVER;
-    }
-
-    private boolean enableNativeHeapZeroInit(ProcessRecord app) {
-        // Look at the process attribute first.
-        if (app.processInfo != null
-                && app.processInfo.nativeHeapZeroInitialized != ApplicationInfo.ZEROINIT_DEFAULT) {
-            return app.processInfo.nativeHeapZeroInitialized == ApplicationInfo.ZEROINIT_ENABLED;
-        }
-        // Then at the application attribute.
-        if (app.info.getNativeHeapZeroInitialized() != ApplicationInfo.ZEROINIT_DEFAULT) {
-            return app.info.getNativeHeapZeroInitialized() == ApplicationInfo.ZEROINIT_ENABLED;
-        }
-        // Compat feature last.
-        if (mPlatformCompat.isChangeEnabled(NATIVE_HEAP_ZERO_INIT, app.info)) {
-            return true;
-        }
-        return false;
-    }
-
     /**
      * @return {@code true} if process start is successful, false otherwise.
      */
@@ -1992,8 +1808,6 @@
                 runtimeFlags |= Zygote.USE_APP_IMAGE_STARTUP_CACHE;
             }
 
-            runtimeFlags |= decideGwpAsanLevel(app);
-
             String invokeWith = null;
             if ((app.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
                 // Debuggable apps may include a wrapper script with their library directory.
@@ -2024,23 +1838,21 @@
             app.setRequiredAbi(requiredAbi);
             app.setInstructionSet(instructionSet);
 
-            // If instructionSet is non-null, this indicates that the system_server is spawning a
-            // process with an ISA that may be different from its own. System (kernel and hardware)
-            // compatibility for these features is checked in the decideTaggingLevel in the
-            // system_server process (not the child process). As both MTE and TBI are only supported
-            // in aarch64, we can simply ensure that the new process is also aarch64. This prevents
-            // the mismatch where a 64-bit system server spawns a 32-bit child that thinks it should
-            // enable some tagging variant. Theoretically, a 32-bit system server could exist that
-            // spawns 64-bit processes, in which case the new process won't get any tagging. This is
-            // fine as we haven't seen this configuration in practice, and we can reasonable assume
-            // that if tagging is desired, the system server will be 64-bit.
-            if (instructionSet == null || instructionSet.equals("arm64")) {
-                runtimeFlags |= decideTaggingLevel(app);
+            // If this was an external service, the package name and uid in the passed in
+            // ApplicationInfo have been changed to match those of the calling package;
+            // that will incorrectly apply compat feature overrides for the calling package instead
+            // of the defining one.
+            ApplicationInfo definingAppInfo;
+            if (hostingRecord.getDefiningPackageName() != null) {
+                definingAppInfo = new ApplicationInfo(app.info);
+                definingAppInfo.packageName = hostingRecord.getDefiningPackageName();
+                definingAppInfo.uid = uid;
+            } else {
+                definingAppInfo = app.info;
             }
 
-            if (enableNativeHeapZeroInit(app)) {
-                runtimeFlags |= Zygote.NATIVE_HEAP_ZERO_INIT;
-            }
+            runtimeFlags |= Zygote.getMemorySafetyRuntimeFlags(
+                    definingAppInfo, app.processInfo, instructionSet, mPlatformCompat);
 
             // the per-user SELinux context must be set
             if (TextUtils.isEmpty(app.info.seInfoUser)) {
@@ -2299,8 +2111,7 @@
                 // not the calling one.
                 appInfo.packageName = app.getHostingRecord().getDefiningPackageName();
                 appInfo.uid = uid;
-                int runtimeFlags = decideTaggingLevelForAppZygote(app);
-                appZygote = new AppZygote(appInfo, uid, firstUid, lastUid, runtimeFlags);
+                appZygote = new AppZygote(appInfo, app.processInfo, uid, firstUid, lastUid);
                 mAppZygotes.put(app.info.processName, uid, appZygote);
                 zygoteProcessList = new ArrayList<ProcessRecord>();
                 mAppZygoteProcesses.put(appZygote, zygoteProcessList);
@@ -2532,7 +2343,7 @@
     ProcessRecord startProcessLocked(String processName, ApplicationInfo info,
             boolean knownToBeDead, int intentFlags, HostingRecord hostingRecord,
             int zygotePolicyFlags, boolean allowWhileBooting, boolean isolated, int isolatedUid,
-            boolean supplemental, int supplementalUid,
+            boolean isSdkSandbox, int sdkSandboxUid,
             String abiOverride, String entryPoint, String[] entryPointArgs, Runnable crashHandler) {
         long startTime = SystemClock.uptimeMillis();
         ProcessRecord app;
@@ -2626,8 +2437,8 @@
 
         if (app == null) {
             checkSlow(startTime, "startProcess: creating new process record");
-            app = newProcessRecordLocked(info, processName, isolated, isolatedUid, supplemental,
-                    supplementalUid, hostingRecord);
+            app = newProcessRecordLocked(info, processName, isolated, isolatedUid, isSdkSandbox,
+                    sdkSandboxUid, hostingRecord);
             if (app == null) {
                 Slog.w(TAG, "Failed making new process record for "
                         + processName + "/" + info.uid + " isolated=" + isolated);
@@ -3122,13 +2933,13 @@
 
     @GuardedBy("mService")
     ProcessRecord newProcessRecordLocked(ApplicationInfo info, String customProcess,
-            boolean isolated, int isolatedUid, boolean supplemental, int supplementalUid,
+            boolean isolated, int isolatedUid, boolean isSdkSandbox, int sdkSandboxUid,
             HostingRecord hostingRecord) {
         String proc = customProcess != null ? customProcess : info.processName;
         final int userId = UserHandle.getUserId(info.uid);
         int uid = info.uid;
-        if (supplemental) {
-            uid = supplementalUid;
+        if (isSdkSandbox) {
+            uid = sdkSandboxUid;
         }
         if (isolated) {
             if (isolatedUid == 0) {
@@ -3158,7 +2969,8 @@
             FrameworkStatsLog.write(FrameworkStatsLog.ISOLATED_UID_CHANGED, info.uid, uid,
                     FrameworkStatsLog.ISOLATED_UID_CHANGED__EVENT__CREATED);
         }
-        final ProcessRecord r = new ProcessRecord(mService, info, proc, uid);
+        final ProcessRecord r = new ProcessRecord(mService, info, proc, uid,
+                hostingRecord.getDefiningUid(), hostingRecord.getDefiningProcessName());
         final ProcessStateRecord state = r.mState;
 
         if (!mService.mBooted && !mService.mBooting
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index be187e2..7672d10 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -492,24 +492,33 @@
 
     ProcessRecord(ActivityManagerService _service, ApplicationInfo _info, String _processName,
             int _uid) {
+        this(_service, _info, _processName, _uid, -1, null);
+    }
+
+    ProcessRecord(ActivityManagerService _service, ApplicationInfo _info, String _processName,
+            int _uid, int _definingUid, String _definingProcessName) {
         mService = _service;
         mProcLock = _service.mProcLock;
         info = _info;
         ProcessInfo procInfo = null;
         if (_service.mPackageManagerInt != null) {
-            ArrayMap<String, ProcessInfo> processes =
-                    _service.mPackageManagerInt.getProcessesForUid(_uid);
-            if (processes != null) {
-                procInfo = processes.get(_processName);
-                if (procInfo != null && procInfo.deniedPermissions == null
-                        && procInfo.gwpAsanMode == ApplicationInfo.GWP_ASAN_DEFAULT
-                        && procInfo.memtagMode == ApplicationInfo.MEMTAG_DEFAULT
-                        && procInfo.nativeHeapZeroInitialized == ApplicationInfo.ZEROINIT_DEFAULT) {
-                    // If this process hasn't asked for permissions to be denied, or for a
-                    // non-default GwpAsan mode, or any other non-default setting, then we don't
-                    // care about it.
-                    procInfo = null;
-                }
+            if (_definingUid > 0) {
+                ArrayMap<String, ProcessInfo> processes =
+                        _service.mPackageManagerInt.getProcessesForUid(_definingUid);
+                if (processes != null) procInfo = processes.get(_definingProcessName);
+            } else {
+                ArrayMap<String, ProcessInfo> processes =
+                        _service.mPackageManagerInt.getProcessesForUid(_uid);
+                if (processes != null) procInfo = processes.get(_processName);
+            }
+            if (procInfo != null && procInfo.deniedPermissions == null
+                    && procInfo.gwpAsanMode == ApplicationInfo.GWP_ASAN_DEFAULT
+                    && procInfo.memtagMode == ApplicationInfo.MEMTAG_DEFAULT
+                    && procInfo.nativeHeapZeroInitialized == ApplicationInfo.ZEROINIT_DEFAULT) {
+                // If this process hasn't asked for permissions to be denied, or for a
+                // non-default GwpAsan mode, or any other non-default setting, then we don't
+                // care about it.
+                procInfo = null;
             }
         }
         processInfo = procInfo;
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index bf2876f..c53d4d6 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -94,8 +94,8 @@
     final boolean exported; // from ServiceInfo.exported
     final Runnable restarter; // used to schedule retries of starting the service
     final long createRealTime;  // when this service was created
-    final boolean supplemental; // whether this is a supplemental service
-    final int supplementedAppUid; // the app uid for which this supplemental service is running
+    final boolean isSdkSandbox; // whether this is a sdk sandbox service
+    final int sdkSandboxClientAppUid; // the app uid for which this sdk sandbox service is running
     final ArrayMap<Intent.FilterComparison, IntentBindRecord> bindings
             = new ArrayMap<Intent.FilterComparison, IntentBindRecord>();
                             // All active bindings to the service.
@@ -105,7 +105,7 @@
 
     ProcessRecord app;      // where this service is running or null.
     ProcessRecord isolationHostProc; // process which we've started for this service (used for
-                                     // isolated and supplemental processes)
+                                     // isolated and sdk sandbox processes)
     ServiceState tracker; // tracking service execution, may be null
     ServiceState restartTracker; // tracking service restart
     boolean allowlistManager; // any bindings to this service have BIND_ALLOW_WHITELIST_MANAGEMENT?
@@ -579,7 +579,7 @@
     ServiceRecord(ActivityManagerService ams, ComponentName name,
             ComponentName instanceName, String definingPackageName, int definingUid,
             Intent.FilterComparison intent, ServiceInfo sInfo, boolean callerIsFg,
-            Runnable restarter, String supplementalProcessName, int supplementedAppUid) {
+            Runnable restarter, String sdkSandboxProcessName, int sdkSandboxClientAppUid) {
         this.ams = ams;
         this.name = name;
         this.instanceName = instanceName;
@@ -590,12 +590,12 @@
         serviceInfo = sInfo;
         appInfo = sInfo.applicationInfo;
         packageName = sInfo.applicationInfo.packageName;
-        supplemental = supplementalProcessName != null;
-        this.supplementedAppUid = supplementedAppUid;
+        isSdkSandbox = sdkSandboxProcessName != null;
+        this.sdkSandboxClientAppUid = sdkSandboxClientAppUid;
         if ((sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0) {
             processName = sInfo.processName + ":" + instanceName.getClassName();
-        } else if (supplementalProcessName != null) {
-            processName = supplementalProcessName;
+        } else if (sdkSandboxProcessName != null) {
+            processName = sdkSandboxProcessName;
         } else {
             processName = sInfo.processName;
         }
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index b6801fb..1095cf0 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -2613,7 +2613,7 @@
         if (getStartedUserState(userId) == null) {
             return false;
         }
-        if (!getUserInfo(userId).isManagedProfile()) {
+        if (!mInjector.getUserManager().isCredentialSharedWithParent(userId)) {
             return false;
         }
         if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)) {
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index 0edbea0..3efd8ad 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -1320,15 +1320,26 @@
         // Make sure after resetting the game mode is still supported.
         // If not, set the game mode to standard
         int gameMode = getGameMode(packageName, userId);
-        int newGameMode = gameMode;
 
         GamePackageConfiguration config = null;
         synchronized (mOverrideConfigLock) {
             config = mOverrideConfigs.get(packageName);
         }
-        synchronized (mDeviceConfigLock) {
-            config = mConfigs.get(packageName);
+        if (config == null) {
+            synchronized (mDeviceConfigLock) {
+                config = mConfigs.get(packageName);
+            }
         }
+        final int newGameMode = getNewGameMode(gameMode, config);
+        if (gameMode != newGameMode) {
+            setGameMode(packageName, GameManager.GAME_MODE_STANDARD, userId);
+            return;
+        }
+        setGameMode(packageName, gameMode, userId);
+    }
+
+    private int getNewGameMode(int gameMode, GamePackageConfiguration config) {
+        int newGameMode = gameMode;
         if (config != null) {
             int modesBitfield = config.getAvailableGameModesBitfield();
             // Remove UNSUPPORTED to simplify the logic here, since we really just
@@ -1350,11 +1361,7 @@
             // UNSUPPORTED, then set to UNSUPPORTED
             newGameMode = GameManager.GAME_MODE_UNSUPPORTED;
         }
-        if (gameMode != newGameMode) {
-            setGameMode(packageName, GameManager.GAME_MODE_STANDARD, userId);
-            return;
-        }
-        setGameMode(packageName, gameMode, userId);
+        return newGameMode;
     }
 
     /**
@@ -1412,7 +1419,6 @@
             }
             for (final String packageName : packageNames) {
                 int gameMode = getGameMode(packageName, userId);
-                int newGameMode = gameMode;
                 // Make sure the user settings and package configs don't conflict.
                 // I.e. the user setting is set to a mode that no longer available due to
                 // config/manifest changes.
@@ -1421,27 +1427,7 @@
                 synchronized (mDeviceConfigLock) {
                     config = mConfigs.get(packageName);
                 }
-                if (config != null) {
-                    int modesBitfield = config.getAvailableGameModesBitfield();
-                    // Remove UNSUPPORTED to simplify the logic here, since we really just
-                    // want to check if we support selectable game modes
-                    modesBitfield &= ~modeToBitmask(GameManager.GAME_MODE_UNSUPPORTED);
-                    if (!bitFieldContainsModeBitmask(modesBitfield, gameMode)) {
-                        if (bitFieldContainsModeBitmask(modesBitfield,
-                                GameManager.GAME_MODE_STANDARD)) {
-                            // If the current set mode isn't supported,
-                            // but we support STANDARD, then set the mode to STANDARD.
-                            newGameMode = GameManager.GAME_MODE_STANDARD;
-                        } else {
-                            // If we don't support any game modes, then set to UNSUPPORTED
-                            newGameMode = GameManager.GAME_MODE_UNSUPPORTED;
-                        }
-                    }
-                } else if (gameMode != GameManager.GAME_MODE_UNSUPPORTED) {
-                    // If we have no config for the package, but the configured mode is not
-                    // UNSUPPORTED, then set to UNSUPPORTED
-                    newGameMode = GameManager.GAME_MODE_UNSUPPORTED;
-                }
+                final int newGameMode = getNewGameMode(gameMode, config);
                 if (newGameMode != gameMode) {
                     setGameMode(packageName, newGameMode, userId);
                 }
diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
index e8d9dad..e4edf4e 100644
--- a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
+++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
@@ -50,6 +50,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.infra.AndroidFuture;
 import com.android.internal.infra.ServiceConnector;
+import com.android.internal.infra.ServiceConnector.ServiceLifecycleCallbacks;
 import com.android.server.wm.WindowManagerInternal;
 import com.android.server.wm.WindowManagerInternal.TaskSystemBarsListener;
 import com.android.server.wm.WindowManagerService;
@@ -64,6 +65,40 @@
     private static final int CREATE_GAME_SESSION_TIMEOUT_MS = 10_000;
     private static final boolean DEBUG = false;
 
+    private final ServiceLifecycleCallbacks<IGameService> mGameServiceLifecycleCallbacks =
+            new ServiceLifecycleCallbacks<IGameService>() {
+                @Override
+                public void onConnected(@NonNull IGameService service) {
+                    try {
+                        service.connected(mGameServiceController);
+                    } catch (RemoteException ex) {
+                        Slog.w(TAG, "Failed to send connected event", ex);
+                    }
+                }
+
+                @Override
+                public void onDisconnected(@NonNull IGameService service) {
+                    try {
+                        service.disconnected();
+                    } catch (RemoteException ex) {
+                        Slog.w(TAG, "Failed to send disconnected event", ex);
+                    }
+                }
+            };
+
+    private final ServiceLifecycleCallbacks<IGameSessionService>
+            mGameSessionServiceLifecycleCallbacks =
+            new ServiceLifecycleCallbacks<IGameSessionService>() {
+                @Override
+                public void onBinderDied() {
+                    mBackgroundExecutor.execute(() -> {
+                        synchronized (mLock) {
+                            destroyAndClearAllGameSessionsLocked();
+                        }
+                    });
+                }
+            };
+
     private final TaskSystemBarsListener mTaskSystemBarsVisibilityListener =
             new TaskSystemBarsListener() {
                 @Override
@@ -206,11 +241,11 @@
         }
         mIsRunning = true;
 
-        // TODO(b/204503192): In cases where the connection to the game service fails retry with
-        //  back off mechanism.
-        AndroidFuture<Void> unusedPostConnectedFuture = mGameServiceConnector.post(gameService -> {
-            gameService.connected(mGameServiceController);
-        });
+        mGameServiceConnector.setServiceLifecycleCallbacks(mGameServiceLifecycleCallbacks);
+        mGameSessionServiceConnector.setServiceLifecycleCallbacks(
+                mGameSessionServiceLifecycleCallbacks);
+
+        AndroidFuture<?> unusedConnectFuture = mGameServiceConnector.connect();
 
         try {
             mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
@@ -237,19 +272,14 @@
         mWindowManagerInternal.unregisterTaskSystemBarsListener(
                 mTaskSystemBarsVisibilityListener);
 
-        for (GameSessionRecord gameSessionRecord : mGameSessions.values()) {
-            destroyGameSessionFromRecordLocked(gameSessionRecord);
-        }
-        mGameSessions.clear();
+        destroyAndClearAllGameSessionsLocked();
 
-        // TODO(b/204503192): It is possible that the game service is disconnected. In this
-        //  case we should avoid rebinding just to shut it down again.
-        mGameServiceConnector.post(gameService -> {
-            gameService.disconnected();
-        }).whenComplete((result, t) -> {
-            mGameServiceConnector.unbind();
-        });
+        mGameServiceConnector.unbind();
         mGameSessionServiceConnector.unbind();
+
+        mGameServiceConnector.setServiceLifecycleCallbacks(null);
+        mGameSessionServiceConnector.setServiceLifecycleCallbacks(null);
+
     }
 
     private void onTaskCreated(int taskId, @NonNull ComponentName componentName) {
@@ -488,6 +518,14 @@
                         createGameSessionResult.getSurfacePackage()));
     }
 
+    @GuardedBy("mLock")
+    private void destroyAndClearAllGameSessionsLocked() {
+        for (GameSessionRecord gameSessionRecord : mGameSessions.values()) {
+            destroyGameSessionFromRecordLocked(gameSessionRecord);
+        }
+        mGameSessions.clear();
+    }
+
     private void destroyGameSessionDuringAttach(
             int taskId,
             CreateGameSessionResult createGameSessionResult) {
@@ -544,9 +582,7 @@
                 Slog.d(TAG, "No active game sessions. Disconnecting GameSessionService");
             }
 
-            if (mGameSessionServiceConnector != null) {
-                mGameSessionServiceConnector.unbind();
-            }
+            mGameSessionServiceConnector.unbind();
         }
     }
 
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index e2d00f7..ef7c93d 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -113,7 +113,6 @@
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.PermissionInfo;
 import android.content.pm.UserInfo;
-import com.android.server.pm.pkg.component.ParsedAttribution;
 import android.database.ContentObserver;
 import android.hardware.camera2.CameraDevice.CAMERA_AUDIO_RESTRICTION;
 import android.net.Uri;
@@ -178,6 +177,7 @@
 import com.android.server.SystemServiceManager;
 import com.android.server.pm.PackageList;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
+import com.android.server.pm.pkg.component.ParsedAttribution;
 
 import dalvik.annotation.optimization.NeverCompile;
 
@@ -4551,15 +4551,16 @@
             return new PackageVerificationResult(null,
                     /* isAttributionTagValid */ true);
         }
-        if (Process.isSupplemental(uid)) {
-            // Supplemental processes run in their own UID range, but their associated
-            // UID for checks should always be the UID of the supplemental package.
+        if (Process.isSdkSandboxUid(uid)) {
+            // SDK sandbox processes run in their own UID range, but their associated
+            // UID for checks should always be the UID of the package implementing SDK sandbox
+            // service.
             // TODO: We will need to modify the callers of this function instead, so
             // modifications and checks against the app ops state are done with the
             // correct UID.
             try {
                 final PackageManager pm = mContext.getPackageManager();
-                final String supplementalPackageName = pm.getSupplementalProcessPackageName();
+                final String supplementalPackageName = pm.getSdkSandboxPackageName();
                 if (Objects.equals(packageName, supplementalPackageName)) {
                     int supplementalAppId = pm.getPackageUid(supplementalPackageName,
                             PackageManager.PackageInfoFlags.of(0));
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index ff7557a..270a61b 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1565,6 +1565,13 @@
 
     private AtomicBoolean mMusicMuted = new AtomicBoolean(false);
 
+    private static <T> boolean hasIntersection(Set<T> a, Set<T> b) {
+        for (T e : a) {
+            if (b.contains(e)) return true;
+        }
+        return false;
+    }
+
     boolean messageMutesMusic(int message) {
         if (message == 0) {
             return false;
@@ -1574,8 +1581,8 @@
                 || message == MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT
                 || message == MSG_L_A2DP_DEVICE_CONFIG_CHANGE)
                 && AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)
-                && mDeviceInventory.DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.contains(
-                        mAudioService.getDevicesForStream(AudioSystem.STREAM_MUSIC))) {
+                && hasIntersection(mDeviceInventory.DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET,
+                        mAudioService.getDeviceSetForStream(AudioSystem.STREAM_MUSIC))) {
             return false;
         }
         return true;
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 0ff8a93..82476d8 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -195,6 +195,7 @@
 import java.util.NoSuchElementException;
 import java.util.Objects;
 import java.util.Set;
+import java.util.TreeSet;
 import java.util.UUID;
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -1822,6 +1823,7 @@
         }
 
         // Check if device to be updated is routed for the given audio stream
+        // This may include devices such as SPEAKER_SAFE.
         List<AudioDeviceAttributes> devicesForAttributes = getDevicesForAttributesInt(
                 new AudioAttributes.Builder().setInternalLegacyStreamType(streamType).build(),
                 true /* forVolume */);
@@ -3693,8 +3695,7 @@
 
         int streamType = getBluetoothContextualVolumeStream(newMode);
 
-        final Set<Integer> deviceTypes = AudioSystem.generateAudioDeviceTypesSet(
-                mAudioSystem.getDevicesForStream(streamType));
+        final Set<Integer> deviceTypes = getDeviceSetForStreamDirect(streamType);
         final Set<Integer> absVolumeMultiModeCaseDevices = AudioSystem.intersectionAudioDeviceTypes(
                 mAbsVolumeMultiModeCaseDevices, deviceTypes);
         if (absVolumeMultiModeCaseDevices.isEmpty()) {
@@ -6258,55 +6259,107 @@
         }
     }
 
-    /** only public for mocking/spying, do not call outside of AudioService */
+    /**
+     * Returns device associated with the stream volume.
+     *
+     * Only public for mocking/spying, do not call outside of AudioService.
+     * Device volume aliasing means DEVICE_OUT_SPEAKER may be returned for
+     * DEVICE_OUT_SPEAKER_SAFE.
+     */
     @VisibleForTesting
     public int getDeviceForStream(int stream) {
-        int device = getDevicesForStreamInt(stream);
-        if ((device & (device - 1)) != 0) {
+        return selectOneAudioDevice(getDeviceSetForStream(stream));
+    }
+
+    /*
+     * Must match native apm_extract_one_audio_device() used in getDeviceForVolume()
+     * or the wrong device volume may be adjusted.
+     */
+    private int selectOneAudioDevice(Set<Integer> deviceSet) {
+        if (deviceSet.isEmpty()) {
+            return AudioSystem.DEVICE_NONE;
+        } else if (deviceSet.size() == 1) {
+            return deviceSet.iterator().next();
+        } else {
             // Multiple device selection is either:
             //  - speaker + one other device: give priority to speaker in this case.
             //  - one A2DP device + another device: happens with duplicated output. In this case
             // retain the device on the A2DP output as the other must not correspond to an active
             // selection if not the speaker.
             //  - HDMI-CEC system audio mode only output: give priority to available item in order.
-            // FIXME: Haven't applied audio device type refactor to this API
-            //  as it is going to be deprecated.
-            if ((device & AudioSystem.DEVICE_OUT_SPEAKER) != 0) {
-                device = AudioSystem.DEVICE_OUT_SPEAKER;
-            } else if ((device & AudioSystem.DEVICE_OUT_HDMI_ARC) != 0) {
-                // FIXME(b/184944421): DEVICE_OUT_HDMI_EARC has two bits set,
-                // so it must be handled correctly as it aliases
-                // with DEVICE_OUT_HDMI_ARC | DEVICE_OUT_EARPIECE.
-                device = AudioSystem.DEVICE_OUT_HDMI_ARC;
-            } else if ((device & AudioSystem.DEVICE_OUT_SPDIF) != 0) {
-                device = AudioSystem.DEVICE_OUT_SPDIF;
-            } else if ((device & AudioSystem.DEVICE_OUT_AUX_LINE) != 0) {
-                device = AudioSystem.DEVICE_OUT_AUX_LINE;
+
+            if (deviceSet.contains(AudioSystem.DEVICE_OUT_SPEAKER)) {
+                return AudioSystem.DEVICE_OUT_SPEAKER;
+            } else if (deviceSet.contains(AudioSystem.DEVICE_OUT_SPEAKER_SAFE)) {
+                // Note: DEVICE_OUT_SPEAKER_SAFE not present in getDeviceSetForStreamDirect
+                return AudioSystem.DEVICE_OUT_SPEAKER_SAFE;
+            } else if (deviceSet.contains(AudioSystem.DEVICE_OUT_HDMI_ARC)) {
+                return AudioSystem.DEVICE_OUT_HDMI_ARC;
+            } else if (deviceSet.contains(AudioSystem.DEVICE_OUT_HDMI_EARC)) {
+                return AudioSystem.DEVICE_OUT_HDMI_EARC;
+            } else if (deviceSet.contains(AudioSystem.DEVICE_OUT_AUX_LINE)) {
+                return AudioSystem.DEVICE_OUT_AUX_LINE;
+            } else if (deviceSet.contains(AudioSystem.DEVICE_OUT_SPDIF)) {
+                return AudioSystem.DEVICE_OUT_SPDIF;
             } else {
-                for (int deviceType : AudioSystem.DEVICE_OUT_ALL_A2DP_SET) {
-                    if ((deviceType & device) == deviceType) {
+                // At this point, deviceSet should contain exactly one A2DP device;
+                // regardless, return the first A2DP device in numeric order.
+                // If there is no A2DP device, this falls through to log an error.
+                for (int deviceType : deviceSet) {
+                    if (AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(deviceType)) {
                         return deviceType;
                     }
                 }
             }
         }
-        return device;
+        Log.w(TAG, "selectOneAudioDevice returning DEVICE_NONE from invalid device combination "
+                + AudioSystem.deviceSetToString(deviceSet));
+        return AudioSystem.DEVICE_NONE;
     }
 
     /**
      * @see AudioManager#getDevicesForStream(int)
+     * @deprecated on {@link android.os.Build.VERSION_CODES#T} as new devices
+     *              will have multi-bit device types since S.
+     *              Use {@link #getDevicesForAttributes()} instead.
      */
-    public int getDevicesForStream(int streamType) {
+    @Override
+    @Deprecated
+    public int getDeviceMaskForStream(int streamType) {
         ensureValidStreamType(streamType);
+        // no permission required
         final long token = Binder.clearCallingIdentity();
         try {
-            return mAudioSystem.getDevicesForStream(streamType);
+            return AudioSystem.getDeviceMaskFromSet(
+                    getDeviceSetForStreamDirect(streamType));
         } finally {
             Binder.restoreCallingIdentity(token);
         }
     }
 
-    private int getDevicesForStreamInt(int stream) {
+    /**
+     * Returns the devices associated with a stream type.
+     *
+     * SPEAKER_SAFE will alias to SPEAKER.
+     */
+    @NonNull
+    private Set<Integer> getDeviceSetForStreamDirect(int stream) {
+        final AudioAttributes attr =
+                AudioProductStrategy.getAudioAttributesForStrategyWithLegacyStreamType(stream);
+        Set<Integer> deviceSet =
+                AudioSystem.generateAudioDeviceTypesSet(
+                        getDevicesForAttributesInt(attr, true /* forVolume */));
+        return deviceSet;
+    }
+
+    /**
+     * Returns a reference to the list of devices for the stream, do not modify.
+     *
+     * The device returned may be aliased to the actual device whose volume curve
+     * will be used.  For example DEVICE_OUT_SPEAKER_SAFE aliases to DEVICE_OUT_SPEAKER.
+     */
+    @NonNull
+    public Set<Integer> getDeviceSetForStream(int stream) {
         ensureValidStreamType(stream);
         synchronized (VolumeStreamState.class) {
             return mStreamStates[stream].observeDevicesForStream_syncVSS(true);
@@ -6318,11 +6371,10 @@
             synchronized (VolumeStreamState.class) {
                 for (int stream = 0; stream < mStreamStates.length; stream++) {
                     if (stream != skipStream) {
-                        int devices = mStreamStates[stream].observeDevicesForStream_syncVSS(
-                                false /*checkOthers*/);
-
-                        Set<Integer> devicesSet = AudioSystem.generateAudioDeviceTypesSet(devices);
-                        for (Integer device : devicesSet) {
+                        Set<Integer> deviceSet =
+                                mStreamStates[stream].observeDevicesForStream_syncVSS(
+                                        false /*checkOthers*/);
+                        for (Integer device : deviceSet) {
                             // Update volume states for devices routed for the stream
                             updateVolumeStates(device, stream,
                                     "AudioService#onObserveDevicesForAllStreams");
@@ -6643,7 +6695,7 @@
                 && DEVICE_MEDIA_UNMUTED_ON_PLUG_SET.contains(newDevice)
                 && mStreamStates[AudioSystem.STREAM_MUSIC].mIsMuted
                 && mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(newDevice) != 0
-                && (newDevice & mAudioSystem.getDevicesForStream(AudioSystem.STREAM_MUSIC)) != 0) {
+                && getDeviceSetForStreamDirect(AudioSystem.STREAM_MUSIC).contains(newDevice)) {
             if (DEBUG_VOL) {
                 Log.i(TAG, String.format("onAccessoryPlugMediaUnmute unmuting device=%d [%s]",
                         newDevice, AudioSystem.getOutputDeviceName(newDevice)));
@@ -7031,7 +7083,7 @@
         private boolean mIsMuted;
         private boolean mIsMutedInternally;
         private String mVolumeIndexSettingName;
-        private int mObservedDevices;
+        @NonNull private Set<Integer> mObservedDeviceSet = new TreeSet<>();
 
         private final SparseIntArray mIndexMap = new SparseIntArray(8) {
             @Override
@@ -7098,17 +7150,30 @@
             }
         }
 
+        /**
+         * Returns a list of devices associated with the stream type.
+         *
+         * This is a reference to the local list, do not modify.
+         */
         @GuardedBy("VolumeStreamState.class")
-        public int observeDevicesForStream_syncVSS(boolean checkOthers) {
+        @NonNull
+        public Set<Integer> observeDevicesForStream_syncVSS(
+                boolean checkOthers) {
             if (!mSystemServer.isPrivileged()) {
-                return AudioSystem.DEVICE_NONE;
+                return new TreeSet<Integer>();
             }
-            final int devices = mAudioSystem.getDevicesForStream(mStreamType);
-            if (devices == mObservedDevices) {
-                return devices;
+            final Set<Integer> deviceSet =
+                    getDeviceSetForStreamDirect(mStreamType);
+            if (deviceSet.equals(mObservedDeviceSet)) {
+                return mObservedDeviceSet;
             }
-            final int prevDevices = mObservedDevices;
-            mObservedDevices = devices;
+
+            // Use legacy bit masks for message signalling.
+            // TODO(b/185386781): message needs update since it uses devices bit-mask.
+            final int devices = AudioSystem.getDeviceMaskFromSet(deviceSet);
+            final int prevDevices = AudioSystem.getDeviceMaskFromSet(mObservedDeviceSet);
+
+            mObservedDeviceSet = deviceSet;
             if (checkOthers) {
                 // one stream's devices have changed, check the others
                 postObserveDevicesForAllStreams(mStreamType);
@@ -7124,7 +7189,7 @@
                     SENDMSG_QUEUE, prevDevices /*arg1*/, devices /*arg2*/,
                     // ok to send reference to this object, it is final
                     mStreamDevicesChanged /*obj*/, 0 /*delay*/);
-            return devices;
+            return mObservedDeviceSet;
         }
 
         public @Nullable String getSettingNameForDevice(int device) {
@@ -7555,19 +7620,7 @@
             }
             pw.println();
             pw.print("   Devices: ");
-            final int devices = getDevicesForStreamInt(mStreamType);
-            int device, i = 0, n = 0;
-            // iterate all devices from 1 to DEVICE_OUT_DEFAULT exclusive
-            // (the default device is not returned by getDevicesForStreamInt)
-            while ((device = 1 << i) != AudioSystem.DEVICE_OUT_DEFAULT) {
-                if ((devices & device) != 0) {
-                    if (n++ > 0) {
-                        pw.print(", ");
-                    }
-                    pw.print(AudioSystem.getOutputDeviceName(device));
-                }
-                i++;
-            }
+            pw.print(AudioSystem.deviceSetToString(getDeviceSetForStream(mStreamType)));
         }
     }
 
@@ -9324,7 +9377,9 @@
                     mDeviceBroker.setForceUse_Async(AudioSystem.FOR_HDMI_SYSTEM_AUDIO, config,
                             "setHdmiSystemAudioSupported");
                 }
-                device = getDevicesForStreamInt(AudioSystem.STREAM_MUSIC);
+                // TODO(b/185386781): Update AudioManager API to use device list.
+                // So far, this value appears to be advisory for debug log.
+                device = getDeviceMaskForStream(AudioSystem.STREAM_MUSIC);
             }
         }
         return device;
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index 6ef8e87..1429b3c 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -52,15 +52,13 @@
      * in measured methods
      */
     private static final boolean ENABLE_GETDEVICES_STATS = false;
-    private static final int NB_MEASUREMENTS = 2;
-    private static final int METHOD_GETDEVICESFORSTREAM = 0;
-    private static final int METHOD_GETDEVICESFORATTRIBUTES = 1;
+    private static final int NB_MEASUREMENTS = 1;
+    private static final int METHOD_GETDEVICESFORATTRIBUTES = 0;
     private long[] mMethodTimeNs;
     private int[] mMethodCallCounter;
-    private String[] mMethodNames = {"getDevicesForStream", "getDevicesForAttributes"};
+    private String[] mMethodNames = {"getDevicesForAttributes"};
 
     private static final boolean USE_CACHE_FOR_GETDEVICES = true;
-    private ConcurrentHashMap<Integer, Integer> mDevicesForStreamCache;
     private ConcurrentHashMap<Pair<AudioAttributes, Boolean>, ArrayList<AudioDeviceAttributes>>
             mDevicesForAttrCache;
     private int[] mMethodCacheHit;
@@ -113,8 +111,6 @@
             sSingletonDefaultAdapter = new AudioSystemAdapter();
             AudioSystem.setRoutingCallback(sSingletonDefaultAdapter);
             if (USE_CACHE_FOR_GETDEVICES) {
-                sSingletonDefaultAdapter.mDevicesForStreamCache =
-                        new ConcurrentHashMap<>(AudioSystem.getNumStreamTypes());
                 sSingletonDefaultAdapter.mDevicesForAttrCache =
                         new ConcurrentHashMap<>(AudioSystem.getNumStreamTypes());
                 sSingletonDefaultAdapter.mMethodCacheHit = new int[NB_MEASUREMENTS];
@@ -131,11 +127,6 @@
         if (DEBUG_CACHE) {
             Log.d(TAG, "---- clearing cache ----------");
         }
-        if (mDevicesForStreamCache != null) {
-            synchronized (mDevicesForStreamCache) {
-                mDevicesForStreamCache.clear();
-            }
-        }
         if (mDevicesForAttrCache != null) {
             synchronized (mDevicesForAttrCache) {
                 mDevicesForAttrCache.clear();
@@ -144,60 +135,6 @@
     }
 
     /**
-     * Same as {@link AudioSystem#getDevicesForStream(int)}
-     * @param stream a valid stream type
-     * @return a mask of device types
-     */
-    public int getDevicesForStream(int stream) {
-        if (!ENABLE_GETDEVICES_STATS) {
-            return getDevicesForStreamImpl(stream);
-        }
-        mMethodCallCounter[METHOD_GETDEVICESFORSTREAM]++;
-        final long startTime = SystemClock.uptimeNanos();
-        final int res = getDevicesForStreamImpl(stream);
-        mMethodTimeNs[METHOD_GETDEVICESFORSTREAM] += SystemClock.uptimeNanos() - startTime;
-        return res;
-    }
-
-    private int getDevicesForStreamImpl(int stream) {
-        if (USE_CACHE_FOR_GETDEVICES) {
-            Integer res;
-            synchronized (mDevicesForStreamCache) {
-                res = mDevicesForStreamCache.get(stream);
-                if (res == null) {
-                    res = AudioSystem.getDevicesForStream(stream);
-                    mDevicesForStreamCache.put(stream, res);
-                    if (DEBUG_CACHE) {
-                        Log.d(TAG, mMethodNames[METHOD_GETDEVICESFORSTREAM]
-                                + streamDeviceToDebugString(stream, res));
-                    }
-                    return res;
-                }
-                // cache hit
-                mMethodCacheHit[METHOD_GETDEVICESFORSTREAM]++;
-                if (DEBUG_CACHE) {
-                    final int real = AudioSystem.getDevicesForStream(stream);
-                    if (res == real) {
-                        Log.d(TAG, mMethodNames[METHOD_GETDEVICESFORSTREAM]
-                                + streamDeviceToDebugString(stream, res) + " CACHE");
-                    } else {
-                        Log.e(TAG, mMethodNames[METHOD_GETDEVICESFORSTREAM]
-                                + streamDeviceToDebugString(stream, res)
-                                + " CACHE ERROR real dev=0x" + Integer.toHexString(real));
-                    }
-                }
-            }
-            return res;
-        }
-        // not using cache
-        return AudioSystem.getDevicesForStream(stream);
-    }
-
-    private static String streamDeviceToDebugString(int stream, int dev) {
-        return " stream=" + stream + " dev=0x" + Integer.toHexString(dev);
-    }
-
-    /**
      * Same as {@link AudioSystem#getDevicesForAttributes(AudioAttributes)}
      * @param attributes the attributes for which the routing is queried
      * @return the devices that the stream with the given attributes would be routed to
@@ -253,12 +190,9 @@
     }
 
     private static String attrDeviceToDebugString(@NonNull AudioAttributes attr,
-            @NonNull ArrayList<AudioDeviceAttributes> devices) {
-        String ds = " attrUsage=" + attr.getSystemUsage();
-        for (AudioDeviceAttributes ada : devices) {
-            ds = ds.concat(" dev=0x" + Integer.toHexString(ada.getInternalType()));
-        }
-        return ds;
+            @NonNull List<AudioDeviceAttributes> devices) {
+        return " attrUsage=" + attr.getSystemUsage() + " "
+                + AudioSystem.deviceSetToString(AudioSystem.generateAudioDeviceTypesSet(devices));
     }
 
     /**
@@ -518,20 +452,23 @@
      */
     public void dump(PrintWriter pw) {
         pw.println("\nAudioSystemAdapter:");
-        pw.println(" mDevicesForStreamCache:");
-        if (mDevicesForStreamCache != null) {
-            for (Integer stream : mDevicesForStreamCache.keySet()) {
-                pw.println("\t stream:" + stream + " device:"
-                        + AudioSystem.getOutputDeviceName(mDevicesForStreamCache.get(stream)));
-            }
-        }
         pw.println(" mDevicesForAttrCache:");
         if (mDevicesForAttrCache != null) {
             for (Map.Entry<Pair<AudioAttributes, Boolean>, ArrayList<AudioDeviceAttributes>>
                     entry : mDevicesForAttrCache.entrySet()) {
-                pw.println("\t" + entry.getKey().first + " forVolume: " + entry.getKey().second);
-                for (AudioDeviceAttributes devAttr : entry.getValue()) {
-                    pw.println("\t\t" + devAttr);
+                final AudioAttributes attributes = entry.getKey().first;
+                try {
+                    final int stream = attributes.getVolumeControlStream();
+                    pw.println("\t" + attributes + " forVolume: " + entry.getKey().second
+                            + " stream: "
+                            + AudioSystem.STREAM_NAMES[stream] + "(" + stream + ")");
+                    for (AudioDeviceAttributes devAttr : entry.getValue()) {
+                        pw.println("\t\t" + devAttr);
+                    }
+                } catch (IllegalArgumentException e) {
+                    // dump could fail if attributes do not map to a stream.
+                    pw.println("\t dump failed for attributes: " + attributes);
+                    Log.e(TAG, "dump failed", e);
                 }
             }
         }
@@ -545,7 +482,8 @@
                     + ": counter=" + mMethodCallCounter[i]
                     + " time(ms)=" + (mMethodTimeNs[i] / 1E6)
                     + (USE_CACHE_FOR_GETDEVICES
-                        ? (" FScacheHit=" + mMethodCacheHit[METHOD_GETDEVICESFORSTREAM]) : ""));
+                        ? (" FScacheHit=" + mMethodCacheHit[METHOD_GETDEVICESFORATTRIBUTES])
+                        : ""));
         }
         pw.println("\n");
     }
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index 79705a3..bf69284 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -20,12 +20,8 @@
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE;
 import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_DEFAULT;
-import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT;
+import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE;
 
-import static com.android.server.biometrics.BiometricServiceStateProto.MULTI_SENSOR_STATE_FACE_SCANNING;
-import static com.android.server.biometrics.BiometricServiceStateProto.MULTI_SENSOR_STATE_FP_SCANNING;
-import static com.android.server.biometrics.BiometricServiceStateProto.MULTI_SENSOR_STATE_SWITCHING;
-import static com.android.server.biometrics.BiometricServiceStateProto.MULTI_SENSOR_STATE_UNKNOWN;
 import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTHENTICATED_PENDING_SYSUI;
 import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_CALLED;
 import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_IDLE;
@@ -100,14 +96,6 @@
     @Retention(RetentionPolicy.SOURCE)
     @interface SessionState {}
 
-    /** Defined in biometrics.proto */
-    @IntDef({
-            MULTI_SENSOR_STATE_UNKNOWN,
-            MULTI_SENSOR_STATE_FACE_SCANNING,
-            MULTI_SENSOR_STATE_FP_SCANNING})
-    @Retention(RetentionPolicy.SOURCE)
-    @interface MultiSensorState {}
-
     /**
      * Notify the holder of the AuthSession that the caller/client's binder has died. The
      * holder (BiometricService) should schedule {@link AuthSession#onClientDied()} to be run
@@ -119,7 +107,7 @@
 
     private final Context mContext;
     private final IStatusBarService mStatusBarService;
-    private final IBiometricSysuiReceiver mSysuiReceiver;
+    @VisibleForTesting final IBiometricSysuiReceiver mSysuiReceiver;
     private final KeyStore mKeyStore;
     private final Random mRandom;
     private final ClientDeathReceiver mClientDeathReceiver;
@@ -133,7 +121,7 @@
     private final long mRequestId;
     private final long mOperationId;
     private final int mUserId;
-    private final IBiometricSensorReceiver mSensorReceiver;
+    @VisibleForTesting final IBiometricSensorReceiver mSensorReceiver;
     // Original receiver from BiometricPrompt.
     private final IBiometricServiceReceiver mClientReceiver;
     private final String mOpPackageName;
@@ -143,10 +131,10 @@
     // The current state, which can be either idle, called, or started
     private @SessionState int mState = STATE_AUTH_IDLE;
     private @BiometricMultiSensorMode int mMultiSensorMode;
-    private @MultiSensorState int mMultiSensorState;
     private int[] mSensors;
     // TODO(b/197265902): merge into state
     private boolean mCancelled;
+    private int mAuthenticatedSensorId = -1;
     // For explicit confirmation, do not send to keystore until the user has confirmed
     // the authentication.
     private byte[] mTokenEscrow;
@@ -232,8 +220,16 @@
         }
     }
 
-    private void setSensorsToStateWaitingForCookie() throws RemoteException {
+    private void setSensorsToStateWaitingForCookie(boolean isTryAgain) throws RemoteException {
         for (BiometricSensor sensor : mPreAuthInfo.eligibleSensors) {
+            @BiometricSensor.SensorState final int state = sensor.getSensorState();
+            if (isTryAgain
+                    && state != BiometricSensor.STATE_STOPPED
+                    && state != BiometricSensor.STATE_CANCELING) {
+                Slog.d(TAG, "Skip retry because sensor: " + sensor.id + " is: " + state);
+                continue;
+            }
+
             final int cookie = mRandom.nextInt(Integer.MAX_VALUE - 1) + 1;
             final boolean requireConfirmation = isConfirmationRequired(sensor);
 
@@ -254,7 +250,6 @@
             mState = STATE_SHOWING_DEVICE_CREDENTIAL;
             mSensors = new int[0];
             mMultiSensorMode = BIOMETRIC_MULTI_SENSOR_DEFAULT;
-            mMultiSensorState = MULTI_SENSOR_STATE_UNKNOWN;
 
             mStatusBarService.showAuthenticationDialog(
                     mPromptInfo,
@@ -269,7 +264,7 @@
                     mMultiSensorMode);
         } else if (!mPreAuthInfo.eligibleSensors.isEmpty()) {
             // Some combination of biometric or biometric|credential is requested
-            setSensorsToStateWaitingForCookie();
+            setSensorsToStateWaitingForCookie(false /* isTryAgain */);
             mState = STATE_AUTH_CALLED;
         } else {
             // No authenticators requested. This should never happen - an exception should have
@@ -283,6 +278,10 @@
             Slog.w(TAG, "Received cookie but already cancelled (ignoring): " + cookie);
             return;
         }
+        if (hasAuthenticated()) {
+            Slog.d(TAG, "onCookieReceived after successful auth");
+            return;
+        }
 
         for (BiometricSensor sensor : mPreAuthInfo.eligibleSensors) {
             sensor.goToStateCookieReturnedIfCookieMatches(cookie);
@@ -307,7 +306,6 @@
                     }
                     mMultiSensorMode = getMultiSensorModeForNewSession(
                             mPreAuthInfo.eligibleSensors);
-                    mMultiSensorState = MULTI_SENSOR_STATE_UNKNOWN;
 
                     mStatusBarService.showAuthenticationDialog(mPromptInfo,
                             mSysuiReceiver,
@@ -381,9 +379,8 @@
         // sending the final error callback to the application.
         for (BiometricSensor sensor : mPreAuthInfo.eligibleSensors) {
             try {
-                final boolean shouldCancel = filter.apply(sensor);
-                Slog.d(TAG, "sensorId: " + sensor.id + ", shouldCancel: " + shouldCancel);
-                if (shouldCancel) {
+                if (filter.apply(sensor)) {
+                    Slog.d(TAG, "Cancelling sensorId: " + sensor.id);
                     sensor.goToStateCancelling(mToken, mOpPackageName, mRequestId);
                 }
             } catch (RemoteException e) {
@@ -412,10 +409,16 @@
             }
         }
 
+        // do not propagate the error and let onAuthenticationSucceeded handle the new state
+        if (hasAuthenticated()) {
+            Slog.d(TAG, "onErrorReceived after successful auth (ignoring)");
+            return false;
+        }
+
         mErrorEscrow = error;
         mVendorCodeEscrow = vendorCode;
 
-        final @BiometricAuthenticator.Modality int modality = sensorIdToModality(sensorId);
+        @Modality final int modality = sensorIdToModality(sensorId);
 
         switch (mState) {
             case STATE_AUTH_CALLED: {
@@ -430,7 +433,6 @@
 
                     mState = STATE_SHOWING_DEVICE_CREDENTIAL;
                     mMultiSensorMode = BIOMETRIC_MULTI_SENSOR_DEFAULT;
-                    mMultiSensorState = MULTI_SENSOR_STATE_UNKNOWN;
                     mSensors = new int[0];
 
                     mStatusBarService.showAuthenticationDialog(
@@ -468,12 +470,6 @@
                     return true;
                 } else {
                     mState = STATE_ERROR_PENDING_SYSUI;
-                    if (mMultiSensorMode == BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT
-                            && mMultiSensorState == MULTI_SENSOR_STATE_FACE_SCANNING) {
-                        // wait for the UI to signal when modality should switch
-                        Slog.d(TAG, "onErrorReceived: waiting for modality switch callback");
-                        mMultiSensorState = MULTI_SENSOR_STATE_SWITCHING;
-                    }
                     mStatusBarService.onBiometricError(modality, error, vendorCode);
                 }
                 break;
@@ -505,6 +501,11 @@
     }
 
     void onAcquired(int sensorId, int acquiredInfo, int vendorCode) {
+        if (hasAuthenticated()) {
+            Slog.d(TAG, "onAcquired after successful auth");
+            return;
+        }
+
         final String message = getAcquiredMessageForSensor(sensorId, acquiredInfo, vendorCode);
         Slog.d(TAG, "sensorId: " + sensorId + " acquiredInfo: " + acquiredInfo
                 + " message: " + message);
@@ -520,6 +521,10 @@
     }
 
     void onSystemEvent(int event) {
+        if (hasAuthenticated()) {
+            Slog.d(TAG, "onSystemEvent after successful auth");
+            return;
+        }
         if (!mPromptInfo.isReceiveSystemEvents()) {
             return;
         }
@@ -538,53 +543,35 @@
 
         mState = STATE_AUTH_STARTED_UI_SHOWING;
 
-        if (mMultiSensorMode == BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT) {
-            mMultiSensorState = MULTI_SENSOR_STATE_FACE_SCANNING;
-        } else {
-            startFingerprintSensorsNow();
-        }
-    }
-
-    // call anytime after onDialogAnimatedIn() to indicate it's appropriate to start the
-    // fingerprint sensor (i.e. face auth has failed or is not available)
-    void onStartFingerprint() {
-        if (mMultiSensorMode != BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT) {
-            Slog.e(TAG, "onStartFingerprint, unexpected mode: " + mMultiSensorMode);
-            return;
-        }
-
-        if (mState != STATE_AUTH_STARTED
-                && mState != STATE_AUTH_STARTED_UI_SHOWING
-                && mState != STATE_AUTH_PAUSED
-                && mState != STATE_ERROR_PENDING_SYSUI) {
-            Slog.w(TAG, "onStartFingerprint, started from unexpected state: " + mState);
-        }
-
-        mMultiSensorState = MULTI_SENSOR_STATE_FP_SCANNING;
-        startFingerprintSensorsNow();
-    }
-
-    // unguarded helper for the above methods only
-    private void startFingerprintSensorsNow() {
         startAllPreparedFingerprintSensors();
         mState = STATE_AUTH_STARTED_UI_SHOWING;
     }
 
     void onTryAgainPressed() {
+        if (hasAuthenticated()) {
+            Slog.d(TAG, "onTryAgainPressed after successful auth");
+            return;
+        }
+
         if (mState != STATE_AUTH_PAUSED) {
             Slog.w(TAG, "onTryAgainPressed, state: " + mState);
         }
 
         try {
-            setSensorsToStateWaitingForCookie();
+            setSensorsToStateWaitingForCookie(true /* isTryAgain */);
             mState = STATE_AUTH_PAUSED_RESUMING;
         } catch (RemoteException e) {
             Slog.e(TAG, "RemoteException: " + e);
         }
     }
 
-    void onAuthenticationSucceeded(int sensorId, boolean strong,
-            byte[] token) {
+    void onAuthenticationSucceeded(int sensorId, boolean strong, byte[] token) {
+        if (hasAuthenticated()) {
+            Slog.d(TAG, "onAuthenticationSucceeded after successful auth");
+            return;
+        }
+
+        mAuthenticatedSensorId = sensorId;
         if (strong) {
             mTokenEscrow = token;
         } else {
@@ -596,7 +583,7 @@
         try {
             // Notify SysUI that the biometric has been authenticated. SysUI already knows
             // the implicit/explicit state and will react accordingly.
-            mStatusBarService.onBiometricAuthenticated();
+            mStatusBarService.onBiometricAuthenticated(sensorIdToModality(sensorId));
 
             final boolean requireConfirmation = isConfirmationRequiredByAnyEligibleSensor();
 
@@ -609,20 +596,22 @@
         } catch (RemoteException e) {
             Slog.e(TAG, "RemoteException", e);
         }
+
+        cancelAllSensors(sensor -> sensor.id != sensorId);
     }
 
-    void onAuthenticationRejected() {
-        try {
-            mStatusBarService.onBiometricError(TYPE_NONE,
-                    BiometricConstants.BIOMETRIC_PAUSED_REJECTED, 0 /* vendorCode */);
+    void onAuthenticationRejected(int sensorId) {
+        if (hasAuthenticated()) {
+            Slog.d(TAG, "onAuthenticationRejected after successful auth");
+            return;
+        }
 
-            // TODO: This logic will need to be updated if BP is multi-modal
-            if (hasPausableBiometric()) {
-                // Pause authentication. onBiometricAuthenticated(false) causes the
-                // dialog to show a "try again" button for passive modalities.
+        try {
+            mStatusBarService.onBiometricError(sensorIdToModality(sensorId),
+                    BiometricConstants.BIOMETRIC_PAUSED_REJECTED, 0 /* vendorCode */);
+            if (pauseSensorIfSupported(sensorId)) {
                 mState = STATE_AUTH_PAUSED;
             }
-
             mClientReceiver.onAuthenticationFailed();
         } catch (RemoteException e) {
             Slog.e(TAG, "RemoteException", e);
@@ -630,15 +619,34 @@
     }
 
     void onAuthenticationTimedOut(int sensorId, int cookie, int error, int vendorCode) {
+        if (hasAuthenticated()) {
+            Slog.d(TAG, "onAuthenticationTimedOut after successful auth");
+            return;
+        }
+
         try {
             mStatusBarService.onBiometricError(sensorIdToModality(sensorId), error, vendorCode);
+            pauseSensorIfSupported(sensorId);
             mState = STATE_AUTH_PAUSED;
         } catch (RemoteException e) {
             Slog.e(TAG, "RemoteException", e);
         }
     }
 
+    private boolean pauseSensorIfSupported(int sensorId) {
+        if (sensorIdToModality(sensorId) == TYPE_FACE) {
+            cancelAllSensors(sensor -> sensor.id == sensorId);
+            return true;
+        }
+        return false;
+    }
+
     void onDeviceCredentialPressed() {
+        if (hasAuthenticated()) {
+            Slog.d(TAG, "onDeviceCredentialPressed after successful auth");
+            return;
+        }
+
         // Cancel authentication. Skip the token/package check since we are cancelling
         // from system server. The interface is permission protected so this is fine.
         cancelAllSensors();
@@ -666,6 +674,10 @@
         }
     }
 
+    private boolean hasAuthenticated() {
+        return mAuthenticatedSensorId != -1;
+    }
+
     private void logOnDialogDismissed(@BiometricPrompt.DismissedReason int reason) {
         if (reason == BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED) {
             // Explicit auth, authentication confirmed.
@@ -794,6 +806,11 @@
      * @return true if this AuthSession is finished, e.g. should be set to null
      */
     boolean onCancelAuthSession(boolean force) {
+        if (hasAuthenticated()) {
+            Slog.d(TAG, "onCancelAuthSession after successful auth");
+            return true;
+        }
+
         mCancelled = true;
 
         final boolean authStarted = mState == STATE_AUTH_CALLED
@@ -848,15 +865,6 @@
         return remainingCookies == 0;
     }
 
-    private boolean hasPausableBiometric() {
-        for (BiometricSensor sensor : mPreAuthInfo.eligibleSensors) {
-            if (sensor.modality == TYPE_FACE) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     @SessionState int getState() {
         return mState;
     }
@@ -919,7 +927,7 @@
         }
 
         if (hasFace && hasFingerprint) {
-            return BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT;
+            return BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE;
         }
         return BIOMETRIC_MULTI_SENSOR_DEFAULT;
     }
diff --git a/services/core/java/com/android/server/biometrics/BiometricSensor.java b/services/core/java/com/android/server/biometrics/BiometricSensor.java
index 0333c3e..7166783 100644
--- a/services/core/java/com/android/server/biometrics/BiometricSensor.java
+++ b/services/core/java/com/android/server/biometrics/BiometricSensor.java
@@ -131,8 +131,10 @@
 
     void goToStateCancelling(IBinder token, String opPackageName, long requestId)
             throws RemoteException {
-        impl.cancelAuthenticationFromService(token, opPackageName, requestId);
-        mSensorState = STATE_CANCELING;
+        if (mSensorState != STATE_CANCELING) {
+            impl.cancelAuthenticationFromService(token, opPackageName, requestId);
+            mSensorState = STATE_CANCELING;
+        }
     }
 
     void goToStoppedStateIfCookieMatches(int cookie, int error) {
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 758cf7a..0d9b754 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -55,7 +55,6 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
-import android.os.Message;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
@@ -84,6 +83,7 @@
 import java.util.Random;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Supplier;
 
 /**
  * System service that arbitrates the modality for BiometricPrompt to use.
@@ -92,22 +92,6 @@
 
     static final String TAG = "BiometricService";
 
-    private static final int MSG_ON_AUTHENTICATION_SUCCEEDED = 2;
-    private static final int MSG_ON_AUTHENTICATION_REJECTED = 3;
-    private static final int MSG_ON_ERROR = 4;
-    private static final int MSG_ON_ACQUIRED = 5;
-    private static final int MSG_ON_DISMISSED = 6;
-    private static final int MSG_ON_TRY_AGAIN_PRESSED = 7;
-    private static final int MSG_ON_READY_FOR_AUTHENTICATION = 8;
-    private static final int MSG_AUTHENTICATE = 9;
-    private static final int MSG_CANCEL_AUTHENTICATION = 10;
-    private static final int MSG_ON_AUTHENTICATION_TIMED_OUT = 11;
-    private static final int MSG_ON_DEVICE_CREDENTIAL_PRESSED = 12;
-    private static final int MSG_ON_SYSTEM_EVENT = 13;
-    private static final int MSG_CLIENT_DIED = 14;
-    private static final int MSG_ON_DIALOG_ANIMATED_IN = 15;
-    private static final int MSG_ON_START_FINGERPRINT_NOW = 16;
-
     private final Injector mInjector;
     private final DevicePolicyManager mDevicePolicyManager;
     @VisibleForTesting
@@ -116,7 +100,7 @@
     final SettingObserver mSettingObserver;
     private final List<EnabledOnKeyguardCallback> mEnabledOnKeyguardCallbacks;
     private final Random mRandom = new Random();
-    @NonNull private final AtomicLong mRequestCounter;
+    @NonNull private final Supplier<Long> mRequestCounter;
 
     @VisibleForTesting
     IStatusBarService mStatusBarService;
@@ -128,133 +112,13 @@
     // Get and cache the available biometric authenticators and their associated info.
     final ArrayList<BiometricSensor> mSensors = new ArrayList<>();
 
+    @VisibleForTesting
     BiometricStrengthController mBiometricStrengthController;
 
     // The current authentication session, null if idle/done.
     @VisibleForTesting
-    AuthSession mCurrentAuthSession;
-
-    @VisibleForTesting
-    final Handler mHandler = new Handler(Looper.getMainLooper()) {
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_ON_AUTHENTICATION_SUCCEEDED: {
-                    SomeArgs args = (SomeArgs) msg.obj;
-                    handleAuthenticationSucceeded(
-                            args.argi1 /* sensorId */,
-                            (byte[]) args.arg1 /* token */);
-                    args.recycle();
-                    break;
-                }
-
-                case MSG_ON_AUTHENTICATION_REJECTED: {
-                    handleAuthenticationRejected();
-                    break;
-                }
-
-                case MSG_ON_ERROR: {
-                    SomeArgs args = (SomeArgs) msg.obj;
-                    handleOnError(
-                            args.argi1 /* sensorId */,
-                            args.argi2 /* cookie */,
-                            args.argi3 /* error */,
-                            args.argi4 /* vendorCode */);
-                    args.recycle();
-                    break;
-                }
-
-                case MSG_ON_ACQUIRED: {
-                    SomeArgs args = (SomeArgs) msg.obj;
-                    handleOnAcquired(
-                            args.argi1 /* sensorId */,
-                            args.argi2 /* acquiredInfo */,
-                            args.argi3 /* vendorCode */);
-                    args.recycle();
-                    break;
-                }
-
-                case MSG_ON_DISMISSED: {
-                    handleOnDismissed(msg.arg1, (byte[]) msg.obj);
-                    break;
-                }
-
-                case MSG_ON_TRY_AGAIN_PRESSED: {
-                    handleOnTryAgainPressed();
-                    break;
-                }
-
-                case MSG_ON_READY_FOR_AUTHENTICATION: {
-                    SomeArgs args = (SomeArgs) msg.obj;
-                    handleOnReadyForAuthentication(
-                            args.argi1 /* cookie */);
-                    args.recycle();
-                    break;
-                }
-
-                case MSG_AUTHENTICATE: {
-                    SomeArgs args = (SomeArgs) msg.obj;
-                    handleAuthenticate(
-                            (IBinder) args.arg1 /* token */,
-                            (long) args.arg6 /* requestId */,
-                            (long) args.arg2 /* operationId */,
-                            args.argi1 /* userid */,
-                            (IBiometricServiceReceiver) args.arg3 /* receiver */,
-                            (String) args.arg4 /* opPackageName */,
-                            (PromptInfo) args.arg5 /* promptInfo */);
-                    args.recycle();
-                    break;
-                }
-
-                case MSG_CANCEL_AUTHENTICATION: {
-                    SomeArgs args = (SomeArgs) msg.obj;
-                    handleCancelAuthentication((long) args.arg3 /* requestId */);
-                    args.recycle();
-                    break;
-                }
-
-                case MSG_ON_AUTHENTICATION_TIMED_OUT: {
-                    SomeArgs args = (SomeArgs) msg.obj;
-                    handleAuthenticationTimedOut(
-                            args.argi1 /* sensorId */,
-                            args.argi2 /* cookie */,
-                            args.argi3 /* error */,
-                            args.argi4 /* vendorCode */);
-                    args.recycle();
-                    break;
-                }
-
-                case MSG_ON_DEVICE_CREDENTIAL_PRESSED: {
-                    handleOnDeviceCredentialPressed();
-                    break;
-                }
-
-                case MSG_ON_SYSTEM_EVENT: {
-                    handleOnSystemEvent((int) msg.obj);
-                    break;
-                }
-
-                case MSG_CLIENT_DIED: {
-                    handleClientDied();
-                    break;
-                }
-
-                case MSG_ON_DIALOG_ANIMATED_IN: {
-                    handleOnDialogAnimatedIn();
-                    break;
-                }
-
-                case MSG_ON_START_FINGERPRINT_NOW: {
-                    handleOnStartFingerprintNow();
-                    break;
-                }
-
-                default:
-                    Slog.e(TAG, "Unknown message: " + msg);
-                    break;
-            }
-        }
-    };
+    AuthSession mAuthSession;
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
 
     /**
      * Tracks authenticatorId invalidation. For more details, see
@@ -552,93 +416,74 @@
     }
 
     // Receives events from individual biometric sensors.
-    @VisibleForTesting
-    final IBiometricSensorReceiver mBiometricSensorReceiver = new IBiometricSensorReceiver.Stub() {
-        @Override
-        public void onAuthenticationSucceeded(int sensorId, byte[] token) {
-            SomeArgs args = SomeArgs.obtain();
-            args.argi1 = sensorId;
-            args.arg1 = token;
-            mHandler.obtainMessage(MSG_ON_AUTHENTICATION_SUCCEEDED, args).sendToTarget();
-        }
-
-        @Override
-        public void onAuthenticationFailed(int sensorId) {
-            Slog.v(TAG, "onAuthenticationFailed");
-            mHandler.obtainMessage(MSG_ON_AUTHENTICATION_REJECTED).sendToTarget();
-        }
-
-        @Override
-        public void onError(int sensorId, int cookie, @BiometricConstants.Errors int error,
-                int vendorCode) {
-            // Determine if error is hard or soft error. Certain errors (such as TIMEOUT) are
-            // soft errors and we should allow the user to try authenticating again instead of
-            // dismissing BiometricPrompt.
-            if (error == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT) {
-                SomeArgs args = SomeArgs.obtain();
-                args.argi1 = sensorId;
-                args.argi2 = cookie;
-                args.argi3 = error;
-                args.argi4 = vendorCode;
-                mHandler.obtainMessage(MSG_ON_AUTHENTICATION_TIMED_OUT, args).sendToTarget();
-            } else {
-                SomeArgs args = SomeArgs.obtain();
-                args.argi1 = sensorId;
-                args.argi2 = cookie;
-                args.argi3 = error;
-                args.argi4 = vendorCode;
-                mHandler.obtainMessage(MSG_ON_ERROR, args).sendToTarget();
+    private IBiometricSensorReceiver createBiometricSensorReceiver(final long requestId) {
+        return new IBiometricSensorReceiver.Stub() {
+            @Override
+            public void onAuthenticationSucceeded(int sensorId, byte[] token) {
+                mHandler.post(() -> handleAuthenticationSucceeded(requestId, sensorId, token));
             }
-        }
 
-        @Override
-        public void onAcquired(int sensorId, int acquiredInfo, int vendorCode) {
-            SomeArgs args = SomeArgs.obtain();
-            args.argi1 = sensorId;
-            args.argi2 = acquiredInfo;
-            args.argi3 = vendorCode;
-            mHandler.obtainMessage(MSG_ON_ACQUIRED, args).sendToTarget();
-        }
-    };
+            @Override
+            public void onAuthenticationFailed(int sensorId) {
+                Slog.v(TAG, "onAuthenticationFailed");
+                mHandler.post(() -> handleAuthenticationRejected(requestId, sensorId));
+            }
 
-    final IBiometricSysuiReceiver mSysuiReceiver = new IBiometricSysuiReceiver.Stub() {
-        @Override
-        public void onDialogDismissed(@BiometricPrompt.DismissedReason int reason,
-                @Nullable byte[] credentialAttestation) {
-            mHandler.obtainMessage(MSG_ON_DISMISSED,
-                    reason,
-                    0 /* arg2 */,
-                    credentialAttestation /* obj */).sendToTarget();
-        }
+            @Override
+            public void onError(int sensorId, int cookie, @BiometricConstants.Errors int error,
+                    int vendorCode) {
+                // Determine if error is hard or soft error. Certain errors (such as TIMEOUT) are
+                // soft errors and we should allow the user to try authenticating again instead of
+                // dismissing BiometricPrompt.
+                if (error == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT) {
+                    mHandler.post(() -> handleAuthenticationTimedOut(
+                            requestId, sensorId, cookie, error, vendorCode));
+                } else {
+                    mHandler.post(() -> handleOnError(
+                            requestId, sensorId, cookie, error, vendorCode));
+                }
+            }
 
-        @Override
-        public void onTryAgainPressed() {
-            mHandler.sendEmptyMessage(MSG_ON_TRY_AGAIN_PRESSED);
-        }
+            @Override
+            public void onAcquired(int sensorId, int acquiredInfo, int vendorCode) {
+                mHandler.post(() -> handleOnAcquired(
+                        requestId, sensorId, acquiredInfo, vendorCode));
+            }
+        };
+    }
 
-        @Override
-        public void onDeviceCredentialPressed() {
-            mHandler.sendEmptyMessage(MSG_ON_DEVICE_CREDENTIAL_PRESSED);
-        }
+    private IBiometricSysuiReceiver createSysuiReceiver(final long requestId) {
+        return new IBiometricSysuiReceiver.Stub() {
+            @Override
+            public void onDialogDismissed(@BiometricPrompt.DismissedReason int reason,
+                    @Nullable byte[] credentialAttestation) {
+                mHandler.post(() -> handleOnDismissed(requestId, reason, credentialAttestation));
+            }
 
-        @Override
-        public void onSystemEvent(int event) {
-            mHandler.obtainMessage(MSG_ON_SYSTEM_EVENT, event).sendToTarget();
-        }
+            @Override
+            public void onTryAgainPressed() {
+                mHandler.post(() -> handleOnTryAgainPressed(requestId));
+            }
 
-        @Override
-        public void onDialogAnimatedIn() {
-            mHandler.obtainMessage(MSG_ON_DIALOG_ANIMATED_IN).sendToTarget();
-        }
+            @Override
+            public void onDeviceCredentialPressed() {
+                mHandler.post(() -> handleOnDeviceCredentialPressed(requestId));
+            }
 
-        @Override
-        public void onStartFingerprintNow() {
-            mHandler.obtainMessage(MSG_ON_START_FINGERPRINT_NOW).sendToTarget();
-        }
-    };
+            @Override
+            public void onSystemEvent(int event) {
+                mHandler.post(() -> handleOnSystemEvent(requestId, event));
+            }
 
-    private final AuthSession.ClientDeathReceiver mClientDeathReceiver = () -> {
-        mHandler.sendEmptyMessage(MSG_CLIENT_DIED);
+            @Override
+            public void onDialogAnimatedIn() {
+                mHandler.post(() -> handleOnDialogAnimatedIn(requestId));
+            }
+        };
+    }
+
+    private AuthSession.ClientDeathReceiver createClientDeathReceiver(final long requestId) {
+        return () -> mHandler.post(() -> handleClientDied(requestId));
     };
 
     /**
@@ -679,12 +524,10 @@
         }
 
         @Override // Binder call
-        public void onReadyForAuthentication(int cookie) {
+        public void onReadyForAuthentication(long requestId, int cookie) {
             checkInternalPermission();
 
-            SomeArgs args = SomeArgs.obtain();
-            args.argi1 = cookie;
-            mHandler.obtainMessage(MSG_ON_READY_FOR_AUTHENTICATION, args).sendToTarget();
+            mHandler.post(() -> handleOnReadyForAuthentication(requestId, cookie));
         }
 
         @Override // Binder call
@@ -711,18 +554,9 @@
                 }
             }
 
-            final long requestId = mRequestCounter.incrementAndGet();
-
-            SomeArgs args = SomeArgs.obtain();
-            args.arg1 = token;
-            args.arg2 = operationId;
-            args.argi1 = userId;
-            args.arg3 = receiver;
-            args.arg4 = opPackageName;
-            args.arg5 = promptInfo;
-            args.arg6 = requestId;
-
-            mHandler.obtainMessage(MSG_AUTHENTICATE, args).sendToTarget();
+            final long requestId = mRequestCounter.get();
+            mHandler.post(() -> handleAuthenticate(
+                    token, requestId, operationId, userId, receiver, opPackageName, promptInfo));
 
             return requestId;
         }
@@ -736,7 +570,7 @@
             args.arg2 = opPackageName;
             args.arg3 = requestId;
 
-            mHandler.obtainMessage(MSG_CANCEL_AUTHENTICATION, args).sendToTarget();
+            mHandler.post(() -> handleCancelAuthentication(requestId));
         }
 
         @Override // Binder call
@@ -1002,8 +836,7 @@
                     Slog.d(TAG, "ClearSchedulerBuffer: " + clearSchedulerBuffer);
                     final ProtoOutputStream proto = new ProtoOutputStream(fd);
                     proto.write(BiometricServiceStateProto.AUTH_SESSION_STATE,
-                            mCurrentAuthSession != null ? mCurrentAuthSession.getState()
-                                    : STATE_AUTH_IDLE);
+                            mAuthSession != null ? mAuthSession.getState() : STATE_AUTH_IDLE);
                     for (BiometricSensor sensor : mSensors) {
                         byte[] serviceState = sensor.impl
                                 .dumpSensorServiceStateProto(clearSchedulerBuffer);
@@ -1128,8 +961,9 @@
                     CoexCoordinator.FACE_HAPTIC_DISABLE, 1) != 0;
         }
 
-        public AtomicLong getRequestGenerator() {
-            return new AtomicLong(0);
+        public Supplier<Long> getRequestGenerator() {
+            final AtomicLong generator = new AtomicLong(0);
+            return () -> generator.incrementAndGet();
         }
     }
 
@@ -1202,172 +1036,184 @@
         return false;
     }
 
-    private void handleAuthenticationSucceeded(int sensorId, byte[] token) {
+    @Nullable
+    private AuthSession getAuthSessionIfCurrent(long requestId) {
+        final AuthSession session = mAuthSession;
+        if (session != null && session.getRequestId() == requestId) {
+            return session;
+        }
+        return null;
+    }
+
+    private void handleAuthenticationSucceeded(long requestId, int sensorId, byte[] token) {
         Slog.v(TAG, "handleAuthenticationSucceeded(), sensorId: " + sensorId);
         // Should never happen, log this to catch bad HAL behavior (e.g. auth succeeded
         // after user dismissed/canceled dialog).
-        if (mCurrentAuthSession == null) {
+        final AuthSession session = getAuthSessionIfCurrent(requestId);
+        if (session == null) {
             Slog.e(TAG, "handleAuthenticationSucceeded: AuthSession is null");
             return;
         }
 
-        mCurrentAuthSession.onAuthenticationSucceeded(sensorId, isStrongBiometric(sensorId), token);
+        session.onAuthenticationSucceeded(sensorId, isStrongBiometric(sensorId), token);
     }
 
-    private void handleAuthenticationRejected() {
+    private void handleAuthenticationRejected(long requestId, int sensorId) {
         Slog.v(TAG, "handleAuthenticationRejected()");
 
         // Should never happen, log this to catch bad HAL behavior (e.g. auth rejected
         // after user dismissed/canceled dialog).
-        if (mCurrentAuthSession == null) {
-            Slog.e(TAG, "handleAuthenticationRejected: AuthSession is null");
+        final AuthSession session = getAuthSessionIfCurrent(requestId);
+        if (session == null) {
+            Slog.w(TAG, "handleAuthenticationRejected: AuthSession is not current");
             return;
         }
 
-        mCurrentAuthSession.onAuthenticationRejected();
+        session.onAuthenticationRejected(sensorId);
     }
 
-    private void handleAuthenticationTimedOut(int sensorId, int cookie, int error, int vendorCode) {
+    private void handleAuthenticationTimedOut(long requestId, int sensorId, int cookie, int error,
+            int vendorCode) {
         Slog.v(TAG, "handleAuthenticationTimedOut(), sensorId: " + sensorId
                 + ", cookie: " + cookie
                 + ", error: " + error
                 + ", vendorCode: " + vendorCode);
         // Should never happen, log this to catch bad HAL behavior (e.g. auth succeeded
         // after user dismissed/canceled dialog).
-        if (mCurrentAuthSession == null) {
-            Slog.e(TAG, "handleAuthenticationTimedOut: AuthSession is null");
+        final AuthSession session = getAuthSessionIfCurrent(requestId);
+        if (session == null) {
+            Slog.w(TAG, "handleAuthenticationTimedOut: AuthSession is not current");
             return;
         }
 
-        mCurrentAuthSession.onAuthenticationTimedOut(sensorId, cookie, error, vendorCode);
+        session.onAuthenticationTimedOut(sensorId, cookie, error, vendorCode);
     }
 
-    private void handleOnError(int sensorId, int cookie, @BiometricConstants.Errors int error,
-            int vendorCode) {
+    private void handleOnError(long requestId, int sensorId, int cookie,
+            @BiometricConstants.Errors int error, int vendorCode) {
         Slog.d(TAG, "handleOnError() sensorId: " + sensorId
                 + ", cookie: " + cookie
                 + ", error: " + error
                 + ", vendorCode: " + vendorCode);
 
-        if (mCurrentAuthSession == null) {
-            Slog.e(TAG, "handleOnError: AuthSession is null");
+        final AuthSession session = getAuthSessionIfCurrent(requestId);
+        if (session == null) {
+            Slog.w(TAG, "handleOnError: AuthSession is not current");
             return;
         }
 
         try {
-            final boolean finished = mCurrentAuthSession
-                    .onErrorReceived(sensorId, cookie, error, vendorCode);
+            final boolean finished = session.onErrorReceived(sensorId, cookie, error, vendorCode);
             if (finished) {
                 Slog.d(TAG, "handleOnError: AuthSession finished");
-                mCurrentAuthSession = null;
+                mAuthSession = null;
             }
         } catch (RemoteException e) {
             Slog.e(TAG, "RemoteException", e);
         }
     }
 
-    private void handleOnAcquired(int sensorId, int acquiredInfo, int vendorCode) {
+    private void handleOnAcquired(long requestId, int sensorId, int acquiredInfo, int vendorCode) {
         // Should never happen, log this to catch bad HAL behavior (e.g. auth succeeded
         // after user dismissed/canceled dialog).
-        if (mCurrentAuthSession == null) {
-            Slog.e(TAG, "onAcquired: AuthSession is null");
+        final AuthSession session = getAuthSessionIfCurrent(requestId);
+        if (session == null) {
+            Slog.w(TAG, "onAcquired: AuthSession is not current");
             return;
         }
 
-        mCurrentAuthSession.onAcquired(sensorId, acquiredInfo, vendorCode);
+        session.onAcquired(sensorId, acquiredInfo, vendorCode);
     }
 
-    private void handleOnDismissed(@BiometricPrompt.DismissedReason int reason,
+    private void handleOnDismissed(long requestId, @BiometricPrompt.DismissedReason int reason,
             @Nullable byte[] credentialAttestation) {
-        if (mCurrentAuthSession == null) {
-            Slog.e(TAG, "onDismissed: " + reason + ", AuthSession is null");
+        final AuthSession session = getAuthSessionIfCurrent(requestId);
+        if (session == null) {
+            Slog.e(TAG, "onDismissed: " + reason + ", AuthSession is not current");
             return;
         }
 
-        mCurrentAuthSession.onDialogDismissed(reason, credentialAttestation);
-        mCurrentAuthSession = null;
+        session.onDialogDismissed(reason, credentialAttestation);
+        mAuthSession = null;
     }
 
-    private void handleOnTryAgainPressed() {
+    private void handleOnTryAgainPressed(long requestId) {
         Slog.d(TAG, "onTryAgainPressed");
         // No need to check permission, since it can only be invoked by SystemUI
         // (or system server itself).
-        if (mCurrentAuthSession == null) {
-            Slog.e(TAG, "handleOnTryAgainPressed: AuthSession is null");
+        final AuthSession session = getAuthSessionIfCurrent(requestId);
+        if (session == null) {
+            Slog.w(TAG, "handleOnTryAgainPressed: AuthSession is not current");
             return;
         }
 
-        mCurrentAuthSession.onTryAgainPressed();
+        session.onTryAgainPressed();
     }
 
-    private void handleOnDeviceCredentialPressed() {
+    private void handleOnDeviceCredentialPressed(long requestId) {
         Slog.d(TAG, "onDeviceCredentialPressed");
-        if (mCurrentAuthSession == null) {
-            Slog.e(TAG, "handleOnDeviceCredentialPressed: AuthSession is null");
+        final AuthSession session = getAuthSessionIfCurrent(requestId);
+        if (session == null) {
+            Slog.w(TAG, "handleOnDeviceCredentialPressed: AuthSession is not current");
             return;
         }
 
-        mCurrentAuthSession.onDeviceCredentialPressed();
+        session.onDeviceCredentialPressed();
     }
 
-    private void handleOnSystemEvent(int event) {
+    private void handleOnSystemEvent(long requestId, int event) {
         Slog.d(TAG, "onSystemEvent: " + event);
 
-        if (mCurrentAuthSession == null) {
-            Slog.e(TAG, "handleOnSystemEvent: AuthSession is null");
+        final AuthSession session = getAuthSessionIfCurrent(requestId);
+        if (session == null) {
+            Slog.w(TAG, "handleOnSystemEvent: AuthSession is not current");
             return;
         }
 
-        mCurrentAuthSession.onSystemEvent(event);
+        session.onSystemEvent(event);
     }
 
-    private void handleClientDied() {
-        if (mCurrentAuthSession == null) {
-            Slog.e(TAG, "handleClientDied: AuthSession is null");
+    private void handleClientDied(long requestId) {
+        final AuthSession session = getAuthSessionIfCurrent(requestId);
+        if (session == null) {
+            Slog.w(TAG, "handleClientDied: AuthSession is not current");
             return;
         }
 
-        Slog.e(TAG, "Session: " + mCurrentAuthSession);
-        final boolean finished = mCurrentAuthSession.onClientDied();
+        Slog.e(TAG, "Session: " + session);
+        final boolean finished = session.onClientDied();
         if (finished) {
-            mCurrentAuthSession = null;
+            mAuthSession = null;
         }
     }
 
-    private void handleOnDialogAnimatedIn() {
+    private void handleOnDialogAnimatedIn(long requestId) {
         Slog.d(TAG, "handleOnDialogAnimatedIn");
-        if (mCurrentAuthSession == null) {
-            Slog.e(TAG, "handleOnDialogAnimatedIn: AuthSession is null");
+
+        final AuthSession session = getAuthSessionIfCurrent(requestId);
+        if (session == null) {
+            Slog.w(TAG, "handleOnDialogAnimatedIn: AuthSession is not current");
             return;
         }
 
-        mCurrentAuthSession.onDialogAnimatedIn();
-    }
-
-    private void handleOnStartFingerprintNow() {
-        Slog.d(TAG, "handleOnStartFingerprintNow");
-        if (mCurrentAuthSession == null) {
-            Slog.e(TAG, "handleOnStartFingerprintNow: AuthSession is null");
-            return;
-        }
-
-        mCurrentAuthSession.onStartFingerprint();
+        session.onDialogAnimatedIn();
     }
 
     /**
      * Invoked when each service has notified that its client is ready to be started. When
      * all biometrics are ready, this invokes the SystemUI dialog through StatusBar.
      */
-    private void handleOnReadyForAuthentication(int cookie) {
-        if (mCurrentAuthSession == null) {
+    private void handleOnReadyForAuthentication(long requestId, int cookie) {
+        final AuthSession session = getAuthSessionIfCurrent(requestId);
+        if (session == null) {
             // Only should happen if a biometric was locked out when authenticate() was invoked.
             // In that case, if device credentials are allowed, the UI is already showing. If not
             // allowed, the error has already been returned to the caller.
-            Slog.w(TAG, "handleOnReadyForAuthentication: AuthSession is null");
+            Slog.w(TAG, "handleOnReadyForAuthentication: AuthSession is not current");
             return;
         }
 
-        mCurrentAuthSession.onCookieReceived(cookie);
+        session.onCookieReceived(cookie);
     }
 
     private void handleAuthenticate(IBinder token, long requestId, long operationId, int userId,
@@ -1428,47 +1274,41 @@
 
         // No need to dismiss dialog / send error yet if we're continuing authentication, e.g.
         // "Try again" is showing due to something like ERROR_TIMEOUT.
-        if (mCurrentAuthSession != null) {
+        if (mAuthSession != null) {
             // Forcefully cancel authentication. Dismiss the UI, and immediately send
             // ERROR_CANCELED to the client. Note that we should/will ignore HAL ERROR_CANCELED.
             // Expect to see some harmless "unknown cookie" errors.
-            Slog.w(TAG, "Existing AuthSession: " + mCurrentAuthSession);
-            mCurrentAuthSession.onCancelAuthSession(true /* force */);
-            mCurrentAuthSession = null;
+            Slog.w(TAG, "Existing AuthSession: " + mAuthSession);
+            mAuthSession.onCancelAuthSession(true /* force */);
+            mAuthSession = null;
         }
 
         final boolean debugEnabled = mInjector.isDebugEnabled(getContext(), userId);
-        mCurrentAuthSession = new AuthSession(getContext(), mStatusBarService, mSysuiReceiver,
-                mKeyStore, mRandom, mClientDeathReceiver, preAuthInfo, token, requestId,
-                operationId, userId, mBiometricSensorReceiver, receiver, opPackageName, promptInfo,
-                debugEnabled, mInjector.getFingerprintSensorProperties(getContext()));
+        mAuthSession = new AuthSession(getContext(), mStatusBarService,
+                createSysuiReceiver(requestId), mKeyStore, mRandom,
+                createClientDeathReceiver(requestId), preAuthInfo, token, requestId,
+                operationId, userId, createBiometricSensorReceiver(requestId), receiver,
+                opPackageName, promptInfo, debugEnabled,
+                mInjector.getFingerprintSensorProperties(getContext()));
         try {
-            mCurrentAuthSession.goToInitialState();
+            mAuthSession.goToInitialState();
         } catch (RemoteException e) {
             Slog.e(TAG, "RemoteException", e);
         }
     }
 
     private void handleCancelAuthentication(long requestId) {
-        if (mCurrentAuthSession == null) {
-            Slog.e(TAG, "handleCancelAuthentication: AuthSession is null");
-            return;
-        }
-        if (mCurrentAuthSession.getRequestId() != requestId) {
-            // TODO: actually cancel the operation
-            // This can happen if the operation has been queued, but is cancelled before
-            // it reaches the head of the scheduler. Consider it a programming error for now
-            // and ignore it.
-            Slog.e(TAG, "handleCancelAuthentication: AuthSession mismatch current requestId: "
-                    + mCurrentAuthSession.getRequestId() + " cancel for: " + requestId
-                    + " (ignoring cancellation)");
+        final AuthSession session = getAuthSessionIfCurrent(requestId);
+        if (session == null) {
+            Slog.w(TAG, "handleCancelAuthentication: AuthSession is not current");
+            // TODO: actually cancel the operation?
             return;
         }
 
-        final boolean finished = mCurrentAuthSession.onCancelAuthSession(false /* force */);
+        final boolean finished = session.onCancelAuthSession(false /* force */);
         if (finished) {
             Slog.d(TAG, "handleCancelAuthentication: AuthSession finished");
-            mCurrentAuthSession = null;
+            mAuthSession = null;
         }
     }
 
@@ -1491,7 +1331,7 @@
             pw.println(" " + sensor);
         }
         pw.println();
-        pw.println("CurrentSession: " + mCurrentAuthSession);
+        pw.println("CurrentSession: " + mAuthSession);
         pw.println();
         pw.println("CoexCoordinator: " + CoexCoordinator.getInstance().toString());
         pw.println();
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 54b79e1..6d68772 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -86,6 +86,7 @@
     private long mStartTimeMs;
 
     private boolean mAuthAttempted;
+    private boolean mAuthSuccess = false;
 
     // TODO: This is currently hard to maintain, as each AuthenticationClient subclass must update
     //  the state. We should think of a way to improve this in the future.
@@ -237,6 +238,7 @@
                         "Successful background authentication!");
             }
 
+            mAuthSuccess = true;
             markAlreadyDone();
 
             if (mTaskStackListener != null) {
@@ -502,6 +504,11 @@
         return mAuthAttempted;
     }
 
+    /** If an auth attempt completed successfully. */
+    public boolean wasAuthSuccessful() {
+        return mAuthSuccess;
+    }
+
     protected int getShowOverlayReason() {
         if (isKeyguard()) {
             return BiometricOverlayConstants.REASON_AUTH_KEYGUARD;
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 1a6da94..d0ec447 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -316,7 +316,8 @@
             }
         } else {
             try {
-                mBiometricService.onReadyForAuthentication(cookie);
+                mBiometricService.onReadyForAuthentication(
+                        mCurrentOperation.getClientMonitor().getRequestId(), cookie);
             } catch (RemoteException e) {
                 Slog.e(getTag(), "Remote exception when contacting BiometricService", e);
             }
diff --git a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
index f1c786b49..46d863d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
@@ -123,7 +123,8 @@
         }
     }
 
-    void onRemoved(BiometricAuthenticator.Identifier identifier, int remaining)
+    /** Called when a user has been removed. */
+    public void onRemoved(BiometricAuthenticator.Identifier identifier, int remaining)
             throws RemoteException {
         if (mFaceServiceReceiver != null) {
             mFaceServiceReceiver.onRemoved((Face) identifier, remaining);
diff --git a/services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java b/services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java
index 25d4a38..5aa9b79 100644
--- a/services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java
@@ -173,18 +173,13 @@
     }
 
     // SensorType to AuthenticationClient map
-    private final Map<Integer, AuthenticationClient<?>> mClientMap;
-    @VisibleForTesting final LinkedList<SuccessfulAuth> mSuccessfulAuths;
+    private final Map<Integer, AuthenticationClient<?>> mClientMap = new HashMap<>();
+    @VisibleForTesting final LinkedList<SuccessfulAuth> mSuccessfulAuths = new LinkedList<>();
     private boolean mAdvancedLogicEnabled;
     private boolean mFaceHapticDisabledWhenNonBypass;
-    private final Handler mHandler;
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
 
-    private CoexCoordinator() {
-        // Singleton
-        mClientMap = new HashMap<>();
-        mSuccessfulAuths = new LinkedList<>();
-        mHandler = new Handler(Looper.getMainLooper());
-    }
+    private CoexCoordinator() {}
 
     public void addAuthenticationClient(@BiometricScheduler.SensorType int sensorType,
             @NonNull AuthenticationClient<?> client) {
@@ -221,8 +216,14 @@
     public void onAuthenticationSucceeded(long currentTimeMillis,
             @NonNull AuthenticationClient<?> client,
             @NonNull Callback callback) {
+        final boolean isUsingSingleModality = isSingleAuthOnly(client);
+
         if (client.isBiometricPrompt()) {
-            callback.sendHapticFeedback();
+            if (!isUsingSingleModality && hasMultipleSuccessfulAuthentications()) {
+                // only send feedback on the first one
+            } else {
+                callback.sendHapticFeedback();
+            }
             // For BP, BiometricService will add the authToken to Keystore.
             callback.sendAuthenticationResult(false /* addAuthTokenIfStrong */);
             callback.handleLifecycleAfterAuth();
@@ -234,7 +235,7 @@
             callback.sendAuthenticationResult(true /* addAuthTokenIfStrong */);
             callback.handleLifecycleAfterAuth();
         } else if (mAdvancedLogicEnabled && client.isKeyguard()) {
-            if (isSingleAuthOnly(client)) {
+            if (isUsingSingleModality) {
                 // Single sensor authentication
                 callback.sendHapticFeedback();
                 callback.sendAuthenticationResult(true /* addAuthTokenIfStrong */);
@@ -295,10 +296,10 @@
             @NonNull AuthenticationClient<?> client,
             @LockoutTracker.LockoutMode int lockoutMode,
             @NonNull Callback callback) {
-        final boolean keyguardAdvancedLogic = mAdvancedLogicEnabled && client.isKeyguard();
+        final boolean isUsingSingleModality = isSingleAuthOnly(client);
 
-        if (keyguardAdvancedLogic) {
-            if (isSingleAuthOnly(client)) {
+        if (mAdvancedLogicEnabled && client.isKeyguard()) {
+            if (isUsingSingleModality) {
                 callback.sendHapticFeedback();
                 callback.handleLifecycleAfterAuth();
             } else {
@@ -319,8 +320,7 @@
                         // also done now.
                         callback.sendHapticFeedback();
                         callback.handleLifecycleAfterAuth();
-                    }
-                    else {
+                    } else {
                         // UDFPS auth has never been attempted.
                         if (mFaceHapticDisabledWhenNonBypass && !face.isKeyguardBypassEnabled()) {
                             Slog.w(TAG, "Skipping face reject haptic");
@@ -360,6 +360,11 @@
                     callback.handleLifecycleAfterAuth();
                 }
             }
+        } else if (client.isBiometricPrompt() && !isUsingSingleModality) {
+            if (!isCurrentFaceAuth(client)) {
+                callback.sendHapticFeedback();
+            }
+            callback.handleLifecycleAfterAuth();
         } else {
             callback.sendHapticFeedback();
             callback.handleLifecycleAfterAuth();
@@ -380,6 +385,8 @@
      */
     public void onAuthenticationError(@NonNull AuthenticationClient<?> client,
             @BiometricConstants.Errors int error, @NonNull ErrorCallback callback) {
+        final boolean isUsingSingleModality = isSingleAuthOnly(client);
+
         // Figure out non-coex state
         final boolean shouldUsuallyVibrate;
         if (isCurrentFaceAuth(client)) {
@@ -401,25 +408,26 @@
         }
 
         // Figure out coex state
-        final boolean keyguardAdvancedLogic = mAdvancedLogicEnabled && client.isKeyguard();
         final boolean hapticSuppressedByCoex;
-
-        if (keyguardAdvancedLogic) {
-            if (isSingleAuthOnly(client)) {
+        if (mAdvancedLogicEnabled && client.isKeyguard()) {
+            if (isUsingSingleModality) {
                 hapticSuppressedByCoex = false;
             } else {
                 hapticSuppressedByCoex = isCurrentFaceAuth(client)
                         && !client.isKeyguardBypassEnabled();
             }
+        } else if (client.isBiometricPrompt() && !isUsingSingleModality) {
+            hapticSuppressedByCoex = isCurrentFaceAuth(client);
         } else {
             hapticSuppressedByCoex = false;
         }
 
         // Combine and send feedback if appropriate
-        Slog.d(TAG, "shouldUsuallyVibrate: " + shouldUsuallyVibrate
-                + ", hapticSuppressedByCoex: " + hapticSuppressedByCoex);
         if (shouldUsuallyVibrate && !hapticSuppressedByCoex) {
             callback.sendHapticFeedback();
+        } else {
+            Slog.v(TAG, "no haptic shouldUsuallyVibrate: " + shouldUsuallyVibrate
+                    + ", hapticSuppressedByCoex: " + hapticSuppressedByCoex);
         }
     }
 
@@ -504,6 +512,19 @@
         return true;
     }
 
+    private boolean hasMultipleSuccessfulAuthentications() {
+        int count = 0;
+        for (AuthenticationClient<?> c : mClientMap.values()) {
+            if (c.wasAuthSuccessful()) {
+                count++;
+            }
+            if (count > 1) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
diff --git a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
index 07ce841..e0d5194 100644
--- a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.BiometricConstants;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
@@ -71,6 +72,24 @@
 
     @Override
     public void onRemoved(@NonNull BiometricAuthenticator.Identifier identifier, int remaining) {
+        // This happens when we have failed to remove a biometric.
+        if (identifier == null) {
+            Slog.e(TAG, "identifier was null, skipping onRemove()");
+            try {
+                if (getListener() != null) {
+                    getListener().onError(getSensorId(), getCookie(),
+                            BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_REMOVE,
+                            0 /* vendorCode */);
+                } else {
+                    Slog.e(TAG, "Error, listener was null, not sending onError callback");
+                }
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Failed to send error to client for onRemoved", e);
+            }
+            mCallback.onClientFinished(this, false /* success */);
+            return;
+        }
+
         Slog.d(TAG, "onRemoved: " + identifier.getBiometricId() + " remaining: " + remaining);
         mBiometricUtils.removeBiometricForUser(getContext(), getTargetUserId(),
                 identifier.getBiometricId());
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 9bfdd68..66e9da0 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -2579,7 +2579,7 @@
 
         boolean getAllowNonNativeRefreshRateOverride() {
             return DisplayProperties
-                    .debug_allow_non_native_refresh_rate_override().orElse(false);
+                    .debug_allow_non_native_refresh_rate_override().orElse(true);
         }
 
         @NonNull
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index c15242a..140a28f 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -144,6 +144,7 @@
 import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
+import java.util.OptionalInt;
 
 /*
  * Wraps the C++ InputManager and provides its callbacks.
@@ -2915,48 +2916,17 @@
 
     // Native callback
     @SuppressWarnings("unused")
-    private void notifyWindowUnresponsive(IBinder token, String reason) {
-        int gestureMonitorPid = -1;
-        synchronized (mInputMonitors) {
-            final GestureMonitorSpyWindow gestureMonitor = mInputMonitors.get(token);
-            if (gestureMonitor != null) {
-                gestureMonitorPid = gestureMonitor.mWindowHandle.ownerPid;
-            }
-        }
-        if (gestureMonitorPid != -1) {
-            mWindowManagerCallbacks.notifyGestureMonitorUnresponsive(gestureMonitorPid, reason);
-            return;
-        }
-        mWindowManagerCallbacks.notifyWindowUnresponsive(token, reason);
+    private void notifyWindowUnresponsive(IBinder token, int pid, boolean isPidValid,
+            String reason) {
+        mWindowManagerCallbacks.notifyWindowUnresponsive(token,
+                isPidValid ? OptionalInt.of(pid) : OptionalInt.empty(), reason);
     }
 
     // Native callback
     @SuppressWarnings("unused")
-    private void notifyMonitorUnresponsive(int pid, String reason) {
-        mWindowManagerCallbacks.notifyGestureMonitorUnresponsive(pid, reason);
-    }
-
-    // Native callback
-    @SuppressWarnings("unused")
-    private void notifyWindowResponsive(IBinder token) {
-        int gestureMonitorPid = -1;
-        synchronized (mInputMonitors) {
-            final GestureMonitorSpyWindow gestureMonitor = mInputMonitors.get(token);
-            if (gestureMonitor != null) {
-                gestureMonitorPid = gestureMonitor.mWindowHandle.ownerPid;
-            }
-        }
-        if (gestureMonitorPid != -1) {
-            mWindowManagerCallbacks.notifyGestureMonitorResponsive(gestureMonitorPid);
-            return;
-        }
-        mWindowManagerCallbacks.notifyWindowResponsive(token);
-    }
-
-    // Native callback
-    @SuppressWarnings("unused")
-    private void notifyMonitorResponsive(int pid) {
-        mWindowManagerCallbacks.notifyGestureMonitorResponsive(pid);
+    private void notifyWindowResponsive(IBinder token, int pid, boolean isPidValid) {
+        mWindowManagerCallbacks.notifyWindowResponsive(token,
+                isPidValid ? OptionalInt.of(pid) : OptionalInt.empty());
     }
 
     // Native callback.
@@ -3329,34 +3299,22 @@
         void notifyNoFocusedWindowAnr(InputApplicationHandle applicationHandle);
 
         /**
-         * Notify the window manager about a gesture monitor that is unresponsive.
-         *
-         * @param pid the pid of the gesture monitor process
-         * @param reason the reason why this connection is unresponsive
-         */
-        void notifyGestureMonitorUnresponsive(int pid, @NonNull String reason);
-
-        /**
          * Notify the window manager about a window that is unresponsive.
          *
          * @param token the token that can be used to look up the window
+         * @param pid the pid of the window owner, if known
          * @param reason the reason why this connection is unresponsive
          */
-        void notifyWindowUnresponsive(@NonNull IBinder token, @NonNull String reason);
-
-        /**
-         * Notify the window manager about a gesture monitor that has become responsive.
-         *
-         * @param pid the pid of the gesture monitor process
-         */
-        void notifyGestureMonitorResponsive(int pid);
+        void notifyWindowUnresponsive(@NonNull IBinder token, @NonNull OptionalInt pid,
+                @NonNull String reason);
 
         /**
          * Notify the window manager about a window that has become responsive.
          *
          * @param token the token that can be used to look up the window
+         * @param pid the pid of the window owner, if known
          */
-        void notifyWindowResponsive(@NonNull IBinder token);
+        void notifyWindowResponsive(@NonNull IBinder token, @NonNull OptionalInt pid);
 
         /**
          * This callback is invoked when an event first arrives to InputDispatcher and before it is
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java b/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java
index a102406..1203769 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java
@@ -262,7 +262,7 @@
         long nextAlarmTime = strongAuthTime + dpm.getRequiredStrongAuthTimeout(null, userId);
 
         // schedule a new alarm listener for the user
-        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, nextAlarmTime,
+        mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextAlarmTime,
                 STRONG_AUTH_TIMEOUT_ALARM_TAG, alarm, mHandler);
     }
 
@@ -303,7 +303,7 @@
             alarm = new NonStrongBiometricTimeoutAlarmListener(userId);
             mNonStrongBiometricTimeoutAlarmListener.put(userId, alarm);
             // schedule a new alarm listener for the user
-            mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, nextAlarmTime,
+            mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextAlarmTime,
                     NON_STRONG_BIOMETRIC_TIMEOUT_ALARM_TAG, alarm, mHandler);
         }
 
@@ -394,7 +394,7 @@
         }
         // schedule a new alarm listener for the user
         if (DEBUG) Slog.d(TAG, "Schedule a new alarm for non-strong biometric idle timeout");
-        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, nextAlarmTime,
+        mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextAlarmTime,
                 NON_STRONG_BIOMETRIC_IDLE_TIMEOUT_ALARM_TAG, alarm, mHandler);
     }
 
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 30ac1b8..0c9855b 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -2161,8 +2161,8 @@
     private String[] getPackagesForUidInternal(int uid, int callingUid) {
         final boolean isCallerInstantApp = getInstantAppPackageName(callingUid) != null;
         final int userId = UserHandle.getUserId(uid);
-        if (Process.isSupplemental(uid)) {
-            uid = getSupplementalProcessUid();
+        if (Process.isSdkSandboxUid(uid)) {
+            uid = getBaseSdkSandboxUid();
         }
         final int appId = UserHandle.getAppId(uid);
         return getPackagesForUidInternalBody(callingUid, userId, appId, isCallerInstantApp);
@@ -2401,9 +2401,9 @@
     }
 
     public final boolean isCallerSameApp(String packageName, int uid) {
-        if (Process.isSupplemental(uid)) {
+        if (Process.isSdkSandboxUid(uid)) {
             return (packageName != null
-                    && packageName.equals(mService.getSupplementalProcessPackageName()));
+                    && packageName.equals(mService.getSdkSandboxPackageName()));
         }
         AndroidPackage pkg = mPackages.get(packageName);
         return pkg != null
@@ -2812,8 +2812,8 @@
                     "MATCH_ANY_USER flag requires INTERACT_ACROSS_USERS permission");
         } else if ((flags & PackageManager.MATCH_UNINSTALLED_PACKAGES) != 0
                 && isCallerSystemUser
-                && mUserManager.hasManagedProfile(UserHandle.USER_SYSTEM)) {
-            // If the caller wants all packages and has a restricted profile associated with it,
+                && mUserManager.hasProfile(UserHandle.USER_SYSTEM)) {
+            // If the caller wants all packages and has a profile associated with it,
             // then match all users. This is to make sure that launchers that need to access
             //work
             // profile apps don't start breaking. TODO: Remove this hack when launchers stop
@@ -4326,8 +4326,8 @@
         if (getInstantAppPackageName(callingUid) != null) {
             return null;
         }
-        if (Process.isSupplemental(uid)) {
-            uid = getSupplementalProcessUid();
+        if (Process.isSdkSandboxUid(uid)) {
+            uid = getBaseSdkSandboxUid();
         }
         final int callingUserId = UserHandle.getUserId(callingUid);
         final int appId = UserHandle.getAppId(uid);
@@ -4362,8 +4362,8 @@
         final String[] names = new String[uids.length];
         for (int i = uids.length - 1; i >= 0; i--) {
             int uid = uids[i];
-            if (Process.isSupplemental(uid)) {
-                uid = getSupplementalProcessUid();
+            if (Process.isSdkSandboxUid(uid)) {
+                uid = getBaseSdkSandboxUid();
             }
             final int appId = UserHandle.getAppId(uid);
             final Object obj = mSettings.getSettingBase(appId);
@@ -4411,8 +4411,8 @@
         if (getInstantAppPackageName(callingUid) != null) {
             return 0;
         }
-        if (Process.isSupplemental(uid)) {
-            uid = getSupplementalProcessUid();
+        if (Process.isSdkSandboxUid(uid)) {
+            uid = getBaseSdkSandboxUid();
         }
         final int callingUserId = UserHandle.getUserId(callingUid);
         final int appId = UserHandle.getAppId(uid);
@@ -4439,8 +4439,8 @@
         if (getInstantAppPackageName(callingUid) != null) {
             return 0;
         }
-        if (Process.isSupplemental(uid)) {
-            uid = getSupplementalProcessUid();
+        if (Process.isSdkSandboxUid(uid)) {
+            uid = getBaseSdkSandboxUid();
         }
         final int callingUserId = UserHandle.getUserId(callingUid);
         final int appId = UserHandle.getAppId(uid);
@@ -4466,8 +4466,8 @@
         if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
             return false;
         }
-        if (Process.isSupplemental(uid)) {
-            uid = getSupplementalProcessUid();
+        if (Process.isSdkSandboxUid(uid)) {
+            uid = getBaseSdkSandboxUid();
         }
         final int appId = UserHandle.getAppId(uid);
         final Object obj = mSettings.getSettingBase(appId);
@@ -5597,8 +5597,8 @@
 
     @Override
     public int getUidTargetSdkVersion(int uid) {
-        if (Process.isSupplemental(uid)) {
-            uid = getSupplementalProcessUid();
+        if (Process.isSdkSandboxUid(uid)) {
+            uid = getBaseSdkSandboxUid();
         }
         final int appId = UserHandle.getAppId(uid);
         final SettingBase settingBase = mSettings.getSettingBase(appId);
@@ -5628,8 +5628,8 @@
     @Nullable
     @Override
     public ArrayMap<String, ProcessInfo> getProcessesForUid(int uid) {
-        if (Process.isSupplemental(uid)) {
-            uid = getSupplementalProcessUid();
+        if (Process.isSdkSandboxUid(uid)) {
+            uid = getBaseSdkSandboxUid();
         }
         final int appId = UserHandle.getAppId(uid);
         final SettingBase settingBase = mSettings.getSettingBase(appId);
@@ -5661,8 +5661,8 @@
         }
     }
 
-    private int getSupplementalProcessUid() {
-        return getPackage(mService.getSupplementalProcessPackageName()).getUid();
+    private int getBaseSdkSandboxUid() {
+        return getPackage(mService.getSdkSandboxPackageName()).getUid();
     }
 
     @Nullable
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index 53eb9cf..c832693 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -460,7 +460,8 @@
                 | DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES
                 | DexoptOptions.DEXOPT_BOOT_COMPLETE
                 | (force ? DexoptOptions.DEXOPT_FORCE : 0);
-        return performDexOpt(new DexoptOptions(packageName, compilerFilter, flags));
+        return performDexOpt(new DexoptOptions(packageName, REASON_CMDLINE,
+                compilerFilter, null /* splitName */, flags));
     }
 
     // Sort apps by importance for dexopt ordering. Important apps are given
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 54c2019..ceab925 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -2893,9 +2893,13 @@
             }
         }
 
-        final boolean deferInstallObserver = succeeded && update && !killApp;
+        final boolean deferInstallObserver = succeeded && update;
         if (deferInstallObserver) {
-            mPm.scheduleDeferredNoKillInstallObserver(res, installObserver);
+            if (killApp) {
+                mPm.scheduleDeferredPendingKillInstallObserver(res, installObserver);
+            } else {
+                mPm.scheduleDeferredNoKillInstallObserver(res, installObserver);
+            }
         } else {
             mPm.notifyInstallObserver(res, installObserver);
         }
diff --git a/services/core/java/com/android/server/pm/PackageHandler.java b/services/core/java/com/android/server/pm/PackageHandler.java
index b028a2c..e8faca9 100644
--- a/services/core/java/com/android/server/pm/PackageHandler.java
+++ b/services/core/java/com/android/server/pm/PackageHandler.java
@@ -24,6 +24,7 @@
 import static com.android.server.pm.PackageManagerService.DEFAULT_UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD;
 import static com.android.server.pm.PackageManagerService.DEFERRED_NO_KILL_INSTALL_OBSERVER;
 import static com.android.server.pm.PackageManagerService.DEFERRED_NO_KILL_POST_DELETE;
+import static com.android.server.pm.PackageManagerService.DEFERRED_PENDING_KILL_INSTALL_OBSERVER;
 import static com.android.server.pm.PackageManagerService.DOMAIN_VERIFICATION;
 import static com.android.server.pm.PackageManagerService.ENABLE_ROLLBACK_STATUS;
 import static com.android.server.pm.PackageManagerService.ENABLE_ROLLBACK_TIMEOUT;
@@ -126,10 +127,12 @@
                     }
                 }
             } break;
-            case DEFERRED_NO_KILL_INSTALL_OBSERVER: {
-                String packageName = (String) msg.obj;
+            case DEFERRED_NO_KILL_INSTALL_OBSERVER:
+            case DEFERRED_PENDING_KILL_INSTALL_OBSERVER: {
+                final String packageName = (String) msg.obj;
                 if (packageName != null) {
-                    mPm.notifyInstallObserver(packageName);
+                    final boolean killApp = msg.what == DEFERRED_PENDING_KILL_INSTALL_OBSERVER;
+                    mPm.notifyInstallObserver(packageName, killApp);
                 }
             } break;
             case WRITE_SETTINGS: {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index c05faf1..6fbad24 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -247,8 +247,8 @@
 import com.android.server.pm.verify.domain.DomainVerificationService;
 import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy;
 import com.android.server.pm.verify.domain.proxy.DomainVerificationProxyV1;
+import com.android.server.sdksandbox.SdkSandboxManagerLocal;
 import com.android.server.storage.DeviceStorageMonitorInternal;
-import com.android.server.supplementalprocess.SupplementalProcessManagerLocal;
 import com.android.server.utils.SnapshotCache;
 import com.android.server.utils.TimingsTraceAndSlog;
 import com.android.server.utils.Watchable;
@@ -840,6 +840,9 @@
     private final Map<String, Pair<PackageInstalledInfo, IPackageInstallObserver2>>
             mNoKillInstallObservers = Collections.synchronizedMap(new HashMap<>());
 
+    private final Map<String, Pair<PackageInstalledInfo, IPackageInstallObserver2>>
+            mPendingKillInstallObservers = Collections.synchronizedMap(new HashMap<>());
+
     // Internal interface for permission manager
     final PermissionManagerServiceInternal mPermissionManager;
 
@@ -887,9 +890,11 @@
     static final int CHECK_PENDING_INTEGRITY_VERIFICATION = 26;
     static final int DOMAIN_VERIFICATION = 27;
     static final int PRUNE_UNUSED_STATIC_SHARED_LIBRARIES = 28;
+    static final int DEFERRED_PENDING_KILL_INSTALL_OBSERVER = 29;
 
     static final int DEFERRED_NO_KILL_POST_DELETE_DELAY_MS = 3 * 1000;
     private static final int DEFERRED_NO_KILL_INSTALL_OBSERVER_DELAY_MS = 500;
+    private static final int DEFERRED_PENDING_KILL_INSTALL_OBSERVER_DELAY_MS = 1000;
 
     static final int WRITE_SETTINGS_DELAY = 10*1000;  // 10 seconds
 
@@ -934,7 +939,7 @@
     final @Nullable String mOverlayConfigSignaturePackage;
     final @Nullable String mRecentsPackage;
     final @Nullable String mAmbientContextDetectionPackage;
-    private final @NonNull String mRequiredSupplementalProcessPackage;
+    private final @NonNull String mRequiredSdkSandboxPackage;
 
     @GuardedBy("mLock")
     private final PackageUsage mPackageUsage = new PackageUsage();
@@ -1166,13 +1171,14 @@
         Computer computer = snapshotComputer();
         ArraySet<String> packagesToNotify = computer.getNotifyPackagesForReplacedReceived(packages);
         for (int index = 0; index < packagesToNotify.size(); index++) {
-            notifyInstallObserver(packagesToNotify.valueAt(index));
+            notifyInstallObserver(packagesToNotify.valueAt(index), false /* killApp */);
         }
     }
 
-    void notifyInstallObserver(String packageName) {
-        Pair<PackageInstalledInfo, IPackageInstallObserver2> pair =
-                mNoKillInstallObservers.remove(packageName);
+    void notifyInstallObserver(String packageName, boolean killApp) {
+        final Pair<PackageInstalledInfo, IPackageInstallObserver2> pair =
+                killApp ? mPendingKillInstallObservers.remove(packageName)
+                        : mNoKillInstallObservers.remove(packageName);
 
         if (pair != null) {
             notifyInstallObserver(pair.first, pair.second);
@@ -1211,6 +1217,15 @@
                 delay ? getPruneUnusedSharedLibrariesDelay() : 0);
     }
 
+    void scheduleDeferredPendingKillInstallObserver(PackageInstalledInfo info,
+            IPackageInstallObserver2 observer) {
+        final String packageName = info.mPkg.getPackageName();
+        mPendingKillInstallObservers.put(packageName, Pair.create(info, observer));
+        final Message message = mHandler.obtainMessage(DEFERRED_PENDING_KILL_INSTALL_OBSERVER,
+                packageName);
+        mHandler.sendMessageDelayed(message, DEFERRED_PENDING_KILL_INSTALL_OBSERVER_DELAY_MS);
+    }
+
     private static long getPruneUnusedSharedLibrariesDelay() {
         return SystemProperties.getLong("debug.pm.prune_unused_shared_libraries_delay",
                 PRUNE_UNUSED_SHARED_LIBRARIES_DELAY);
@@ -1667,7 +1682,7 @@
         mSharedSystemSharedLibraryPackageName = testParams.sharedSystemSharedLibraryPackageName;
         mOverlayConfigSignaturePackage = testParams.overlayConfigSignaturePackage;
         mResolveComponentName = testParams.resolveComponentName;
-        mRequiredSupplementalProcessPackage = testParams.requiredSupplementalProcessPackage;
+        mRequiredSdkSandboxPackage = testParams.requiredSdkSandboxPackage;
 
         mLiveComputer = createLiveComputer();
         mSnapshotComputer = null;
@@ -2055,8 +2070,6 @@
                 }
             }
 
-            mPrepareAppDataFuture = mAppDataHelper.fixAppsDataOnBoot();
-
             // If this is first boot after an OTA, and a normal boot, then
             // we need to clear code cache directories.
             // Note that we do *not* clear the application profiles. These remain valid
@@ -2077,6 +2090,9 @@
                 ver.fingerprint = PackagePartitions.FINGERPRINT;
             }
 
+            // Defer the app data fixup until we are done with app data clearing above.
+            mPrepareAppDataFuture = mAppDataHelper.fixAppsDataOnBoot();
+
             // Legacy existing (installed before Q) non-system apps to hide
             // their icons in launcher.
             if (!mOnlyCore && mIsPreQUpgrade) {
@@ -2141,8 +2157,8 @@
                     getPackageInfo(mRequiredPermissionControllerPackage, 0,
                             UserHandle.USER_SYSTEM).getLongVersionCode());
 
-            // Resolve the supplemental process
-            mRequiredSupplementalProcessPackage = getRequiredSupplementalProcessPackageName();
+            // Resolve the sdk sandbox package
+            mRequiredSdkSandboxPackage = getRequiredSdkSandboxPackageName();
 
             // Initialize InstantAppRegistry's Instant App list for all users.
             for (AndroidPackage pkg : mPackages.values()) {
@@ -2557,8 +2573,9 @@
         try {
             return super.onTransact(code, data, reply, flags);
         } catch (RuntimeException e) {
-            if (!(e instanceof SecurityException) && !(e instanceof IllegalArgumentException)) {
-                Slog.wtf(TAG, "Package Manager Crash", e);
+            if (!(e instanceof SecurityException) && !(e instanceof IllegalArgumentException)
+                    && !(e instanceof ParcelableException)) {
+                Slog.wtf(TAG, "Package Manager Unexpected Exception", e);
             }
             throw e;
         }
@@ -3143,8 +3160,8 @@
     }
 
     @Override
-    public String getSupplementalProcessPackageName() {
-        return mRequiredSupplementalProcessPackage;
+    public String getSdkSandboxPackageName() {
+        return mRequiredSdkSandboxPackage;
     }
 
     String getPackageInstallerPackageName() {
@@ -5458,8 +5475,8 @@
         }
     }
 
-    private @NonNull String getRequiredSupplementalProcessPackageName() {
-        final Intent intent = new Intent(SupplementalProcessManagerLocal.SERVICE_INTERFACE);
+    private @NonNull String getRequiredSdkSandboxPackageName() {
+        final Intent intent = new Intent(SdkSandboxManagerLocal.SERVICE_INTERFACE);
 
         final List<ResolveInfo> matches = queryIntentServicesInternal(
                 intent,
@@ -5471,7 +5488,7 @@
         if (matches.size() == 1) {
             return matches.get(0).getComponentInfo().packageName;
         } else {
-            throw new RuntimeException("There should exactly one supplemental process; found "
+            throw new RuntimeException("There should exactly one sdk sandbox package; found "
                     + matches.size() + ": matches=" + matches);
         }
     }
@@ -6675,6 +6692,11 @@
 
     @Override
     public IPackageInstaller getPackageInstaller() {
+        // Return installer service for internal calls.
+        if (PackageManagerServiceUtils.isSystemOrRoot()) {
+            return mInstallerService;
+        }
+        // Return null for InstantApps.
         if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
             return null;
         }
@@ -7393,6 +7415,12 @@
         }
 
         @Override
+        public void onPackageProcessKilledForUninstall(String packageName) {
+            mHandler.post(() -> PackageManagerService.this.notifyInstallObserver(packageName,
+                    true /* killApp */));
+        }
+
+        @Override
         public SparseArray<String> getAppsWithSharedUserIds() {
             return mComputer.getAppsWithSharedUserIds();
         }
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
index d12c826..5bdda0b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
@@ -89,7 +89,7 @@
     public @Nullable String defaultTextClassifierPackage;
     public @Nullable String systemTextClassifierPackage;
     public @Nullable String overlayConfigSignaturePackage;
-    public @NonNull String requiredSupplementalProcessPackage;
+    public @NonNull String requiredSdkSandboxPackage;
     public ViewCompiler viewCompiler;
     public @Nullable String retailDemoPackage;
     public @Nullable String recentsPackage;
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index a9471cf..8d3fbf7 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -1250,6 +1250,14 @@
     }
 
     /**
+     * Check if the Binder caller is system UID or root's UID.
+     */
+    public static boolean isSystemOrRoot() {
+        final int uid = Binder.getCallingUid();
+        return uid == Process.SYSTEM_UID || uid == Process.ROOT_UID;
+    }
+
+    /**
      * Enforces that only the system UID or root's UID can call a method exposed
      * via Binder.
      *
@@ -1257,8 +1265,7 @@
      * @throws SecurityException if the caller is not system or root
      */
     public static void enforceSystemOrRoot(String message) {
-        final int uid = Binder.getCallingUid();
-        if (uid != Process.SYSTEM_UID && uid != Process.ROOT_UID) {
+        if (!isSystemOrRoot()) {
             throw new SecurityException(message);
         }
     }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index b7c55c5..fed214f 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -6263,11 +6263,11 @@
     }
 
     /**
-     * Checks if the given user has a managed profile associated with it.
+     * Checks if the given user has a profile associated with it.
      * @param userId The parent user
      * @return
      */
-    boolean hasManagedProfile(@UserIdInt int userId) {
+    boolean hasProfile(@UserIdInt int userId) {
         synchronized (mUsersLock) {
             UserInfo userInfo = getUserInfoLU(userId);
             final int userSize = mUsers.size();
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 49553f4..36633cc 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -1021,7 +1021,8 @@
         }
         for (String packageName : packageNames) {
             grantPermissionsToSystemPackage(NO_PM_CACHE, packageName, userId,
-                    PHONE_PERMISSIONS, ALWAYS_LOCATION_PERMISSIONS, SMS_PERMISSIONS);
+                    PHONE_PERMISSIONS, ALWAYS_LOCATION_PERMISSIONS, SMS_PERMISSIONS,
+                    NOTIFICATION_PERMISSIONS);
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/pkg/AndroidPackageApi.java b/services/core/java/com/android/server/pm/pkg/AndroidPackageApi.java
index d3c8c7b..a0bb8dc 100644
--- a/services/core/java/com/android/server/pm/pkg/AndroidPackageApi.java
+++ b/services/core/java/com/android/server/pm/pkg/AndroidPackageApi.java
@@ -268,6 +268,9 @@
     List<FeatureGroupInfo> getFeatureGroups();
 
     @NonNull
+    List<String> getImplicitPermissions();
+
+    @NonNull
     List<ParsedInstrumentation> getInstrumentations();
 
     long getLongVersionCode();
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index c637c67..f2ce0d4 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -34,6 +34,7 @@
 import android.app.ActivityTaskManager;
 import android.app.AppOpsManager;
 import android.app.AppOpsManagerInternal;
+import android.app.KeyguardManager;
 import android.app.TaskInfo;
 import android.app.compat.CompatChanges;
 import android.compat.annotation.ChangeId;
@@ -150,6 +151,7 @@
     private Context mContext;
     private PackageManagerInternal mPackageManagerInternal;
     private NotificationManagerInternal mNotificationManager;
+    private final KeyguardManager mKeyguardManager;
     private final PackageManager mPackageManager;
 
     public PermissionPolicyService(@NonNull Context context) {
@@ -157,6 +159,7 @@
 
         mContext = context;
         mPackageManager = context.getPackageManager();
+        mKeyguardManager = context.getSystemService(KeyguardManager.class);
         LocalServices.addService(PermissionPolicyInternal.class, new Internal());
     }
 
@@ -1046,12 +1049,21 @@
                     }
 
                     @Override
-                    public void onActivityLaunched(TaskInfo taskInfo, ActivityInfo activityInfo) {
-                        super.onActivityLaunched(taskInfo, activityInfo);
-                        clearNotificationReviewFlagsIfNeeded(activityInfo.packageName,
-                                UserHandle.of(taskInfo.userId));
-                        showNotificationPromptIfNeeded(activityInfo.packageName,
-                                taskInfo.userId, taskInfo.taskId);
+                    public void onActivityLaunched(TaskInfo taskInfo, ActivityInfo activityInfo,
+                            ActivityInterceptorInfo info) {
+                        super.onActivityLaunched(taskInfo, activityInfo, info);
+                        if (!shouldShowNotificationDialogOrClearFlags(info.intent,
+                                info.checkedOptions)) {
+                            return;
+                        }
+                        UserHandle user = UserHandle.of(taskInfo.userId);
+                        if (CompatChanges.isChangeEnabled(NOTIFICATION_PERM_CHANGE_ID,
+                                activityInfo.packageName, user)) {
+                            clearNotificationReviewFlagsIfNeeded(activityInfo.packageName, user);
+                        } else {
+                            showNotificationPromptIfNeeded(activityInfo.packageName,
+                                    taskInfo.userId, taskInfo.taskId);
+                        }
                     }
                 };
 
@@ -1092,10 +1104,28 @@
             launchNotificationPermissionRequestDialog(packageName, user, taskId);
         }
 
+        /**
+         * Determine if we should show a notification dialog, or clear the REVIEW_REQUIRED flag,
+         * from a particular package for a particular intent. Returns true if:
+         * 1. The isEligibleForLegacyPermissionPrompt ActivityOption is set, or
+         * 2. The intent is a launcher intent (action is ACTION_MAIN, category is LAUNCHER)
+         */
+        private boolean shouldShowNotificationDialogOrClearFlags(Intent intent,
+                ActivityOptions options) {
+            if ((options != null && options.isEligibleForLegacyPermissionPrompt())) {
+                return true;
+            }
+
+            return Intent.ACTION_MAIN.equals(intent.getAction())
+                    && intent.getCategories() != null
+                    && (intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)
+                    || intent.getCategories().contains(Intent.CATEGORY_LEANBACK_LAUNCHER)
+                    || intent.getCategories().contains(Intent.CATEGORY_CAR_LAUNCHER));
+        }
+
         private void clearNotificationReviewFlagsIfNeeded(String packageName, UserHandle user) {
-            if (!CompatChanges.isChangeEnabled(NOTIFICATION_PERM_CHANGE_ID, packageName, user)
-                    || ((mPackageManager.getPermissionFlags(POST_NOTIFICATIONS, packageName, user)
-                    & FLAG_PERMISSION_REVIEW_REQUIRED) == 0)) {
+            if ((mPackageManager.getPermissionFlags(POST_NOTIFICATIONS, packageName, user)
+                    & FLAG_PERMISSION_REVIEW_REQUIRED) == 0) {
                 return;
             }
             try {
@@ -1210,8 +1240,8 @@
             }
 
             if (!pkg.getRequestedPermissions().contains(POST_NOTIFICATIONS)
-                    || CompatChanges.isChangeEnabled(NOTIFICATION_PERM_CHANGE_ID,
-                    pkg.getPackageName(), user)) {
+                    || CompatChanges.isChangeEnabled(NOTIFICATION_PERM_CHANGE_ID, pkgName, user)
+                    || mKeyguardManager.isKeyguardLocked()) {
                 return false;
             }
 
@@ -1220,7 +1250,7 @@
                 mNotificationManager = LocalServices.getService(NotificationManagerInternal.class);
             }
             boolean hasCreatedNotificationChannels = mNotificationManager
-                    .getNumNotificationChannelsForPackage(pkg.getPackageName(), uid, true) > 0;
+                    .getNumNotificationChannelsForPackage(pkgName, uid, true) > 0;
             int flags = mPackageManager.getPermissionFlags(POST_NOTIFICATIONS, pkgName, user);
             boolean explicitlySet = (flags & PermissionManager.EXPLICIT_SET_FLAGS) != 0;
             boolean needsReview = (flags & FLAG_PERMISSION_REVIEW_REQUIRED) != 0;
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index bd58472..914e5ec 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -1176,14 +1176,14 @@
 
     @Override
     public void onBootPhase(int phase) {
-        synchronized (mLock) {
-            if (phase == PHASE_SYSTEM_SERVICES_READY) {
-                systemReady();
+        if (phase == PHASE_SYSTEM_SERVICES_READY) {
+            systemReady();
 
-            } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
-                incrementBootCount();
+        } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
+            incrementBootCount();
 
-            } else if (phase == PHASE_BOOT_COMPLETED) {
+        } else if (phase == PHASE_BOOT_COMPLETED) {
+            synchronized (mLock) {
                 final long now = mClock.uptimeMillis();
                 mBootCompleted = true;
                 mDirty |= DIRTY_BOOT_COMPLETED;
diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java
index 2491565d..8755662 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -27,6 +27,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
@@ -51,8 +52,13 @@
     private static final boolean DEBUG = false;
     @VisibleForTesting final long mHintSessionPreferredRate;
 
+    // Multi-levle map storing all active AppHintSessions.
+    // First level is keyed by the UID of the client process creating the session.
+    // Second level is keyed by an IBinder passed from client process. This is used to observe
+    // when the process exits. The client generally uses the same IBinder object across multiple
+    // sessions, so the value is a set of AppHintSessions.
     @GuardedBy("mLock")
-    private final ArrayMap<Integer, ArrayMap<IBinder, AppHintSession>> mActiveSessions;
+    private final ArrayMap<Integer, ArrayMap<IBinder, ArraySet<AppHintSession>>> mActiveSessions;
 
     /** Lock to protect HAL handles and listen list. */
     private final Object mLock = new Object();
@@ -201,13 +207,16 @@
         public void onUidGone(int uid, boolean disabled) {
             FgThread.getHandler().post(() -> {
                 synchronized (mLock) {
-                    ArrayMap<IBinder, AppHintSession> tokenMap = mActiveSessions.get(uid);
+                    ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = mActiveSessions.get(uid);
                     if (tokenMap == null) {
                         return;
                     }
                     for (int i = tokenMap.size() - 1; i >= 0; i--) {
                         // Will remove the session from tokenMap
-                        tokenMap.valueAt(i).close();
+                        ArraySet<AppHintSession> sessionSet = tokenMap.valueAt(i);
+                        for (int j = sessionSet.size() - 1; j >= 0; j--) {
+                            sessionSet.valueAt(j).close();
+                        }
                     }
                     mProcStatesCache.delete(uid);
                 }
@@ -231,12 +240,14 @@
             FgThread.getHandler().post(() -> {
                 synchronized (mLock) {
                     mProcStatesCache.put(uid, procState);
-                    ArrayMap<IBinder, AppHintSession> tokenMap = mActiveSessions.get(uid);
+                    ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = mActiveSessions.get(uid);
                     if (tokenMap == null) {
                         return;
                     }
-                    for (AppHintSession s : tokenMap.values()) {
-                        s.onProcStateChanged();
+                    for (ArraySet<AppHintSession> sessionSet : tokenMap.values()) {
+                        for (AppHintSession s : sessionSet) {
+                            s.onProcStateChanged();
+                        }
                     }
                 }
             });
@@ -305,17 +316,25 @@
 
                 long halSessionPtr = mNativeWrapper.halCreateHintSession(callingTgid, callingUid,
                         tids, durationNanos);
-                if (halSessionPtr == 0) return null;
+                if (halSessionPtr == 0) {
+                    return null;
+                }
 
                 AppHintSession hs = new AppHintSession(callingUid, callingTgid, tids, token,
                         halSessionPtr, durationNanos);
                 synchronized (mLock) {
-                    ArrayMap<IBinder, AppHintSession> tokenMap = mActiveSessions.get(callingUid);
+                    ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap =
+                            mActiveSessions.get(callingUid);
                     if (tokenMap == null) {
                         tokenMap = new ArrayMap<>(1);
                         mActiveSessions.put(callingUid, tokenMap);
                     }
-                    tokenMap.put(token, hs);
+                    ArraySet<AppHintSession> sessionSet = tokenMap.get(token);
+                    if (sessionSet == null) {
+                        sessionSet = new ArraySet<>(1);
+                        tokenMap.put(token, sessionSet);
+                    }
+                    sessionSet.add(hs);
                     return hs;
                 }
             } finally {
@@ -339,10 +358,14 @@
                 pw.println("Active Sessions:");
                 for (int i = 0; i < mActiveSessions.size(); i++) {
                     pw.println("Uid " + mActiveSessions.keyAt(i).toString() + ":");
-                    ArrayMap<IBinder, AppHintSession> tokenMap = mActiveSessions.valueAt(i);
+                    ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap =
+                            mActiveSessions.valueAt(i);
                     for (int j = 0; j < tokenMap.size(); j++) {
-                        pw.println("  Session " + j + ":");
-                        tokenMap.valueAt(j).dump(pw, "    ");
+                        ArraySet<AppHintSession> sessionSet = tokenMap.valueAt(j);
+                        for (int k = 0; k < sessionSet.size(); ++k) {
+                            pw.println("  Session:");
+                            sessionSet.valueAt(k).dump(pw, "    ");
+                        }
                     }
                 }
             }
@@ -432,11 +455,18 @@
                 mNativeWrapper.halCloseHintSession(mHalSessionPtr);
                 mHalSessionPtr = 0;
                 mToken.unlinkToDeath(this, 0);
-                ArrayMap<IBinder, AppHintSession> tokenMap = mActiveSessions.get(mUid);
+                ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = mActiveSessions.get(mUid);
                 if (tokenMap == null) {
-                    Slogf.w(TAG, "UID %d is note present in active session map", mUid);
+                    Slogf.w(TAG, "UID %d is not present in active session map", mUid);
+                    return;
                 }
-                tokenMap.remove(mToken);
+                ArraySet<AppHintSession> sessionSet = tokenMap.get(mToken);
+                if (sessionSet == null) {
+                    Slogf.w(TAG, "Token %s is not present in token map", mToken.toString());
+                    return;
+                }
+                sessionSet.remove(this);
+                if (sessionSet.isEmpty()) tokenMap.remove(mToken);
                 if (tokenMap.isEmpty()) mActiveSessions.remove(mUid);
             }
         }
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index 411f3dc..11fd99c 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -157,7 +157,7 @@
      * @see com.android.internal.statusbar.IStatusBar#requestWindowMagnificationConnection(boolean
      * request)
      */
-    void requestWindowMagnificationConnection(boolean request);
+    boolean requestWindowMagnificationConnection(boolean request);
 
     /**
      * Handles a logging command from the WM shell command.
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index e4a969b..59b9daf 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -637,12 +637,14 @@
         }
 
         @Override
-        public void requestWindowMagnificationConnection(boolean request) {
+        public boolean requestWindowMagnificationConnection(boolean request) {
             if (mBar != null) {
                 try {
                     mBar.requestWindowMagnificationConnection(request);
+                    return true;
                 } catch (RemoteException ex) { }
             }
+            return false;
         }
 
         @Override
@@ -856,11 +858,11 @@
     }
 
     @Override
-    public void onBiometricAuthenticated() {
+    public void onBiometricAuthenticated(@Modality int modality) {
         enforceBiometricDialog();
         if (mBar != null) {
             try {
-                mBar.onBiometricAuthenticated();
+                mBar.onBiometricAuthenticated(modality);
             } catch (RemoteException ex) {
             }
         }
@@ -1952,8 +1954,7 @@
     public void setNavBarMode(@NavBarMode int navBarMode) {
         enforceStatusBar();
         if (navBarMode != NAV_BAR_MODE_DEFAULT && navBarMode != NAV_BAR_MODE_KIDS) {
-            throw new UnsupportedOperationException(
-                    "Supplied navBarMode not supported: " + navBarMode);
+            throw new IllegalArgumentException("Supplied navBarMode not supported: " + navBarMode);
         }
 
         final int userId = mCurrentUserId;
@@ -1961,6 +1962,8 @@
         try {
             Settings.Secure.putIntForUser(mContext.getContentResolver(),
                     Settings.Secure.NAV_BAR_KIDS_MODE, navBarMode, userId);
+            Settings.Secure.putIntForUser(mContext.getContentResolver(),
+                    Settings.Secure.NAV_BAR_FORCE_VISIBLE, navBarMode, userId);
 
             IOverlayManager overlayManager = getOverlayManager();
             if (overlayManager != null && navBarMode == NAV_BAR_MODE_KIDS
diff --git a/services/core/java/com/android/server/timezonedetector/OWNERS b/services/core/java/com/android/server/timezonedetector/OWNERS
index 0293242..485a0dd 100644
--- a/services/core/java/com/android/server/timezonedetector/OWNERS
+++ b/services/core/java/com/android/server/timezonedetector/OWNERS
@@ -3,5 +3,6 @@
 # ultimately referenced by other OWNERS files for components maintained by the same team.
 nfuller@google.com
 jmorace@google.com
+kanyinsola@google.com
 mingaleev@google.com
 narayan@google.com
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 8f703c5..d3f3abe 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -79,6 +79,7 @@
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 import android.util.TypedValue;
 import android.util.proto.ProtoOutputStream;
 import android.view.Display;
@@ -138,7 +139,7 @@
             new SparseArray<>();
     private SparseArray<IBinder> mFocusedWindow = new SparseArray<>();
     private int mFocusedDisplay = -1;
-    private boolean mIsImeVisible = false;
+    private final SparseBooleanArray mIsImeVisibleArray = new SparseBooleanArray();
     // Set to true if initializing window population complete.
     private boolean mAllObserversInitialized = true;
     private final AccessibilityWindowsPopulator mAccessibilityWindowsPopulator;
@@ -167,8 +168,11 @@
             if (dc != null) {
                 final Display display = dc.getDisplay();
                 if (display != null && display.getType() != Display.TYPE_OVERLAY) {
-                    mDisplayMagnifiers.put(displayId, new DisplayMagnifier(
-                            mService, dc, display, callbacks));
+                    final DisplayMagnifier magnifier = new DisplayMagnifier(
+                            mService, dc, display, callbacks);
+                    magnifier.notifyImeWindowVisibilityChanged(
+                            mIsImeVisibleArray.get(displayId, false));
+                    mDisplayMagnifiers.put(displayId, magnifier);
                     result = true;
                 }
             }
@@ -494,11 +498,13 @@
             mAccessibilityTracing.logTrace(TAG + ".updateImeVisibilityIfNeeded",
                     FLAGS_MAGNIFICATION_CALLBACK, "displayId=" + displayId + ";shown=" + shown);
         }
-        if (mIsImeVisible == shown) {
+
+        final boolean isDisplayImeVisible = mIsImeVisibleArray.get(displayId, false);
+        if (isDisplayImeVisible == shown) {
             return;
         }
 
-        mIsImeVisible = shown;
+        mIsImeVisibleArray.put(displayId, shown);
         final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
         if (displayMagnifier != null) {
             displayMagnifier.notifyImeWindowVisibilityChanged(shown);
@@ -534,6 +540,7 @@
     }
 
     public void onDisplayRemoved(int displayId) {
+        mIsImeVisibleArray.delete(displayId);
         mFocusedWindow.remove(displayId);
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
index 4df2e17..06c58ba 100644
--- a/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
+++ b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
@@ -43,9 +43,13 @@
     public abstract @Nullable ActivityInterceptResult intercept(ActivityInterceptorInfo info);
 
     /**
-     * Called when an activity is successfully launched.
+     * Called when an activity is successfully launched. The intent included in the
+     * ActivityInterceptorInfo may have changed from the one sent in
+     * {@link #intercept(ActivityInterceptorInfo)}, due to the return from
+     * {@link #intercept(ActivityInterceptorInfo)}.
      */
-    public void onActivityLaunched(TaskInfo taskInfo, ActivityInfo activityInfo) {
+    public void onActivityLaunched(TaskInfo taskInfo, ActivityInfo activityInfo,
+            ActivityInterceptorInfo info) {
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 9c910eb..4e751f5 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -46,7 +46,6 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.app.WindowConfiguration.activityTypeToString;
-import static android.app.WindowConfiguration.isSplitScreenWindowingMode;
 import static android.app.admin.DevicePolicyResources.Drawables.Source.PROFILE_SWITCH_ANIMATION;
 import static android.app.admin.DevicePolicyResources.Drawables.Style.OUTLINE;
 import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
@@ -7515,8 +7514,7 @@
         final int parentWindowingMode =
                 newParentConfiguration.windowConfiguration.getWindowingMode();
         final boolean isFixedOrientationLetterboxAllowed =
-                isSplitScreenWindowingMode(parentWindowingMode)
-                        || parentWindowingMode == WINDOWING_MODE_MULTI_WINDOW
+                parentWindowingMode == WINDOWING_MODE_MULTI_WINDOW
                         || parentWindowingMode == WINDOWING_MODE_FULLSCREEN;
         // TODO(b/181207944): Consider removing the if condition and always run
         // resolveFixedOrientationConfiguration() since this should be applied for all cases.
diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
index 658a17b..c5fcd12 100644
--- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
@@ -188,10 +188,7 @@
         final SparseArray<ActivityInterceptorCallback> callbacks =
                 mService.getActivityInterceptorCallbacks();
         final ActivityInterceptorCallback.ActivityInterceptorInfo interceptorInfo =
-                new ActivityInterceptorCallback.ActivityInterceptorInfo(mRealCallingUid,
-                        mRealCallingPid, mUserId, mCallingPackage, mCallingFeatureId, mIntent,
-                        mRInfo, mAInfo, mResolvedType, mCallingPid, mCallingUid,
-                        mActivityOptions);
+                getInterceptorInfo();
 
         for (int i = 0; i < callbacks.size(); i++) {
             final ActivityInterceptorCallback callback = callbacks.valueAt(i);
@@ -412,9 +409,17 @@
     void onActivityLaunched(TaskInfo taskInfo, ActivityInfo activityInfo) {
         final SparseArray<ActivityInterceptorCallback> callbacks =
                 mService.getActivityInterceptorCallbacks();
+        ActivityInterceptorCallback.ActivityInterceptorInfo info = getInterceptorInfo();
         for (int i = 0; i < callbacks.size(); i++) {
             final ActivityInterceptorCallback callback = callbacks.valueAt(i);
-            callback.onActivityLaunched(taskInfo, activityInfo);
+            callback.onActivityLaunched(taskInfo, activityInfo, info);
         }
     }
+
+    private ActivityInterceptorCallback.ActivityInterceptorInfo getInterceptorInfo() {
+        return new ActivityInterceptorCallback.ActivityInterceptorInfo(mRealCallingUid,
+                mRealCallingPid, mUserId, mCallingPackage, mCallingFeatureId, mIntent,
+                mRInfo, mAInfo, mResolvedType, mCallingPid, mCallingUid,
+                mActivityOptions);
+    }
 }
diff --git a/services/core/java/com/android/server/wm/AnrController.java b/services/core/java/com/android/server/wm/AnrController.java
index 3d54b27..6befefd 100644
--- a/services/core/java/com/android/server/wm/AnrController.java
+++ b/services/core/java/com/android/server/wm/AnrController.java
@@ -21,6 +21,7 @@
 import static com.android.server.wm.ActivityRecord.INVALID_PID;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
+import android.annotation.NonNull;
 import android.os.Build;
 import android.os.IBinder;
 import android.os.Process;
@@ -35,6 +36,7 @@
 
 import java.io.File;
 import java.util.ArrayList;
+import java.util.OptionalInt;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -75,7 +77,33 @@
         activity.inputDispatchingTimedOut(reason, INVALID_PID);
     }
 
-    void notifyWindowUnresponsive(IBinder inputToken, String reason) {
+
+    /**
+     * Notify a window was unresponsive.
+     *
+     * @param token - the input token of the window
+     * @param pid - the pid of the window, if known
+     * @param reason - the reason for the window being unresponsive
+     */
+    void notifyWindowUnresponsive(@NonNull IBinder token, @NonNull OptionalInt pid,
+            @NonNull String reason) {
+        if (notifyWindowUnresponsive(token, reason)) {
+            return;
+        }
+        if (!pid.isPresent()) {
+            Slog.w(TAG_WM, "Failed to notify that window token=" + token + " was unresponsive.");
+            return;
+        }
+        notifyWindowUnresponsive(pid.getAsInt(), reason);
+    }
+
+    /**
+     * Notify a window identified by its input token was unresponsive.
+     *
+     * @return true if the window was identified by the given input token and the request was
+     *         handled, false otherwise.
+     */
+    private boolean notifyWindowUnresponsive(@NonNull IBinder inputToken, String reason) {
         preDumpIfLockTooSlow();
         final int pid;
         final boolean aboveSystem;
@@ -83,10 +111,8 @@
         synchronized (mService.mGlobalLock) {
             InputTarget target = mService.getInputTargetFromToken(inputToken);
             if (target == null) {
-                Slog.e(TAG_WM, "Unknown token, dropping notifyConnectionUnresponsive request");
-                return;
+                return false;
             }
-
             WindowState windowState = target.getWindowState();
             pid = target.getPid();
             // Blame the activity if the input token belongs to the window. If the target is
@@ -102,34 +128,63 @@
         } else {
             mService.mAmInternal.inputDispatchingTimedOut(pid, aboveSystem, reason);
         }
+        return true;
     }
 
-    void notifyWindowResponsive(IBinder inputToken) {
+    /**
+     * Notify a window owned by the provided pid was unresponsive.
+     */
+    private void notifyWindowUnresponsive(int pid, String reason) {
+        Slog.i(TAG_WM, "ANR in input window owned by pid=" + pid + ". Reason: " + reason);
+        dumpAnrStateLocked(null /* activity */, null /* windowState */, reason);
+
+        // We cannot determine the z-order of the window, so place the anr dialog as high
+        // as possible.
+        mService.mAmInternal.inputDispatchingTimedOut(pid, true /*aboveSystem*/, reason);
+    }
+
+    /**
+     * Notify a window was responsive after previously being unresponsive.
+     *
+     * @param token - the input token of the window
+     * @param pid - the pid of the window, if known
+     */
+    void notifyWindowResponsive(@NonNull IBinder token, @NonNull OptionalInt pid) {
+        if (notifyWindowResponsive(token)) {
+            return;
+        }
+        if (!pid.isPresent()) {
+            Slog.w(TAG_WM, "Failed to notify that window token=" + token + " was responsive.");
+            return;
+        }
+        notifyWindowResponsive(pid.getAsInt());
+    }
+
+    /**
+     * Notify a window identified by its input token was responsive after previously being
+     * unresponsive.
+     *
+     * @return true if the window was identified by the given input token and the request was
+     *         handled, false otherwise.
+     */
+    private boolean notifyWindowResponsive(@NonNull IBinder inputToken) {
         final int pid;
         synchronized (mService.mGlobalLock) {
             InputTarget target = mService.getInputTargetFromToken(inputToken);
             if (target == null) {
-                Slog.e(TAG_WM, "Unknown token, dropping notifyWindowConnectionResponsive request");
-                return;
+                return false;
             }
             pid = target.getPid();
         }
         mService.mAmInternal.inputDispatchingResumed(pid);
+        return true;
     }
 
-    void notifyGestureMonitorUnresponsive(int gestureMonitorPid, String reason) {
-        preDumpIfLockTooSlow();
-        synchronized (mService.mGlobalLock) {
-            Slog.i(TAG_WM, "ANR in gesture monitor owned by pid:" + gestureMonitorPid
-                    + ".  Reason: " + reason);
-            dumpAnrStateLocked(null /* activity */, null /* windowState */, reason);
-        }
-        mService.mAmInternal.inputDispatchingTimedOut(gestureMonitorPid, /* aboveSystem */ true,
-                reason);
-    }
-
-    void notifyGestureMonitorResponsive(int gestureMonitorPid) {
-        mService.mAmInternal.inputDispatchingResumed(gestureMonitorPid);
+    /**
+     * Notify a window owned by the provided pid was responsive after previously being unresponsive.
+     */
+    private void notifyWindowResponsive(int pid) {
+        mService.mAmInternal.inputDispatchingResumed(pid);
     }
 
     /**
@@ -228,12 +283,7 @@
         mService.mAtmService.saveANRState(reason);
     }
 
-    private boolean isWindowAboveSystem(WindowState windowState) {
-        if (windowState == null) {
-            // If the window state is not available we cannot easily determine its z order. Try to
-            // place the anr dialog as high as possible.
-            return true;
-        }
+    private boolean isWindowAboveSystem(@NonNull WindowState windowState) {
         int systemAlertLayer = mService.mPolicy.getWindowLayerFromTypeLw(
                 TYPE_APPLICATION_OVERLAY, windowState.mOwnerCanAddInternalSystemWindow);
         return windowState.mBaseLayer > systemAlertLayer;
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 487aff6..23f14a7 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -53,7 +53,7 @@
      * Returns true if the back predictability feature is enabled
      */
     static boolean isEnabled() {
-        return SystemProperties.getInt(BACK_PREDICTABILITY_PROP, 1) > 0;
+        return SystemProperties.getInt(BACK_PREDICTABILITY_PROP, 0) > 0;
     }
 
     static boolean isScreenshotEnabled() {
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index afa4f19..08a9da4 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -24,8 +24,6 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.activityTypeToString;
 import static android.app.WindowConfiguration.windowingModeToString;
 import static android.app.WindowConfigurationProto.WINDOWING_MODE;
@@ -475,33 +473,9 @@
         return WindowConfiguration.inMultiWindowMode(windowingMode);
     }
 
-    /** Returns true if this container is currently in split-screen windowing mode. */
-    public boolean inSplitScreenWindowingMode() {
-        /*@WindowConfiguration.WindowingMode*/ int windowingMode =
-                mFullConfiguration.windowConfiguration.getWindowingMode();
-
-        return windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
-                || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-    }
-
-    /** Returns true if this container is currently in split-screen secondary windowing mode. */
-    public boolean inSplitScreenSecondaryWindowingMode() {
-        /*@WindowConfiguration.WindowingMode*/ int windowingMode =
-                mFullConfiguration.windowConfiguration.getWindowingMode();
-
-        return windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-    }
-
-    public boolean inSplitScreenPrimaryWindowingMode() {
-        return mFullConfiguration.windowConfiguration.getWindowingMode()
-                == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-    }
-
     /**
-     * Returns true if this container can be put in either
-     * {@link WindowConfiguration#WINDOWING_MODE_SPLIT_SCREEN_PRIMARY} or
-     * {@link WindowConfiguration##WINDOWING_MODE_SPLIT_SCREEN_SECONDARY} windowing modes based on
-     * its current state.
+     * Returns true if this container supports split-screen multi-window and can be put in
+     * split-screen based on its current state.
      */
     public boolean supportsSplitScreenWindowingMode() {
         return mFullConfiguration.windowConfiguration.supportSplitScreenWindowingMode();
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 1c0687a..82ad861 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -26,7 +26,6 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -5521,11 +5520,7 @@
         final float[] tmpFloat9 = new float[9];
         forAllWindows(w -> {
             if (w.isVisible() && !w.inPinnedWindowingMode()) {
-                if (w.mSession.mSetsUnrestrictedKeepClearAreas) {
-                    outUnrestricted.addAll(w.getKeepClearAreas(tmpMatrix, tmpFloat9));
-                } else {
-                    outRestricted.addAll(w.getKeepClearAreas(tmpMatrix, tmpFloat9));
-                }
+                w.getKeepClearAreas(outRestricted, outUnrestricted, tmpMatrix, tmpFloat9);
             }
 
             // We stop traversing when we reach the base of a fullscreen app.
@@ -5583,13 +5578,12 @@
     }
 
     static boolean alwaysCreateRootTask(int windowingMode, int activityType) {
-        // Always create a root task for fullscreen, freeform, and split-screen-secondary windowing
+        // Always create a root task for fullscreen, freeform, and multi windowing
         // modes so that we can manage visual ordering and return types correctly.
         return activityType == ACTIVITY_TYPE_STANDARD
                 && (windowingMode == WINDOWING_MODE_FULLSCREEN
                 || windowingMode == WINDOWING_MODE_FREEFORM
                 || windowingMode == WINDOWING_MODE_PINNED
-                || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
                 || windowingMode == WINDOWING_MODE_MULTI_WINDOW);
     }
 
@@ -6368,10 +6362,10 @@
         }
 
         /**
-         * Returns the fixed orientation requested by a transient launch (e.g. recents animation).
-         * If it doesn't return SCREEN_ORIENTATION_UNSET, the rotation change should be deferred.
+         * Returns {@code true} if the transient launch (e.g. recents animation) requested a fixed
+         * orientation, then the rotation change should be deferred.
          */
-        @ActivityInfo.ScreenOrientation int getTransientFixedOrientation() {
+        boolean shouldDeferRotation() {
             ActivityRecord source = null;
             if (mTransitionController.isShellTransitionsEnabled()) {
                 final ActivityRecord r = mFixedRotationLaunchingApp;
@@ -6383,13 +6377,10 @@
             }
             if (source == null || source.getRequestedConfigurationOrientation(
                     true /* forDisplay */) == ORIENTATION_UNDEFINED) {
-                return SCREEN_ORIENTATION_UNSET;
+                return false;
             }
-            if (!mWmService.mPolicy.okToAnimate(false /* ignoreScreenOn */)) {
-                // If screen is off or the device is going to sleep, then still allow to update.
-                return SCREEN_ORIENTATION_UNSET;
-            }
-            return source.mOrientation;
+            // If screen is off or the device is going to sleep, then still allow to update.
+            return mWmService.mPolicy.okToAnimate(false /* ignoreScreenOn */);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index f116fff..262ddae 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -446,12 +446,10 @@
                 return false;
             }
 
-            final int transientFixedOrientation =
-                    mDisplayContent.mFixedRotationTransitionListener.getTransientFixedOrientation();
-            if (transientFixedOrientation != SCREEN_ORIENTATION_UNSET) {
+            if (mDisplayContent.mFixedRotationTransitionListener.shouldDeferRotation()) {
                 // Makes sure that after the transition is finished, updateOrientation() can see
                 // the difference from the latest orientation source.
-                mLastOrientation = transientFixedOrientation;
+                mLastOrientation = SCREEN_ORIENTATION_UNSET;
                 // During the recents animation, the closing app might still be considered on top.
                 // In order to ignore its requested orientation to avoid a sensor led rotation (e.g
                 // user rotating the device while the recents animation is running), we ignore
diff --git a/services/core/java/com/android/server/wm/DragResizeMode.java b/services/core/java/com/android/server/wm/DragResizeMode.java
index d754fd8..684cf06 100644
--- a/services/core/java/com/android/server/wm/DragResizeMode.java
+++ b/services/core/java/com/android/server/wm/DragResizeMode.java
@@ -40,8 +40,6 @@
         switch (mode) {
             case DRAG_RESIZE_MODE_FREEFORM:
                 return rootTask.getWindowingMode() == WINDOWING_MODE_FREEFORM;
-            case DRAG_RESIZE_MODE_DOCKED_DIVIDER:
-                return rootTask.inSplitScreenWindowingMode();
             default:
                 return false;
         }
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index 1f0fdcf..8d1425d 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -39,6 +39,7 @@
 import com.android.server.input.InputManagerService;
 
 import java.io.PrintWriter;
+import java.util.OptionalInt;
 
 final class InputManagerCallback implements InputManagerService.WindowManagerCallbacks {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "InputManagerCallback" : TAG_WM;
@@ -98,23 +99,14 @@
     }
 
     @Override
-    public void notifyGestureMonitorUnresponsive(int pid, @NonNull String reason) {
-        mService.mAnrController.notifyGestureMonitorUnresponsive(pid, reason);
+    public void notifyWindowUnresponsive(@NonNull IBinder token, @NonNull OptionalInt pid,
+            @NonNull String reason) {
+        mService.mAnrController.notifyWindowUnresponsive(token, pid, reason);
     }
 
     @Override
-    public void notifyWindowUnresponsive(@NonNull IBinder token, String reason) {
-        mService.mAnrController.notifyWindowUnresponsive(token, reason);
-    }
-
-    @Override
-    public void notifyGestureMonitorResponsive(int pid) {
-        mService.mAnrController.notifyGestureMonitorResponsive(pid);
-    }
-
-    @Override
-    public void notifyWindowResponsive(@NonNull IBinder token) {
-        mService.mAnrController.notifyWindowResponsive(token);
+    public void notifyWindowResponsive(@NonNull IBinder token, @NonNull OptionalInt pid) {
+        mService.mAnrController.notifyWindowResponsive(token, pid);
     }
 
     /** Notifies that the input device configuration has changed. */
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index bb6d83c..4fdb1f7 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -449,11 +449,8 @@
             boolean copyState) {
         final WindowState roundedCornerWindow = mPolicy.getRoundedCornerWindow();
         final Task task = w.getTask();
-        final boolean isInSplitScreenMode = task != null && task.inMultiWindowMode()
-                && task.getRootTask() != null
-                && task.getRootTask().getAdjacentTaskFragment() != null;
         if (task != null && !task.getWindowConfiguration().tasksAreFloating()
-                && (roundedCornerWindow != null || isInSplitScreenMode)) {
+                && (roundedCornerWindow != null || task.inSplitScreen())) {
             // Instead of using display frame to calculating rounded corner, for the fake rounded
             // corners drawn by divider bar or task bar, we need to re-calculate rounded corners
             // based on task bounds and if the task bounds is intersected with task bar, we should
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 93714e8..a407021 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -18,7 +18,6 @@
 
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.view.RemoteAnimationTarget.MODE_CLOSING;
@@ -610,8 +609,7 @@
             final TaskAnimationAdapter adapter = mPendingAnimations.get(i);
             final Task task = adapter.mTask;
             final TaskFragment adjacentTask = task.getRootTask().getAdjacentTaskFragment();
-            final boolean inSplitScreen = task.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW
-                    && adjacentTask != null;
+            final boolean inSplitScreen = task.inSplitScreen();
             if (task.isActivityTypeHomeOrRecents()
                     // Skip if the task is in split screen and in landscape.
                     || (inSplitScreen && isDisplayLandscape)
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index cd8ddf4..bafdd92 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -84,6 +84,7 @@
 import com.android.server.wm.WindowManagerService.H;
 
 import java.io.PrintWriter;
+import java.util.Collections;
 import java.util.List;
 import java.util.function.BiConsumer;
 
@@ -521,10 +522,15 @@
     }
 
     @Override
-    public void reportKeepClearAreasChanged(IWindow window, List<Rect> keepClearAreas) {
+    public void reportKeepClearAreasChanged(IWindow window, List<Rect> restricted,
+            List<Rect> unrestricted) {
+        if (!mSetsUnrestrictedKeepClearAreas && !unrestricted.isEmpty()) {
+            unrestricted = Collections.emptyList();
+        }
+
         final long ident = Binder.clearCallingIdentity();
         try {
-            mService.reportKeepClearAreasChanged(this, window, keepClearAreas);
+            mService.reportKeepClearAreasChanged(this, window, restricted, unrestricted);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index f3933c5..7fe34f4 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -29,6 +29,7 @@
 import static android.app.WindowConfiguration.PINNED_WINDOWING_MODE_ELEVATION_IN_DIP;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.app.WindowConfiguration.activityTypeToString;
@@ -1722,6 +1723,13 @@
                 && (topTask == null || topTask.supportsSplitScreenWindowingModeInner(tda));
     }
 
+    /** Returns {@code true} if this task is currently in split-screen. */
+    boolean inSplitScreen() {
+        return getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW
+                && getRootTask() != null
+                && getRootTask().getAdjacentTaskFragment() != null;
+    }
+
     private boolean supportsSplitScreenWindowingModeInner(@Nullable TaskDisplayArea tda) {
         return super.supportsSplitScreenWindowingMode()
                 && mAtmService.mSupportsSplitScreenMultiWindow
@@ -6020,9 +6028,6 @@
     }
 
     boolean shouldIgnoreInput() {
-        if (inSplitScreenPrimaryWindowingMode() && !isFocusable()) {
-            return true;
-        }
         if (mAtmService.mHasLeanbackFeature && inPinnedWindowingMode()
                 && !isFocusedRootTaskOnDisplay()) {
             // Preventing Picture-in-Picture root task from receiving input on TVs.
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 7fab94c..afc3087 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1767,8 +1767,7 @@
         // Resolve override windowing mode to fullscreen for home task (even on freeform
         // display), or split-screen if in split-screen mode.
         if (getActivityType() == ACTIVITY_TYPE_HOME && windowingMode == WINDOWING_MODE_UNDEFINED) {
-            windowingMode = WindowConfiguration.isSplitScreenWindowingMode(parentWindowingMode)
-                    ? parentWindowingMode : WINDOWING_MODE_FULLSCREEN;
+            windowingMode = WINDOWING_MODE_FULLSCREEN;
             getResolvedOverrideConfiguration().windowConfiguration.setWindowingMode(windowingMode);
         }
 
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 037d582..331f124 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -729,6 +729,16 @@
             // Skip if task still not appeared.
             return;
         }
+        if (force && mPendingTaskEvents.isEmpty()) {
+            // There are task-info changed events do not result in
+            // - RootWindowContainer#performSurfacePlacementNoTrace OR
+            // - WindowAnimator#animate
+            // For instance, when an app requesting aspect ratio change when in PiP mode.
+            // To solve this, we directly dispatch the pending event if there are no events queued (
+            // otherwise, all pending events should be dispatched on next drawn).
+            dispatchTaskInfoChanged(task, true /* force */);
+            return;
+        }
 
         // Defer task info reporting while layout is deferred. This is because layout defer
         // blocks tend to do lots of re-ordering which can mess up animations in receivers.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 04f135e..03e2140 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3035,7 +3035,7 @@
     @Override
     public void onKeyguardShowingAndNotOccludedChanged() {
         mH.sendEmptyMessage(H.RECOMPUTE_FOCUS);
-        dispatchKeyguardLockedStateState();
+        dispatchKeyguardLockedState();
     }
 
     @Override
@@ -3249,7 +3249,7 @@
                         + " permission required to read keyguard visibility");
     }
 
-    private void dispatchKeyguardLockedStateState() {
+    private void dispatchKeyguardLockedState() {
         mH.post(() -> {
             final boolean isKeyguardLocked = mPolicy.isKeyguardShowing();
             if (mDispatchedKeyguardLockedState == isKeyguardLocked) {
@@ -4412,10 +4412,11 @@
         }
     }
 
-    void reportKeepClearAreasChanged(Session session, IWindow window, List<Rect> keepClearAreas) {
+    void reportKeepClearAreasChanged(Session session, IWindow window,
+            List<Rect> restricted, List<Rect> unrestricted) {
         synchronized (mGlobalLock) {
             final WindowState win = windowForClientLocked(session, window, true);
-            if (win.setKeepClearAreas(keepClearAreas)) {
+            if (win.setKeepClearAreas(restricted, unrestricted)) {
                 win.getDisplayContent().updateKeepClearAreas();
             }
         }
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index c70a40c..ce27d73 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -21,11 +21,13 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.window.WindowContainerTransaction.Change.CHANGE_BOUNDS_TRANSACTION;
 import static android.window.WindowContainerTransaction.Change.CHANGE_BOUNDS_TRANSACTION_RECT;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_ADD_RECT_INSETS_PROVIDER;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_LAUNCH_TASK;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_PENDING_INTENT;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_INSETS_PROVIDER;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
@@ -895,6 +897,17 @@
                 taskDisplayArea.moveRootTaskBehindRootTask(thisTask.getRootTask(), restoreAt);
                 break;
             }
+            case HIERARCHY_OP_TYPE_ADD_RECT_INSETS_PROVIDER:
+                final Rect insetsProviderWindowContainer = hop.getInsetsProviderFrame();
+                final WindowContainer receiverWindowContainer =
+                        WindowContainer.fromBinder(hop.getContainer());
+                receiverWindowContainer.addLocalRectInsetsSourceProvider(
+                        insetsProviderWindowContainer, hop.getInsetsTypes());
+                break;
+            case HIERARCHY_OP_TYPE_REMOVE_INSETS_PROVIDER:
+                WindowContainer.fromBinder(hop.getContainer())
+                        .removeLocalInsetsSourceProvider(hop.getInsetsTypes());
+                break;
         }
         return effects;
     }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 517837c..d547275 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -22,7 +22,6 @@
 import static android.app.AppOpsManager.OP_NONE;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.isSplitScreenWindowingMode;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.graphics.GraphicsProtos.dumpPointProto;
 import static android.hardware.display.DisplayManager.SWITCHING_TYPE_NONE;
@@ -181,6 +180,7 @@
 import static com.android.server.wm.WindowStateProto.STACK_ID;
 import static com.android.server.wm.WindowStateProto.SURFACE_INSETS;
 import static com.android.server.wm.WindowStateProto.SURFACE_POSITION;
+import static com.android.server.wm.WindowStateProto.UNRESTRICTED_KEEP_CLEAR_AREAS;
 import static com.android.server.wm.WindowStateProto.VIEW_VISIBILITY;
 import static com.android.server.wm.WindowStateProto.WINDOW_CONTAINER;
 import static com.android.server.wm.WindowStateProto.WINDOW_FRAMES;
@@ -482,6 +482,12 @@
      */
     private final List<Rect> mKeepClearAreas = new ArrayList<>();
 
+    /**
+     * Like mKeepClearAreas, but the unrestricted ones can be trusted to behave nicely.
+     * Floating windows (like Pip) will be moved away from them without applying restrictions.
+     */
+    private final List<Rect> mUnrestrictedKeepClearAreas = new ArrayList<>();
+
     // 0 = left, 1 = right
     private final int[] mLastRequestedExclusionHeight = {0, 0};
     private final int[] mLastGrantedExclusionHeight = {0, 0};
@@ -1023,51 +1029,86 @@
     }
 
     /**
-     * @return a list of rects that should ideally not be covered by floating windows like pip.
-     *         The returned rect coordinates are relative to the display origin.
+     * Collects all restricted and unrestricted keep-clear areas for this window.
+     * Keep-clear areas are rects that should ideally not be covered by floating windows like Pip.
+     * The system is more careful about restricted ones and may apply restrictions to them, while
+     * the unrestricted ones are considered safe.
+     *
+     * @param outRestricted list to add restricted keep-clear areas to
+     * @param outUnrestricted list to add unrestricted keep-clear areas to
      */
-    List<Rect> getKeepClearAreas() {
+    void getKeepClearAreas(List<Rect> outRestricted, List<Rect> outUnrestricted) {
         final Matrix tmpMatrix = new Matrix();
         final float[] tmpFloat9 = new float[9];
-        return getKeepClearAreas(tmpMatrix, tmpFloat9);
+        getKeepClearAreas(outRestricted, outUnrestricted, tmpMatrix, tmpFloat9);
     }
 
     /**
+     * Collects all restricted and unrestricted keep-clear areas for this window.
+     * Keep-clear areas are rects that should ideally not be covered by floating windows like Pip.
+     * The system is more careful about restricted ones and may apply restrictions to them, while
+     * the unrestricted ones are considered safe.
+     *
+     * @param outRestricted list to add restricted keep-clear areas to
+     * @param outUnrestricted list to add unrestricted keep-clear areas to
      * @param tmpMatrix a temporary matrix to be used for transformations
      * @param float9 a temporary array of 9 floats
-     *
-     * @return a list of rects that should ideally not be covered by floating windows like pip.
-     *         The returned rect coordinates are relative to the display origin.
      */
-    List<Rect> getKeepClearAreas(Matrix tmpMatrix, float[] float9) {
+    void getKeepClearAreas(List<Rect> outRestricted, List<Rect> outUnrestricted, Matrix tmpMatrix,
+            float[] float9) {
+        outRestricted.addAll(getRectsInScreenSpace(mKeepClearAreas, tmpMatrix, float9));
+        outUnrestricted.addAll(
+                getRectsInScreenSpace(mUnrestrictedKeepClearAreas, tmpMatrix, float9));
+    }
+
+    /**
+     * Transforms the given rects from window coordinate space to screen space.
+     */
+    List<Rect> getRectsInScreenSpace(List<Rect> rects, Matrix tmpMatrix, float[] float9) {
         getTransformationMatrix(float9, tmpMatrix);
 
-        // Translate all keep-clear rects to screen coordinates.
-        final List<Rect> transformedKeepClearAreas = new ArrayList<Rect>();
+        final List<Rect> transformedRects = new ArrayList<Rect>();
         final RectF tmpRect = new RectF();
         Rect curr;
-        for (Rect r : mKeepClearAreas) {
+        for (Rect r : rects) {
             tmpRect.set(r);
             tmpMatrix.mapRect(tmpRect);
             curr = new Rect();
             tmpRect.roundOut(curr);
-            transformedKeepClearAreas.add(curr);
+            transformedRects.add(curr);
         }
-        return transformedKeepClearAreas;
+        return transformedRects;
     }
 
     /**
-     * @param keepClearAreas the new keep-clear areas for this window. The rects should be defined
-     *                       in window coordinate space
+     * Sets the new keep-clear areas for this window. The rects should be defined in window
+     * coordinate space.
+     * Keep-clear areas can be restricted or unrestricted, depending on whether the app holds the
+     * {@link android.Manifest.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS} system permission.
+     * Restricted ones will be handled more carefully by the system. Restrictions may be applied.
+     * Unrestricted ones are considered safe. The system should move floating windows away from them
+     * without applying restrictions.
+     *
+     * @param restricted the new restricted keep-clear areas for this window
+     * @param unrestricted the new unrestricted keep-clear areas for this window
      *
      * @return true if there is a change in the list of keep-clear areas; false otherwise
      */
-    boolean setKeepClearAreas(List<Rect> keepClearAreas) {
-        if (mKeepClearAreas.equals(keepClearAreas)) {
+    boolean setKeepClearAreas(List<Rect> restricted, List<Rect> unrestricted) {
+        final boolean newRestrictedAreas = !mKeepClearAreas.equals(restricted);
+        final boolean newUnrestrictedAreas = !mUnrestrictedKeepClearAreas.equals(unrestricted);
+        if (!newRestrictedAreas && !newUnrestrictedAreas) {
             return false;
         }
-        mKeepClearAreas.clear();
-        mKeepClearAreas.addAll(keepClearAreas);
+        if (newRestrictedAreas) {
+            mKeepClearAreas.clear();
+            mKeepClearAreas.addAll(restricted);
+        }
+
+        if (newUnrestrictedAreas) {
+            mUnrestrictedKeepClearAreas.clear();
+            mUnrestrictedKeepClearAreas.addAll(unrestricted);
+        }
         return true;
     }
 
@@ -3582,11 +3623,13 @@
         final int requested = mLastRequestedExclusionHeight[side];
         final int granted = mLastGrantedExclusionHeight[side];
 
+        final boolean inSplitScreen = getTask() != null && getTask().inSplitScreen();
+
         FrameworkStatsLog.write(FrameworkStatsLog.EXCLUSION_RECT_STATE_CHANGED,
                 mAttrs.packageName, requested, requested - granted /* rejected */,
                 side + 1 /* Sides are 1-indexed in atoms.proto */,
                 (getConfiguration().orientation == ORIENTATION_LANDSCAPE),
-                isSplitScreenWindowingMode(getWindowingMode()), (int) duration);
+                inSplitScreen, (int) duration);
     }
 
     private void initExclusionRestrictions() {
@@ -4121,8 +4164,7 @@
         if (task == null) {
             return false;
         }
-        if (!inSplitScreenWindowingMode() && !inFreeformWindowingMode()
-                && !task.getRootTask().mCreatedByOrganizer) {
+        if (!inFreeformWindowingMode() && !task.getRootTask().mCreatedByOrganizer) {
             return false;
         }
         // TODO(157912944): formalize drag-resizing so that exceptions aren't hardcoded like this
@@ -4202,9 +4244,12 @@
         proto.write(FORCE_SEAMLESS_ROTATION, mForceSeamlesslyRotate);
         proto.write(HAS_COMPAT_SCALE, hasCompatScale());
         proto.write(GLOBAL_SCALE, mGlobalScale);
-        for (Rect r : getKeepClearAreas()) {
+        for (Rect r : mKeepClearAreas) {
             r.dumpDebug(proto, KEEP_CLEAR_AREAS);
         }
+        for (Rect r : mUnrestrictedKeepClearAreas) {
+            r.dumpDebug(proto, UNRESTRICTED_KEEP_CLEAR_AREAS);
+        }
         proto.end(token);
     }
 
@@ -4373,7 +4418,8 @@
         }
         pw.println(prefix + "isOnScreen=" + isOnScreen());
         pw.println(prefix + "isVisible=" + isVisible());
-        pw.println(prefix + "keepClearAreas=" + getKeepClearAreas());
+        pw.println(prefix + "keepClearAreas: restricted=" + mKeepClearAreas
+                          + ", unrestricted=" + mUnrestrictedKeepClearAreas);
         if (dumpAll) {
             final String visibilityString = mRequestedVisibilities.toString();
             if (!visibilityString.isEmpty()) {
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 3c122b0..31b5579 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -96,8 +96,6 @@
     jmethodID notifyNoFocusedWindowAnr;
     jmethodID notifyWindowUnresponsive;
     jmethodID notifyWindowResponsive;
-    jmethodID notifyMonitorUnresponsive;
-    jmethodID notifyMonitorResponsive;
     jmethodID notifyFocusChanged;
     jmethodID notifySensorEvent;
     jmethodID notifySensorAccuracy;
@@ -308,10 +306,9 @@
     void notifyConfigurationChanged(nsecs_t when) override;
     // ANR-related callbacks -- start
     void notifyNoFocusedWindowAnr(const std::shared_ptr<InputApplicationHandle>& handle) override;
-    void notifyWindowUnresponsive(const sp<IBinder>& token, const std::string& reason) override;
-    void notifyWindowResponsive(const sp<IBinder>& token) override;
-    void notifyMonitorUnresponsive(int32_t pid, const std::string& reason) override;
-    void notifyMonitorResponsive(int32_t pid) override;
+    void notifyWindowUnresponsive(const sp<IBinder>& token, std::optional<int32_t> pid,
+                                  const std::string& reason) override;
+    void notifyWindowResponsive(const sp<IBinder>& token, std::optional<int32_t> pid) override;
     // ANR-related callbacks -- end
     void notifyInputChannelBroken(const sp<IBinder>& token) override;
     void notifyFocusChanged(const sp<IBinder>& oldToken, const sp<IBinder>& newToken) override;
@@ -838,6 +835,7 @@
 }
 
 void NativeInputManager::notifyWindowUnresponsive(const sp<IBinder>& token,
+                                                  std::optional<int32_t> pid,
                                                   const std::string& reason) {
 #if DEBUG_INPUT_DISPATCHER_POLICY
     ALOGD("notifyWindowUnresponsive");
@@ -851,11 +849,12 @@
     ScopedLocalRef<jstring> reasonObj(env, env->NewStringUTF(reason.c_str()));
 
     env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyWindowUnresponsive, tokenObj,
-                        reasonObj.get());
+                        pid.value_or(0), pid.has_value(), reasonObj.get());
     checkAndClearExceptionFromCallback(env, "notifyWindowUnresponsive");
 }
 
-void NativeInputManager::notifyWindowResponsive(const sp<IBinder>& token) {
+void NativeInputManager::notifyWindowResponsive(const sp<IBinder>& token,
+                                                std::optional<int32_t> pid) {
 #if DEBUG_INPUT_DISPATCHER_POLICY
     ALOGD("notifyWindowResponsive");
 #endif
@@ -866,39 +865,11 @@
 
     jobject tokenObj = javaObjectForIBinder(env, token);
 
-    env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyWindowResponsive, tokenObj);
+    env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyWindowResponsive, tokenObj,
+                        pid.value_or(0), pid.has_value());
     checkAndClearExceptionFromCallback(env, "notifyWindowResponsive");
 }
 
-void NativeInputManager::notifyMonitorUnresponsive(int32_t pid, const std::string& reason) {
-#if DEBUG_INPUT_DISPATCHER_POLICY
-    ALOGD("notifyMonitorUnresponsive");
-#endif
-    ATRACE_CALL();
-
-    JNIEnv* env = jniEnv();
-    ScopedLocalFrame localFrame(env);
-
-    ScopedLocalRef<jstring> reasonObj(env, env->NewStringUTF(reason.c_str()));
-
-    env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyMonitorUnresponsive, pid,
-                        reasonObj.get());
-    checkAndClearExceptionFromCallback(env, "notifyMonitorUnresponsive");
-}
-
-void NativeInputManager::notifyMonitorResponsive(int32_t pid) {
-#if DEBUG_INPUT_DISPATCHER_POLICY
-    ALOGD("notifyMonitorResponsive");
-#endif
-    ATRACE_CALL();
-
-    JNIEnv* env = jniEnv();
-    ScopedLocalFrame localFrame(env);
-
-    env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyMonitorResponsive, pid);
-    checkAndClearExceptionFromCallback(env, "notifyMonitorResponsive");
-}
-
 void NativeInputManager::notifyInputChannelBroken(const sp<IBinder>& token) {
 #if DEBUG_INPUT_DISPATCHER_POLICY
     ALOGD("notifyInputChannelBroken");
@@ -2506,16 +2477,10 @@
                   "(Landroid/view/InputApplicationHandle;)V");
 
     GET_METHOD_ID(gServiceClassInfo.notifyWindowUnresponsive, clazz, "notifyWindowUnresponsive",
-                  "(Landroid/os/IBinder;Ljava/lang/String;)V");
-
-    GET_METHOD_ID(gServiceClassInfo.notifyMonitorUnresponsive, clazz, "notifyMonitorUnresponsive",
-                  "(ILjava/lang/String;)V");
+                  "(Landroid/os/IBinder;IZLjava/lang/String;)V");
 
     GET_METHOD_ID(gServiceClassInfo.notifyWindowResponsive, clazz, "notifyWindowResponsive",
-                  "(Landroid/os/IBinder;)V");
-
-    GET_METHOD_ID(gServiceClassInfo.notifyMonitorResponsive, clazz, "notifyMonitorResponsive",
-                  "(I)V");
+                  "(Landroid/os/IBinder;IZ)V");
 
     GET_METHOD_ID(gServiceClassInfo.filterInputEvent, clazz,
             "filterInputEvent", "(Landroid/view/InputEvent;I)Z");
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 1ac0c26..5cda9ea 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -8610,20 +8610,23 @@
                     admin.getPackageName(), userId, "set-device-owner");
 
             Slogf.i(LOG_TAG, "Device owner set: " + admin + " on user " + userId);
-
-            if (setProfileOwnerOnCurrentUserIfNecessary
-                    && mInjector.userManagerIsHeadlessSystemUserMode()) {
-                int currentForegroundUser = getCurrentForegroundUserId();
-                Slogf.i(LOG_TAG, "setDeviceOwner(): setting " + admin
-                        + " as profile owner on user " + currentForegroundUser);
-                // Sets profile owner on current foreground user since
-                // the human user will complete the DO setup workflow from there.
-                manageUserUnchecked(/* deviceOwner= */ admin, /* profileOwner= */ admin,
-                        /* managedUser= */ currentForegroundUser, /* adminExtras= */ null,
-                        /* showDisclaimer= */ false);
-            }
-            return true;
         }
+
+        if (setProfileOwnerOnCurrentUserIfNecessary
+                && mInjector.userManagerIsHeadlessSystemUserMode()) {
+            int currentForegroundUser;
+            synchronized (getLockObject()) {
+                currentForegroundUser = getCurrentForegroundUserId();
+            }
+            Slogf.i(LOG_TAG, "setDeviceOwner(): setting " + admin
+                    + " as profile owner on user " + currentForegroundUser);
+            // Sets profile owner on current foreground user since
+            // the human user will complete the DO setup workflow from there.
+            manageUserUnchecked(/* deviceOwner= */ admin, /* profileOwner= */ admin,
+                    /* managedUser= */ currentForegroundUser, /* adminExtras= */ null,
+                    /* showDisclaimer= */ false);
+        }
+        return true;
     }
 
     @Override
@@ -10853,7 +10856,7 @@
         final int userHandle = user.getIdentifier();
         final long id = mInjector.binderClearCallingIdentity();
         try {
-            maybeInstallDeviceManagerRoleHolderInUser(userHandle);
+            maybeInstallDevicePolicyManagementRoleHolderInUser(userHandle);
 
             manageUserUnchecked(admin, profileOwner, userHandle, adminExtras,
                     /* showDisclaimer= */ true);
@@ -17732,7 +17735,7 @@
                     startTime,
                     callerPackage);
 
-            maybeInstallDeviceManagerRoleHolderInUser(userInfo.id);
+            maybeInstallDevicePolicyManagementRoleHolderInUser(userInfo.id);
 
             installExistingAdminPackage(userInfo.id, admin.getPackageName());
             if (!enableAdminAndSetProfileOwner(
@@ -17800,24 +17803,25 @@
     private void onCreateAndProvisionManagedProfileCompleted(
             ManagedProfileProvisioningParams provisioningParams) {}
 
-    private void maybeInstallDeviceManagerRoleHolderInUser(int targetUserId) {
-        String deviceManagerRoleHolderPackageName = getDeviceManagerRoleHolderPackageName(mContext);
-        if (deviceManagerRoleHolderPackageName == null) {
-            Slogf.d(LOG_TAG, "No device manager role holder specified.");
+    private void maybeInstallDevicePolicyManagementRoleHolderInUser(int targetUserId) {
+        String devicePolicyManagerRoleHolderPackageName =
+                getDevicePolicyManagementRoleHolderPackageName(mContext);
+        if (devicePolicyManagerRoleHolderPackageName == null) {
+            Slogf.d(LOG_TAG, "No device policy management role holder specified.");
             return;
         }
         try {
             if (mIPackageManager.isPackageAvailable(
-                    deviceManagerRoleHolderPackageName, targetUserId)) {
-                Slogf.d(LOG_TAG, "The device manager role holder "
-                        + deviceManagerRoleHolderPackageName + " is already installed in "
+                    devicePolicyManagerRoleHolderPackageName, targetUserId)) {
+                Slogf.d(LOG_TAG, "The device policy management role holder "
+                        + devicePolicyManagerRoleHolderPackageName + " is already installed in "
                         + "user " + targetUserId);
                 return;
             }
-            Slogf.d(LOG_TAG, "Installing the device manager role holder "
-                    + deviceManagerRoleHolderPackageName + " in user " + targetUserId);
+            Slogf.d(LOG_TAG, "Installing the device policy management role holder "
+                    + devicePolicyManagerRoleHolderPackageName + " in user " + targetUserId);
             mIPackageManager.installExistingPackageAsUser(
-                    deviceManagerRoleHolderPackageName,
+                    devicePolicyManagerRoleHolderPackageName,
                     targetUserId,
                     PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS,
                     PackageManager.INSTALL_REASON_POLICY,
@@ -17827,10 +17831,10 @@
         }
     }
 
-    private String getDeviceManagerRoleHolderPackageName(Context context) {
+    private String getDevicePolicyManagementRoleHolderPackageName(Context context) {
         RoleManager roleManager = context.getSystemService(RoleManager.class);
         List<String> roleHolders =
-                roleManager.getRoleHolders(RoleManager.ROLE_DEVICE_MANAGER);
+                roleManager.getRoleHolders(RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT);
         if (roleHolders.isEmpty()) {
             return null;
         }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java b/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java
index 598f9e8..f3b164c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java
@@ -94,7 +94,7 @@
 
         String getActiveApexPackageNameContainingPackage(String packageName);
 
-        String getDeviceManagerRoleHolderPackageName(Context context);
+        String getDevicePolicyManagementRoleHolderPackageName(Context context);
     }
 
     private static final class DefaultInjector implements Injector {
@@ -110,11 +110,11 @@
         }
 
         @Override
-        public String getDeviceManagerRoleHolderPackageName(Context context) {
+        public String getDevicePolicyManagementRoleHolderPackageName(Context context) {
             return Binder.withCleanCallingIdentity(() -> {
                 RoleManager roleManager = context.getSystemService(RoleManager.class);
                 List<String> roleHolders =
-                        roleManager.getRoleHolders(RoleManager.ROLE_DEVICE_MANAGER);
+                        roleManager.getRoleHolders(RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT);
                 if (roleHolders.isEmpty()) {
                     return null;
                 }
@@ -166,7 +166,7 @@
     private Set<String> getDeviceManagerRoleHolders() {
         HashSet<String> result = new HashSet<>();
         String deviceManagerRoleHolderPackageName =
-                mInjector.getDeviceManagerRoleHolderPackageName(mContext);
+                mInjector.getDevicePolicyManagementRoleHolderPackageName(mContext);
         if (deviceManagerRoleHolderPackageName != null) {
             result.add(deviceManagerRoleHolderPackageName);
         }
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index a14e14b..4ff53ea 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -422,8 +422,8 @@
     private static final String SAFETY_CENTER_SERVICE_CLASS =
             "com.android.safetycenter.SafetyCenterService";
 
-    private static final String SUPPLEMENTALPROCESS_SERVICE_CLASS =
-            "com.android.server.supplementalprocess.SupplementalProcessManagerService$Lifecycle";
+    private static final String SDK_SANDBOX_MANAGER_SERVICE_CLASS =
+            "com.android.server.sdksandbox.SdkSandboxManagerService$Lifecycle";
 
     private static final String TETHERING_CONNECTOR_CLASS = "android.net.ITetheringConnector";
 
@@ -2605,9 +2605,9 @@
         mSystemServiceManager.startService(IncidentCompanionService.class);
         t.traceEnd();
 
-        // Supplemental Process
-        t.traceBegin("StartSupplementalProcessManagerService");
-        mSystemServiceManager.startService(SUPPLEMENTALPROCESS_SERVICE_CLASS);
+        // SdkSandboxManagerService
+        t.traceBegin("StarSdkSandboxManagerService");
+        mSystemServiceManager.startService(SDK_SANDBOX_MANAGER_SERVICE_CLASS);
         t.traceEnd();
 
         if (safeMode) {
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 3e60af3..670c159 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -52,7 +52,7 @@
         "service-blobstore",
         "service-jobscheduler",
         "service-permission.impl",
-        "service-supplementalprocess.impl",
+        "service-sdksandbox.impl",
         "services.companion",
         "services.core",
         "services.devicepolicy",
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
index 26b5218..4a40b5f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
@@ -1000,7 +1000,7 @@
             final String dummyPackageName = "com.android.test";
             final String dummyClassName = ".Foo";
             app.setHostingRecord(HostingRecord.byAppZygote(new ComponentName(
-                    dummyPackageName, dummyClassName), "", definingUid));
+                    dummyPackageName, dummyClassName), "", definingUid, ""));
         }
         app.mServices.setConnectionGroup(connectionGroup);
         app.mState.setReportedProcState(procState);
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/FakeServiceConnector.java b/services/tests/mockingservicestests/src/com/android/server/app/FakeServiceConnector.java
index 0ae509e..182430d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/FakeServiceConnector.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/FakeServiceConnector.java
@@ -17,6 +17,7 @@
 package com.android.server.app;
 
 
+import android.annotation.Nullable;
 import android.os.IInterface;
 
 import com.android.internal.infra.AndroidFuture;
@@ -30,9 +31,10 @@
  * Tests provide a service instance via {@link #FakeServiceConnector(IInterface)} that will be
  * connected to and used to fulfill service jobs.
  */
-final class FakeServiceConnector<T extends IInterface> implements
-        ServiceConnector<T> {
+final class FakeServiceConnector<T extends IInterface> implements ServiceConnector<T> {
     private final T mService;
+    @Nullable
+    private ServiceLifecycleCallbacks mServiceLifecycleCallbacks;
     private boolean mIsConnected;
     private int mConnectCount = 0;
 
@@ -96,9 +98,17 @@
 
     @Override
     public void unbind() {
+        if (mServiceLifecycleCallbacks != null) {
+            mServiceLifecycleCallbacks.onDisconnected(mService);
+        }
         mIsConnected = false;
     }
 
+    @Override
+    public void setServiceLifecycleCallbacks(@Nullable ServiceLifecycleCallbacks<T> callbacks) {
+        mServiceLifecycleCallbacks = callbacks;
+    }
+
     private void markPossibleConnection() {
         if (mIsConnected) {
             return;
@@ -106,6 +116,21 @@
 
         mConnectCount += 1;
         mIsConnected = true;
+
+        if (mServiceLifecycleCallbacks != null) {
+            mServiceLifecycleCallbacks.onConnected(mService);
+        }
+    }
+
+    public void killServiceProcess() {
+        if (!mIsConnected) {
+            return;
+        }
+        mIsConnected = false;
+
+        if (mServiceLifecycleCallbacks != null) {
+            mServiceLifecycleCallbacks.onBinderDied();
+        }
     }
 
     /**
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java
index ed232e5..32a31d0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java
@@ -713,6 +713,35 @@
     }
 
     @Test
+    public void gameSessionServiceDies_severalActiveGameSessions_destroysGameSessions() {
+        mGameServiceProviderInstance.start();
+
+        startTask(10, GAME_A_MAIN_ACTIVITY);
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
+        mFakeGameService.requestCreateGameSession(10);
+
+        FakeGameSession gameSession10 = new FakeGameSession();
+        SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class);
+        mFakeGameSessionService.removePendingFutureForTaskId(10)
+                .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10));
+
+        startTask(11, GAME_A_MAIN_ACTIVITY);
+        mFakeGameService.requestCreateGameSession(11);
+
+        FakeGameSession gameSession11 = new FakeGameSession();
+        SurfacePackage mockSurfacePackage11 = Mockito.mock(SurfacePackage.class);
+        mFakeGameSessionService.removePendingFutureForTaskId(11)
+                .complete(new CreateGameSessionResult(gameSession11, mockSurfacePackage11));
+
+        mFakeGameSessionServiceConnector.killServiceProcess();
+
+        assertThat(gameSession10.mIsDestroyed).isTrue();
+        assertThat(gameSession11.mIsDestroyed).isTrue();
+        assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue();
+        assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isFalse();
+    }
+
+    @Test
     public void stop_severalActiveGameSessions_destroysGameSessionsAndUnbinds() throws Exception {
         mGameServiceProviderInstance.start();
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index 6510cd1..cb9f003 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -70,7 +70,7 @@
 import com.android.server.pm.pkg.parsing.ParsingPackageUtils
 import com.android.server.pm.resolution.ComponentResolver
 import com.android.server.pm.verify.domain.DomainVerificationManagerInternal
-import com.android.server.supplementalprocess.SupplementalProcessManagerLocal
+import com.android.server.sdksandbox.SdkSandboxManagerLocal
 import com.android.server.testutils.TestHandler
 import com.android.server.testutils.mock
 import com.android.server.testutils.nullable
@@ -577,7 +577,7 @@
                 1L, systemPartitions[0].privAppFolder,
                 withPackage = { pkg: PackageImpl ->
                     val applicationInfo: ApplicationInfo = createBasicApplicationInfo(pkg)
-                    mockQueryServices(SupplementalProcessManagerLocal.SERVICE_INTERFACE,
+                    mockQueryServices(SdkSandboxManagerLocal.SERVICE_INTERFACE,
                             createBasicServiceInfo(
                                     pkg, applicationInfo, "SupplementalProcessService"))
                     pkg
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 953b536..1f016fb 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -154,6 +154,7 @@
                 mMockWindowMagnificationMgr);
         when(mMockMagnificationController.getFullScreenMagnificationController()).thenReturn(
                 mMockFullScreenMagnificationController);
+        when(mMockMagnificationController.supportWindowMagnification()).thenReturn(true);
         when(mMockWindowManagerService.getAccessibilityController()).thenReturn(
                 mMockA11yController);
         when(mMockA11yController.isAccessibilityTracingEnabled()).thenReturn(false);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
index 3ce2ed8..f3a0b7f 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
@@ -1158,7 +1158,7 @@
         MagnificationCallbacks callbacks = getMagnificationCallbacks(DISPLAY_0);
         callbacks.onImeWindowVisibilityChanged(true);
         mMessageCapturingHandler.sendAllMessages();
-        verify(mRequestObserver).onImeWindowVisibilityChanged(eq(true));
+        verify(mRequestObserver).onImeWindowVisibilityChanged(eq(DISPLAY_0), eq(true));
     }
 
     private void setScaleToMagnifying() {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
index ec59090..cc6d761 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
@@ -41,6 +41,7 @@
 
 import android.accessibilityservice.MagnificationConfig;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.Region;
@@ -100,6 +101,8 @@
     @Mock
     private Context mContext;
     @Mock
+    PackageManager mPackageManager;
+    @Mock
     private FullScreenMagnificationController mScreenMagnificationController;
     private MagnificationScaleProvider mScaleProvider;
     @Captor
@@ -136,6 +139,7 @@
         mMockResolver = new MockContentResolver();
         mMockResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
         when(mContext.getContentResolver()).thenReturn(mMockResolver);
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
         Settings.Secure.putFloatForUser(mMockResolver,
                 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, DEFAULT_SCALE,
                 CURRENT_USER_ID);
@@ -748,7 +752,7 @@
         MagnificationController spyController = spy(mMagnificationController);
         spyController.onWindowMagnificationActivationState(TEST_DISPLAY, true);
 
-        spyController.onImeWindowVisibilityChanged(true);
+        spyController.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
 
         verify(spyController).logMagnificationModeWithIme(
                 eq(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW));
@@ -759,7 +763,7 @@
         MagnificationController spyController = spy(mMagnificationController);
         spyController.onFullScreenMagnificationActivationState(TEST_DISPLAY, true);
 
-        spyController.onImeWindowVisibilityChanged(true);
+        spyController.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
 
         verify(spyController).logMagnificationModeWithIme(
                 eq(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN));
@@ -768,7 +772,7 @@
     @Test
     public void imeWindowStateShown_noMagnifying_noLogAnyMode() {
         MagnificationController spyController = spy(mMagnificationController);
-        spyController.onImeWindowVisibilityChanged(true);
+        spyController.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
 
         verify(spyController, never()).logMagnificationModeWithIme(anyInt());
     }
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java
index 3822dc3..4b77764 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java
@@ -94,6 +94,14 @@
     }
 
     @Test
+    public void moveWindowMagnifierToPosition() throws RemoteException {
+        mConnectionWrapper.moveWindowMagnifierToPosition(TEST_DISPLAY, 100, 150,
+                mAnimationCallback);
+        verify(mConnection).moveWindowMagnifierToPosition(eq(TEST_DISPLAY),
+                eq(100f), eq(150f), any(IRemoteMagnificationAnimationCallback.class));
+    }
+
+    @Test
     public void showMagnificationButton() throws RemoteException {
         mConnectionWrapper.showMagnificationButton(TEST_DISPLAY,
                 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
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 0742c09..978000a 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
@@ -21,6 +21,8 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.notNull;
 import static org.mockito.Mockito.doAnswer;
@@ -54,6 +56,8 @@
 import android.view.accessibility.IWindowMagnificationConnectionCallback;
 import android.view.accessibility.MagnificationAnimationCallback;
 
+import androidx.test.core.app.ApplicationProvider;
+
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.server.LocalServices;
 import com.android.server.accessibility.AccessibilityTraceManager;
@@ -99,12 +103,7 @@
                 mMockCallback, mMockTrace, new MagnificationScaleProvider(mContext));
 
         when(mContext.getContentResolver()).thenReturn(mResolver);
-        doAnswer((InvocationOnMock invocation) -> {
-            final boolean connect = (Boolean) invocation.getArguments()[0];
-            mWindowMagnificationManager.setConnection(
-                    connect ? mMockConnection.getConnection() : null);
-            return null;
-        }).when(mMockStatusBarManagerInternal).requestWindowMagnificationConnection(anyBoolean());
+        stubSetConnection(false);
 
         mResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
         Settings.Secure.putFloatForUser(mResolver,
@@ -112,6 +111,25 @@
                 CURRENT_USER_ID);
     }
 
+    private void stubSetConnection(boolean needDelay) {
+        doAnswer((InvocationOnMock invocation) -> {
+            final boolean connect = (Boolean) invocation.getArguments()[0];
+            // Simulates setConnection() called by another process.
+            if (needDelay) {
+                final Context context = ApplicationProvider.getApplicationContext();
+                context.getMainThreadHandler().postDelayed(
+                        () -> {
+                            mWindowMagnificationManager.setConnection(
+                                    connect ? mMockConnection.getConnection() : null);
+                        }, 10);
+            } else {
+                mWindowMagnificationManager.setConnection(
+                        connect ? mMockConnection.getConnection() : null);
+            }
+            return true;
+        }).when(mMockStatusBarManagerInternal).requestWindowMagnificationConnection(anyBoolean());
+    }
+
     @Test
     public void setConnection_connectionIsNull_wrapperIsNullAndLinkToDeath() {
         mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
@@ -275,32 +293,33 @@
     }
 
     @Test
-    public void onRectangleOnScreenRequested_trackingDisabledByOnDrag_withoutMovingMagnification()
+    public void onRectangleOnScreenRequested_trackingDisabledByOnDrag_withoutMovingMagnifier()
             throws RemoteException {
         mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
         mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
+        mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
         final Region outRegion = new Region();
         mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion);
         final Rect requestedRect = outRegion.getBounds();
         requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10);
-        mMockConnection.getConnectionCallback().onDrag(TEST_DISPLAY);
+        mMockConnection.getConnectionCallback().onMove(TEST_DISPLAY);
 
         mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY,
                 requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom);
 
-        verify(mMockConnection.getConnection(), never()).enableWindowMagnification(eq(TEST_DISPLAY),
-                eq(3f), eq(requestedRect.exactCenterX()), eq(requestedRect.exactCenterY()),
-                eq(0f), eq(0f), notNull());
+        verify(mMockConnection.getConnection(), never())
+                .moveWindowMagnifierToPosition(anyInt(), anyFloat(), anyFloat(), any());
     }
 
 
     @Test
-    public void onRectangleOnScreenRequested_trackingDisabledByScroll_withoutMovingMagnification()
+    public void onRectangleOnScreenRequested_trackingDisabledByScroll_withoutMovingMagnifier()
             throws RemoteException {
         final float distanceX = 10f;
         final float distanceY = 10f;
         mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
         mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
+        mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
         final Region outRegion = new Region();
         mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion);
         final Rect requestedRect = outRegion.getBounds();
@@ -310,16 +329,16 @@
         mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY,
                 requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom);
 
-        verify(mMockConnection.getConnection(), never()).enableWindowMagnification(eq(TEST_DISPLAY),
-                eq(3f), eq(requestedRect.exactCenterX()), eq(requestedRect.exactCenterY()),
-                eq(0f), eq(0f), notNull());
+        verify(mMockConnection.getConnection(), never())
+                .moveWindowMagnifierToPosition(anyInt(), anyFloat(), anyFloat(), any());
     }
 
     @Test
-    public void onRectangleOnScreenRequested_requestRectangleInBound_withoutMovingMagnification()
+    public void onRectangleOnScreenRequested_requestRectangleInBound_withoutMovingMagnifier()
             throws RemoteException {
         mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
         mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
+        mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
         final Region outRegion = new Region();
         mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion);
         final Rect requestedRect = outRegion.getBounds();
@@ -328,12 +347,11 @@
         mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY,
                 requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom);
 
-        verify(mMockConnection.getConnection(), never()).enableWindowMagnification(eq(TEST_DISPLAY),
-                eq(3f), eq(500f), eq(500f), eq(0f), eq(0f), notNull());
+        verify(mMockConnection.getConnection(), never())
+                .moveWindowMagnifierToPosition(anyInt(), anyFloat(), anyFloat(), any());
     }
-
     @Test
-    public void onRectangleOnScreenRequested_trackingEnabledByDefault_movingMagnification()
+    public void onRectangleOnScreenRequested_imeVisibilityDefaultInvisible_withoutMovingMagnifier()
             throws RemoteException {
         mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
         mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
@@ -345,20 +363,56 @@
         mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY,
                 requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom);
 
-        verify(mMockConnection.getConnection()).enableWindowMagnification(eq(TEST_DISPLAY), eq(3f),
+        verify(mMockConnection.getConnection(), never())
+                .moveWindowMagnifierToPosition(anyInt(), anyFloat(), anyFloat(), any());
+    }
+
+    @Test
+    public void onRectangleOnScreenRequested_trackingEnabledByDefault_movingMagnifier()
+            throws RemoteException {
+        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
+        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
+        mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
+        final Region outRegion = new Region();
+        mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion);
+        final Rect requestedRect = outRegion.getBounds();
+        requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10);
+
+        mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY,
+                requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom);
+
+        verify(mMockConnection.getConnection()).moveWindowMagnifierToPosition(eq(TEST_DISPLAY),
                 eq(requestedRect.exactCenterX()), eq(requestedRect.exactCenterY()),
-                eq(0f), eq(0f), notNull());
+                any(IRemoteMagnificationAnimationCallback.class));
     }
 
     @Test
-    public void onRectangleOnScreenRequested_trackingEnabledByDragAndReset_movingMagnification()
+    public void onRectangleOnScreenRequested_imeInvisible_withoutMovingMagnifier()
             throws RemoteException {
-        final PointF initialPoint = new PointF(50f, 50f);
         mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f,
-                initialPoint.x, initialPoint.y);
-        mMockConnection.getConnectionCallback().onDrag(TEST_DISPLAY);
-        mWindowMagnificationManager.onImeWindowVisibilityChanged(true);
+        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
+        mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
+        final Region outRegion = new Region();
+        mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion);
+        final Rect requestedRect = outRegion.getBounds();
+        requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10);
+        mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, false);
+
+        mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY,
+                requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom);
+
+        verify(mMockConnection.getConnection(), never())
+                .moveWindowMagnifierToPosition(anyInt(), anyFloat(), anyFloat(), any());
+    }
+
+    @Test
+    public void onRectangleOnScreenRequested_trackingEnabledByDragAndReset_movingMagnifier()
+            throws RemoteException {
+        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
+        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
+        mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
+        mMockConnection.getConnectionCallback().onMove(TEST_DISPLAY);
+        mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
         final Region outRegion = new Region();
         mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion);
         final Rect requestedRect = outRegion.getBounds();
@@ -367,16 +421,16 @@
         mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY,
                 requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom);
 
-        verify(mMockConnection.getConnection()).enableWindowMagnification(eq(TEST_DISPLAY),
-                eq(3f), eq(requestedRect.exactCenterX()), eq(requestedRect.exactCenterY()),
-                eq(0f), eq(0f), notNull());
+        verify(mMockConnection.getConnection()).moveWindowMagnifierToPosition(eq(TEST_DISPLAY),
+                eq(requestedRect.exactCenterX()), eq(requestedRect.exactCenterY()),
+                any(IRemoteMagnificationAnimationCallback.class));
     }
 
     @Test
-    public void onRectangleOnScreenRequested_followTypingIsDisabled_withoutMovingMagnification()
-            throws RemoteException {
+    public void onRectangleOnScreenRequested_followTypingIsDisabled_withoutMovingMagnifier() {
         mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
         mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
+        mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
         final Region beforeRegion = new Region();
         mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, beforeRegion);
         final Rect requestedRect = beforeRegion.getBounds();
@@ -392,6 +446,48 @@
     }
 
     @Test
+    public void onRectangleOnScreenRequested_trackingDisabled_withoutMovingMagnifier() {
+        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
+        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
+        mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
+        mWindowMagnificationManager.setTrackingTypingFocusEnabled(TEST_DISPLAY, false);
+        final Region beforeRegion = new Region();
+        mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, beforeRegion);
+        final Rect requestedRect = beforeRegion.getBounds();
+        requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10);
+
+        mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY,
+                requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom);
+
+        final Region afterRegion = new Region();
+        mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, afterRegion);
+        assertEquals(afterRegion, beforeRegion);
+    }
+
+    @Test
+    public void onRectangleOnScreenRequested_trackingDisabledAndEnabledMagnifier_movingMagnifier()
+            throws RemoteException {
+        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
+        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
+        mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
+        mWindowMagnificationManager.setTrackingTypingFocusEnabled(TEST_DISPLAY, false);
+        final Region beforeRegion = new Region();
+        mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, beforeRegion);
+        final Rect requestedRect = beforeRegion.getBounds();
+        requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10);
+        mWindowMagnificationManager.disableWindowMagnification(TEST_DISPLAY, false);
+        // Enabling a window magnifier again will turn on the tracking typing focus functionality.
+        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, NaN, NaN, NaN);
+
+        mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY,
+                requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom);
+
+        verify(mMockConnection.getConnection()).moveWindowMagnifierToPosition(eq(TEST_DISPLAY),
+                eq(requestedRect.exactCenterX()), eq(requestedRect.exactCenterY()),
+                any(IRemoteMagnificationAnimationCallback.class));
+    }
+
+    @Test
     public void moveWindowMagnifier_enabled_invokeConnectionMethod() throws RemoteException {
         mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
         mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 2f, NaN, NaN);
@@ -464,7 +560,7 @@
     public void
             requestConnectionToNull_disableAllMagnifiersAndRequestWindowMagnificationConnection()
             throws RemoteException {
-        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
+        assertTrue(mWindowMagnificationManager.requestConnection(true));
         mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, NaN, NaN);
 
         assertTrue(mWindowMagnificationManager.requestConnection(false));
@@ -499,7 +595,7 @@
 
     @Test
     public void requestConnectionToNull_expectedGetterResults() {
-        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
+        mWindowMagnificationManager.requestConnection(true);
         mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, 1, 1);
 
         mWindowMagnificationManager.requestConnection(false);
@@ -513,6 +609,20 @@
     }
 
     @Test
+    public void enableWindowMagnification_connecting_invokeConnectionMethodAfterConnected()
+            throws RemoteException {
+        stubSetConnection(true);
+        mWindowMagnificationManager.requestConnection(true);
+
+        assertTrue(mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, 1, 1));
+
+        // Invoke enableWindowMagnification if the connection is connected.
+        verify(mMockConnection.getConnection()).enableWindowMagnification(
+                eq(TEST_DISPLAY), eq(3f),
+                eq(1f), eq(1f), eq(0f), eq(0f), notNull());
+    }
+
+    @Test
     public void resetAllMagnification_enabledBySameId_windowMagnifiersDisabled() {
         mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
         mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
index b255a35..25cf8a8 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -219,7 +219,7 @@
     public void testMultiAuth_singleSensor_fingerprintSensorStartsAfterDialogAnimationCompletes()
             throws Exception {
         setupFingerprint(0 /* id */, FingerprintSensorProperties.TYPE_UDFPS_OPTICAL);
-        testMultiAuth_fingerprintSensorStartsAfter(false /* fingerprintStartsAfterDelay */);
+        testMultiAuth_fingerprintSensorStartsAfterUINotifies();
     }
 
     @Test
@@ -227,10 +227,10 @@
             throws Exception {
         setupFingerprint(0 /* id */, FingerprintSensorProperties.TYPE_UDFPS_OPTICAL);
         setupFace(1 /* id */, false, mock(IBiometricAuthenticator.class));
-        testMultiAuth_fingerprintSensorStartsAfter(true /* fingerprintStartsAfterDelay */);
+        testMultiAuth_fingerprintSensorStartsAfterUINotifies();
     }
 
-    public void testMultiAuth_fingerprintSensorStartsAfter(boolean fingerprintStartsAfterDelay)
+    public void testMultiAuth_fingerprintSensorStartsAfterUINotifies()
             throws Exception {
         final long operationId = 123;
         final int userId = 10;
@@ -274,12 +274,6 @@
 
         // Notify AuthSession that the UI is shown. Then, fingerprint sensor should be started.
         session.onDialogAnimatedIn();
-        if (fingerprintStartsAfterDelay) {
-            assertEquals(STATE_AUTH_STARTED_UI_SHOWING, session.getState());
-            assertEquals(BiometricSensor.STATE_COOKIE_RETURNED,
-                    session.mPreAuthInfo.eligibleSensors.get(fingerprintSensorId).getSensorState());
-            session.onStartFingerprint();
-        }
         assertEquals(STATE_AUTH_STARTED_UI_SHOWING, session.getState());
         assertEquals(BiometricSensor.STATE_AUTHENTICATING,
                 session.mPreAuthInfo.eligibleSensors.get(fingerprintSensorId).getSensorState());
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index b94b690..2ad5eae 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.biometrics;
 
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
 import static android.hardware.biometrics.BiometricManager.Authenticators;
 import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_DEFAULT;
 
@@ -85,14 +86,11 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.Random;
-import java.util.concurrent.atomic.AtomicLong;
 
 @Presubmit
 @SmallTest
 public class BiometricServiceTest {
 
-    private static final String TAG = "BiometricServiceTest";
-
     private static final String TEST_PACKAGE_NAME = "test_package";
     private static final long TEST_REQUEST_ID = 44;
 
@@ -153,7 +151,7 @@
                 .thenReturn(mock(BiometricStrengthController.class));
         when(mInjector.getTrustManager()).thenReturn(mTrustManager);
         when(mInjector.getDevicePolicyManager(any())).thenReturn(mDevicePolicyManager);
-        when(mInjector.getRequestGenerator()).thenReturn(new AtomicLong(TEST_REQUEST_ID - 1));
+        when(mInjector.getRequestGenerator()).thenReturn(() -> TEST_REQUEST_ID);
 
         when(mResources.getString(R.string.biometric_error_hw_unavailable))
                 .thenReturn(ERROR_HW_UNAVAILABLE);
@@ -178,22 +176,22 @@
         invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
                 true /* requireConfirmation */, null /* authenticators */);
         waitForIdle();
-        verify(mReceiver1.asBinder()).linkToDeath(eq(mBiometricService.mCurrentAuthSession),
+        verify(mReceiver1.asBinder()).linkToDeath(eq(mBiometricService.mAuthSession),
                 anyInt());
 
-        mBiometricService.mBiometricSensorReceiver.onError(
+        mBiometricService.mAuthSession.mSensorReceiver.onError(
                 SENSOR_ID_FACE,
-                getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+                getCookieForCurrentSession(mBiometricService.mAuthSession),
                 BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
                 0 /* vendorCode */);
         waitForIdle();
 
-        assertEquals(STATE_AUTH_PAUSED, mBiometricService.mCurrentAuthSession.getState());
+        assertEquals(STATE_AUTH_PAUSED, mBiometricService.mAuthSession.getState());
 
-        mBiometricService.mCurrentAuthSession.binderDied();
+        mBiometricService.mAuthSession.binderDied();
         waitForIdle();
 
-        assertNull(mBiometricService.mCurrentAuthSession);
+        assertNull(mBiometricService.mAuthSession);
         verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();
         verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
     }
@@ -205,31 +203,31 @@
         invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
                 true /* requireConfirmation */, null /* authenticators */);
         waitForIdle();
-        verify(mReceiver1.asBinder()).linkToDeath(eq(mBiometricService.mCurrentAuthSession),
+        verify(mReceiver1.asBinder()).linkToDeath(eq(mBiometricService.mAuthSession),
                 anyInt());
 
-        assertEquals(STATE_AUTH_STARTED, mBiometricService.mCurrentAuthSession.getState());
-        mBiometricService.mCurrentAuthSession.binderDied();
+        assertEquals(STATE_AUTH_STARTED, mBiometricService.mAuthSession.getState());
+        mBiometricService.mAuthSession.binderDied();
         waitForIdle();
 
-        assertNotNull(mBiometricService.mCurrentAuthSession);
+        assertNotNull(mBiometricService.mAuthSession);
         verify(mBiometricService.mStatusBarService, never()).hideAuthenticationDialog();
         assertEquals(STATE_CLIENT_DIED_CANCELLING,
-                mBiometricService.mCurrentAuthSession.getState());
+                mBiometricService.mAuthSession.getState());
 
-        verify(mBiometricService.mCurrentAuthSession.mPreAuthInfo.eligibleSensors.get(0).impl)
+        verify(mBiometricService.mAuthSession.mPreAuthInfo.eligibleSensors.get(0).impl)
                 .cancelAuthenticationFromService(any(), any(), anyLong());
 
         // Simulate ERROR_CANCELED received from HAL
-        mBiometricService.mBiometricSensorReceiver.onError(
+        mBiometricService.mAuthSession.mSensorReceiver.onError(
                 SENSOR_ID_FACE,
-                getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+                getCookieForCurrentSession(mBiometricService.mAuthSession),
                 BiometricConstants.BIOMETRIC_ERROR_CANCELED,
                 0 /* vendorCode */);
         waitForIdle();
         verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();
         verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
-        assertNull(mBiometricService.mCurrentAuthSession);
+        assertNull(mBiometricService.mAuthSession);
     }
 
     @Test
@@ -265,12 +263,12 @@
                 Authenticators.DEVICE_CREDENTIAL);
         waitForIdle();
 
-        assertNotNull(mBiometricService.mCurrentAuthSession);
+        assertNotNull(mBiometricService.mAuthSession);
         assertEquals(STATE_SHOWING_DEVICE_CREDENTIAL,
-                mBiometricService.mCurrentAuthSession.getState());
+                mBiometricService.mAuthSession.getState());
         // StatusBar showBiometricDialog invoked
         verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
-                eq(mBiometricService.mCurrentAuthSession.mPromptInfo),
+                eq(mBiometricService.mAuthSession.mPromptInfo),
                 any(IBiometricSysuiReceiver.class),
                 AdditionalMatchers.aryEq(new int[0]) /* sensorIds */,
                 eq(true) /* credentialAllowed */,
@@ -304,21 +302,21 @@
         mBiometricService = new BiometricService(mContext, mInjector);
         mBiometricService.onStart();
         mBiometricService.mImpl.registerAuthenticator(0 /* id */,
-                BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
+                TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
                 mFingerprintAuthenticator);
 
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
                 null /* authenticators */);
         waitForIdle();
         verify(mReceiver1).onError(
-                eq(BiometricAuthenticator.TYPE_FINGERPRINT),
+                eq(TYPE_FINGERPRINT),
                 eq(BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS),
                 eq(0 /* vendorCode */));
     }
 
     @Test
     public void testAuthenticate_notStrongEnough_returnsHardwareNotPresent() throws Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_WEAK);
+        setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_WEAK);
 
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
                 Authenticators.BIOMETRIC_STRONG);
@@ -335,7 +333,7 @@
         // is able to proceed.
 
         final int[] modalities = new int[] {
-                BiometricAuthenticator.TYPE_FINGERPRINT,
+                TYPE_FINGERPRINT,
                 BiometricAuthenticator.TYPE_FACE,
         };
 
@@ -356,7 +354,7 @@
 
         // StatusBar showBiometricDialog invoked with face, which was set up to be STRONG
         verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
-                eq(mBiometricService.mCurrentAuthSession.mPromptInfo),
+                eq(mBiometricService.mAuthSession.mPromptInfo),
                 any(IBiometricSysuiReceiver.class),
                 AdditionalMatchers.aryEq(new int[] {SENSOR_ID_FACE}),
                 eq(false) /* credentialAllowed */,
@@ -377,14 +375,14 @@
         mBiometricService = new BiometricService(mContext, mInjector);
         mBiometricService.onStart();
         mBiometricService.mImpl.registerAuthenticator(0 /* id */,
-                BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
+                TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
                 mFingerprintAuthenticator);
 
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
                 null /* authenticators */);
         waitForIdle();
         verify(mReceiver1).onError(
-                eq(BiometricAuthenticator.TYPE_FINGERPRINT),
+                eq(TYPE_FINGERPRINT),
                 eq(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE),
                 eq(0 /* vendorCode */));
     }
@@ -415,13 +413,13 @@
         waitForIdle();
         verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
         final byte[] HAT = generateRandomHAT();
-        mBiometricService.mBiometricSensorReceiver.onAuthenticationSucceeded(
+        mBiometricService.mAuthSession.mSensorReceiver.onAuthenticationSucceeded(
                 SENSOR_ID_FACE,
                 HAT);
         waitForIdle();
         // Confirmation is required
         assertEquals(STATE_AUTH_PENDING_CONFIRM,
-                mBiometricService.mCurrentAuthSession.getState());
+                mBiometricService.mAuthSession.getState());
 
         // Enrolled, not disabled in settings, user doesn't require confirmation in settings
         resetReceivers();
@@ -431,25 +429,25 @@
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
                 null /* authenticators */);
         waitForIdle();
-        mBiometricService.mBiometricSensorReceiver.onAuthenticationSucceeded(
+        mBiometricService.mAuthSession.mSensorReceiver.onAuthenticationSucceeded(
                 SENSOR_ID_FACE,
                 HAT);
         waitForIdle();
         // Confirmation not required, waiting for dialog to dismiss
         assertEquals(STATE_AUTHENTICATED_PENDING_SYSUI,
-                mBiometricService.mCurrentAuthSession.getState());
+                mBiometricService.mAuthSession.getState());
 
     }
 
     @Test
     public void testAuthenticate_happyPathWithoutConfirmation_strongBiometric() throws Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+        setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
         testAuthenticate_happyPathWithoutConfirmation(true /* isStrongBiometric */);
     }
 
     @Test
     public void testAuthenticate_happyPathWithoutConfirmation_weakBiometric() throws Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_WEAK);
+        setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_WEAK);
         testAuthenticate_happyPathWithoutConfirmation(false /* isStrongBiometric */);
     }
 
@@ -461,7 +459,7 @@
         waitForIdle();
 
         // Creates a pending auth session with the correct initial states
-        assertEquals(STATE_AUTH_CALLED, mBiometricService.mCurrentAuthSession.getState());
+        assertEquals(STATE_AUTH_CALLED, mBiometricService.mAuthSession.getState());
 
         // Invokes <Modality>Service#prepareForAuthentication
         ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
@@ -477,19 +475,19 @@
                 cookieCaptor.capture() /* cookie */,
                 anyBoolean() /* allowBackgroundAuthentication */);
 
-        // onReadyForAuthentication, mCurrentAuthSession state OK
-        mBiometricService.mImpl.onReadyForAuthentication(cookieCaptor.getValue());
+        // onReadyForAuthentication, mAuthSession state OK
+        mBiometricService.mImpl.onReadyForAuthentication(TEST_REQUEST_ID, cookieCaptor.getValue());
         waitForIdle();
-        assertEquals(STATE_AUTH_STARTED, mBiometricService.mCurrentAuthSession.getState());
+        assertEquals(STATE_AUTH_STARTED, mBiometricService.mAuthSession.getState());
 
         // startPreparedClient invoked
-        mBiometricService.mCurrentAuthSession.onDialogAnimatedIn();
+        mBiometricService.mAuthSession.onDialogAnimatedIn();
         verify(mBiometricService.mSensors.get(0).impl)
                 .startPreparedClient(cookieCaptor.getValue());
 
         // StatusBar showBiometricDialog invoked
         verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
-                eq(mBiometricService.mCurrentAuthSession.mPromptInfo),
+                eq(mBiometricService.mAuthSession.mPromptInfo),
                 any(IBiometricSysuiReceiver.class),
                 any(),
                 eq(false) /* credentialAllowed */,
@@ -502,18 +500,18 @@
 
         // Hardware authenticated
         final byte[] HAT = generateRandomHAT();
-        mBiometricService.mBiometricSensorReceiver.onAuthenticationSucceeded(
+        mBiometricService.mAuthSession.mSensorReceiver.onAuthenticationSucceeded(
                 SENSOR_ID_FINGERPRINT,
                 HAT);
         waitForIdle();
         // Waiting for SystemUI to send dismissed callback
         assertEquals(STATE_AUTHENTICATED_PENDING_SYSUI,
-                mBiometricService.mCurrentAuthSession.getState());
+                mBiometricService.mAuthSession.getState());
         // Notify SystemUI hardware authenticated
-        verify(mBiometricService.mStatusBarService).onBiometricAuthenticated();
+        verify(mBiometricService.mStatusBarService).onBiometricAuthenticated(TYPE_FINGERPRINT);
 
         // SystemUI sends callback with dismissed reason
-        mBiometricService.mSysuiReceiver.onDialogDismissed(
+        mBiometricService.mAuthSession.mSysuiReceiver.onDialogDismissed(
                 BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED,
                 null /* credentialAttestation */);
         waitForIdle();
@@ -527,7 +525,7 @@
         verify(mReceiver1).onAuthenticationSucceeded(
                 BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC);
         // Current session becomes null
-        assertNull(mBiometricService.mCurrentAuthSession);
+        assertNull(mBiometricService.mAuthSession);
     }
 
     @Test
@@ -542,11 +540,11 @@
         waitForIdle();
 
         assertEquals(STATE_SHOWING_DEVICE_CREDENTIAL,
-                mBiometricService.mCurrentAuthSession.getState());
+                mBiometricService.mAuthSession.getState());
         assertEquals(Authenticators.DEVICE_CREDENTIAL,
-                mBiometricService.mCurrentAuthSession.mPromptInfo.getAuthenticators());
+                mBiometricService.mAuthSession.mPromptInfo.getAuthenticators());
         verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
-                eq(mBiometricService.mCurrentAuthSession.mPromptInfo),
+                eq(mBiometricService.mAuthSession.mPromptInfo),
                 any(IBiometricSysuiReceiver.class),
                 AdditionalMatchers.aryEq(new int[0]) /* sensorIds */,
                 eq(true) /* credentialAllowed */,
@@ -578,16 +576,16 @@
         // Test authentication succeeded goes to PENDING_CONFIRMATION and that the HAT is not
         // sent to KeyStore yet
         final byte[] HAT = generateRandomHAT();
-        mBiometricService.mBiometricSensorReceiver.onAuthenticationSucceeded(
+        mBiometricService.mAuthSession.mSensorReceiver.onAuthenticationSucceeded(
                 SENSOR_ID_FACE,
                 HAT);
         waitForIdle();
         // Waiting for SystemUI to send confirmation callback
-        assertEquals(STATE_AUTH_PENDING_CONFIRM, mBiometricService.mCurrentAuthSession.getState());
+        assertEquals(STATE_AUTH_PENDING_CONFIRM, mBiometricService.mAuthSession.getState());
         verify(mBiometricService.mKeyStore, never()).addAuthToken(any(byte[].class));
 
         // SystemUI sends confirm, HAT is sent to keystore and client is notified.
-        mBiometricService.mSysuiReceiver.onDialogDismissed(
+        mBiometricService.mAuthSession.mSysuiReceiver.onDialogDismissed(
                 BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED,
                 null /* credentialAttestation */);
         waitForIdle();
@@ -624,33 +622,34 @@
         invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
                 false /* requireConfirmation */, null /* authenticators */);
 
-        mBiometricService.mBiometricSensorReceiver.onAuthenticationFailed(SENSOR_ID_FACE);
+        mBiometricService.mAuthSession.mSensorReceiver.onAuthenticationFailed(SENSOR_ID_FACE);
         waitForIdle();
 
         verify(mBiometricService.mStatusBarService).onBiometricError(
-                eq(BiometricAuthenticator.TYPE_NONE),
+                eq(BiometricAuthenticator.TYPE_FACE),
                 eq(BiometricConstants.BIOMETRIC_PAUSED_REJECTED),
                 eq(0 /* vendorCode */));
         verify(mReceiver1).onAuthenticationFailed();
-        assertEquals(STATE_AUTH_PAUSED, mBiometricService.mCurrentAuthSession.getState());
+        assertEquals(STATE_AUTH_PAUSED, mBiometricService.mAuthSession.getState());
     }
 
     @Test
     public void testRejectFingerprint_whenAuthenticating_notifiesAndKeepsAuthenticating()
             throws Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+        setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
         invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
                 false /* requireConfirmation */, null /* authenticators */);
 
-        mBiometricService.mBiometricSensorReceiver.onAuthenticationFailed(SENSOR_ID_FINGERPRINT);
+        mBiometricService.mAuthSession.mSensorReceiver
+                .onAuthenticationFailed(SENSOR_ID_FINGERPRINT);
         waitForIdle();
 
         verify(mBiometricService.mStatusBarService).onBiometricError(
-                eq(BiometricAuthenticator.TYPE_NONE),
+                eq(TYPE_FINGERPRINT),
                 eq(BiometricConstants.BIOMETRIC_PAUSED_REJECTED),
                 eq(0 /* vendorCode */));
         verify(mReceiver1).onAuthenticationFailed();
-        assertEquals(STATE_AUTH_STARTED, mBiometricService.mCurrentAuthSession.getState());
+        assertEquals(STATE_AUTH_STARTED, mBiometricService.mAuthSession.getState());
     }
 
     @Test
@@ -678,14 +677,14 @@
         invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
                 false /* requireConfirmation */, null /* authenticators */);
 
-        mBiometricService.mBiometricSensorReceiver.onError(
+        mBiometricService.mAuthSession.mSensorReceiver.onError(
                 SENSOR_ID_FACE,
-                getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+                getCookieForCurrentSession(mBiometricService.mAuthSession),
                 BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
                 0 /* vendorCode */);
         waitForIdle();
 
-        assertEquals(STATE_AUTH_PAUSED, mBiometricService.mCurrentAuthSession.getState());
+        assertEquals(STATE_AUTH_PAUSED, mBiometricService.mAuthSession.getState());
         verify(mBiometricService.mStatusBarService).onBiometricError(
                 eq(BiometricAuthenticator.TYPE_FACE),
                 eq(BiometricConstants.BIOMETRIC_ERROR_TIMEOUT),
@@ -694,15 +693,15 @@
         verify(mReceiver1, never()).onAuthenticationFailed();
 
         // No auth session. Pressing try again will create one.
-        assertEquals(STATE_AUTH_PAUSED, mBiometricService.mCurrentAuthSession.getState());
+        assertEquals(STATE_AUTH_PAUSED, mBiometricService.mAuthSession.getState());
 
         // Pressing "Try again" on SystemUI
-        mBiometricService.mSysuiReceiver.onTryAgainPressed();
+        mBiometricService.mAuthSession.mSysuiReceiver.onTryAgainPressed();
         waitForIdle();
         verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
 
         // AuthSession is now resuming
-        assertEquals(STATE_AUTH_PAUSED_RESUMING, mBiometricService.mCurrentAuthSession.getState());
+        assertEquals(STATE_AUTH_PAUSED_RESUMING, mBiometricService.mAuthSession.getState());
 
         // Test resuming when hardware becomes ready. SystemUI should not be requested to
         // show another dialog since it's already showing.
@@ -728,14 +727,14 @@
         invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
                 false /* requireConfirmation */, null /* authenticators */);
 
-        mBiometricService.mBiometricSensorReceiver.onError(
+        mBiometricService.mAuthSession.mSensorReceiver.onError(
                 SENSOR_ID_FACE,
-                getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+                getCookieForCurrentSession(mBiometricService.mAuthSession),
                 BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
                 0 /* vendorCode */);
-        mBiometricService.mBiometricSensorReceiver.onError(
+        mBiometricService.mAuthSession.mSensorReceiver.onError(
                 SENSOR_ID_FACE,
-                getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+                getCookieForCurrentSession(mBiometricService.mAuthSession),
                 BiometricConstants.BIOMETRIC_ERROR_CANCELED,
                 0 /* vendorCode */);
         waitForIdle();
@@ -748,7 +747,7 @@
         // Dialog is hidden immediately
         verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();
         // Auth session is over
-        assertNull(mBiometricService.mCurrentAuthSession);
+        assertNull(mBiometricService.mAuthSession);
     }
 
     @Test
@@ -757,61 +756,61 @@
         // For errors that show in SystemUI, BiometricService stays in STATE_ERROR_PENDING_SYSUI
         // until SystemUI notifies us that the dialog is dismissed at which point the current
         // session is done.
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+        setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
         invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
                 false /* requireConfirmation */, null /* authenticators */);
 
-        mBiometricService.mBiometricSensorReceiver.onError(
+        mBiometricService.mAuthSession.mSensorReceiver.onError(
                 SENSOR_ID_FINGERPRINT,
-                getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+                getCookieForCurrentSession(mBiometricService.mAuthSession),
                 BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS,
                 0 /* vendorCode */);
         waitForIdle();
 
         // Sends error to SystemUI and does not notify client yet
-        assertEquals(STATE_ERROR_PENDING_SYSUI, mBiometricService.mCurrentAuthSession.getState());
+        assertEquals(STATE_ERROR_PENDING_SYSUI, mBiometricService.mAuthSession.getState());
         verify(mBiometricService.mStatusBarService).onBiometricError(
-                eq(BiometricAuthenticator.TYPE_FINGERPRINT),
+                eq(TYPE_FINGERPRINT),
                 eq(BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS),
                 eq(0 /* vendorCode */));
         verify(mBiometricService.mStatusBarService, never()).hideAuthenticationDialog();
         verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
 
         // SystemUI animation completed, client is notified, auth session is over
-        mBiometricService.mSysuiReceiver.onDialogDismissed(
+        mBiometricService.mAuthSession.mSysuiReceiver.onDialogDismissed(
                 BiometricPrompt.DISMISSED_REASON_ERROR, null /* credentialAttestation */);
         waitForIdle();
         verify(mReceiver1).onError(
-                eq(BiometricAuthenticator.TYPE_FINGERPRINT),
+                eq(TYPE_FINGERPRINT),
                 eq(BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS),
                 eq(0 /* vendorCode */));
-        assertNull(mBiometricService.mCurrentAuthSession);
+        assertNull(mBiometricService.mAuthSession);
     }
 
     @Test
     public void testErrorFromHal_whilePreparingAuthentication_credentialAllowed() throws Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+        setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
                 false /* requireConfirmation */,
                 Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK);
         waitForIdle();
 
-        assertEquals(STATE_AUTH_CALLED, mBiometricService.mCurrentAuthSession.getState());
-        mBiometricService.mBiometricSensorReceiver.onError(
+        assertEquals(STATE_AUTH_CALLED, mBiometricService.mAuthSession.getState());
+        mBiometricService.mAuthSession.mSensorReceiver.onError(
                 SENSOR_ID_FINGERPRINT,
-                getCookieForPendingSession(mBiometricService.mCurrentAuthSession),
+                getCookieForPendingSession(mBiometricService.mAuthSession),
                 BiometricConstants.BIOMETRIC_ERROR_LOCKOUT,
                 0 /* vendorCode */);
         waitForIdle();
 
         // We should be showing device credential now
-        assertNotNull(mBiometricService.mCurrentAuthSession);
+        assertNotNull(mBiometricService.mAuthSession);
         assertEquals(STATE_SHOWING_DEVICE_CREDENTIAL,
-                mBiometricService.mCurrentAuthSession.getState());
+                mBiometricService.mAuthSession.getState());
         assertEquals(Authenticators.DEVICE_CREDENTIAL,
-                mBiometricService.mCurrentAuthSession.mPromptInfo.getAuthenticators());
+                mBiometricService.mAuthSession.mPromptInfo.getAuthenticators());
         verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
-                eq(mBiometricService.mCurrentAuthSession.mPromptInfo),
+                eq(mBiometricService.mAuthSession.mPromptInfo),
                 any(IBiometricSysuiReceiver.class),
                 AdditionalMatchers.aryEq(new int[0]) /* sensorIds */,
                 eq(true) /* credentialAllowed */,
@@ -826,23 +825,23 @@
     @Test
     public void testErrorFromHal_whilePreparingAuthentication_credentialNotAllowed()
             throws Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+        setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
                 false /* requireConfirmation */, null /* authenticators */);
         waitForIdle();
 
-        mBiometricService.mBiometricSensorReceiver.onError(
+        mBiometricService.mAuthSession.mSensorReceiver.onError(
                 SENSOR_ID_FINGERPRINT,
-                getCookieForPendingSession(mBiometricService.mCurrentAuthSession),
+                getCookieForPendingSession(mBiometricService.mAuthSession),
                 BiometricConstants.BIOMETRIC_ERROR_LOCKOUT,
                 0 /* vendorCode */);
         waitForIdle();
 
         // Error is sent to client
-        verify(mReceiver1).onError(eq(BiometricAuthenticator.TYPE_FINGERPRINT),
+        verify(mReceiver1).onError(eq(TYPE_FINGERPRINT),
                 eq(BiometricConstants.BIOMETRIC_ERROR_LOCKOUT),
                 eq(0) /* vendorCode */);
-        assertNull(mBiometricService.mCurrentAuthSession);
+        assertNull(mBiometricService.mAuthSession);
     }
 
     @Test
@@ -861,7 +860,7 @@
 
     private void testBiometricAuth_whenLockout(@LockoutTracker.LockoutMode int lockoutMode,
             int biometricPromptError) throws Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+        setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
         when(mFingerprintAuthenticator.getLockoutModeForUser(anyInt()))
                 .thenReturn(lockoutMode);
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
@@ -869,16 +868,15 @@
         waitForIdle();
 
         // Modality and error are sent
-        verify(mReceiver1).onError(eq(BiometricAuthenticator.TYPE_FINGERPRINT),
+        verify(mReceiver1).onError(eq(TYPE_FINGERPRINT),
                 eq(biometricPromptError), eq(0) /* vendorCode */);
     }
 
     @Test
     public void testBiometricOrCredentialAuth_whenBiometricLockout_showsCredential()
             throws Exception {
-        when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
-                .thenReturn(true);
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+        when(mTrustManager.isDeviceSecure(anyInt(), anyInt())).thenReturn(true);
+        setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
         when(mFingerprintAuthenticator.getLockoutModeForUser(anyInt()))
                 .thenReturn(LockoutTracker.LOCKOUT_PERMANENT);
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
@@ -887,13 +885,13 @@
         waitForIdle();
 
         verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
-        assertNotNull(mBiometricService.mCurrentAuthSession);
+        assertNotNull(mBiometricService.mAuthSession);
         assertEquals(STATE_SHOWING_DEVICE_CREDENTIAL,
-                mBiometricService.mCurrentAuthSession.getState());
+                mBiometricService.mAuthSession.getState());
         assertEquals(Authenticators.DEVICE_CREDENTIAL,
-                mBiometricService.mCurrentAuthSession.mPromptInfo.getAuthenticators());
+                mBiometricService.mAuthSession.mPromptInfo.getAuthenticators());
         verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
-                eq(mBiometricService.mCurrentAuthSession.mPromptInfo),
+                eq(mBiometricService.mAuthSession.mPromptInfo),
                 any(IBiometricSysuiReceiver.class),
                 AdditionalMatchers.aryEq(new int[0]) /* sensorIds */,
                 eq(true) /* credentialAllowed */,
@@ -959,73 +957,73 @@
     @Test
     public void testErrorFromHal_whileShowingDeviceCredential_doesntNotifySystemUI()
             throws Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+        setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
         invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
                 false /* requireConfirmation */,
                 Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK);
 
-        mBiometricService.mSysuiReceiver.onDeviceCredentialPressed();
+        mBiometricService.mAuthSession.mSysuiReceiver.onDeviceCredentialPressed();
         waitForIdle();
 
         assertEquals(STATE_SHOWING_DEVICE_CREDENTIAL,
-                mBiometricService.mCurrentAuthSession.getState());
+                mBiometricService.mAuthSession.getState());
         verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
 
-        mBiometricService.mBiometricSensorReceiver.onError(
+        mBiometricService.mAuthSession.mSensorReceiver.onError(
                 SENSOR_ID_FINGERPRINT,
-                getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+                getCookieForCurrentSession(mBiometricService.mAuthSession),
                 BiometricConstants.BIOMETRIC_ERROR_CANCELED,
                 0 /* vendorCode */);
         waitForIdle();
 
         assertEquals(STATE_SHOWING_DEVICE_CREDENTIAL,
-                mBiometricService.mCurrentAuthSession.getState());
+                mBiometricService.mAuthSession.getState());
         verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
     }
 
     @Test
     public void testLockout_whileAuthenticating_credentialAllowed() throws Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+        setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
         invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
                 false /* requireConfirmation */,
                 Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK);
 
-        assertEquals(STATE_AUTH_STARTED, mBiometricService.mCurrentAuthSession.getState());
+        assertEquals(STATE_AUTH_STARTED, mBiometricService.mAuthSession.getState());
 
-        mBiometricService.mBiometricSensorReceiver.onError(
+        mBiometricService.mAuthSession.mSensorReceiver.onError(
                 SENSOR_ID_FINGERPRINT,
-                getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+                getCookieForCurrentSession(mBiometricService.mAuthSession),
                 BiometricConstants.BIOMETRIC_ERROR_LOCKOUT,
                 0 /* vendorCode */);
         waitForIdle();
 
         assertEquals(STATE_SHOWING_DEVICE_CREDENTIAL,
-                mBiometricService.mCurrentAuthSession.getState());
+                mBiometricService.mAuthSession.getState());
         verify(mBiometricService.mStatusBarService).onBiometricError(
-                eq(BiometricAuthenticator.TYPE_FINGERPRINT),
+                eq(TYPE_FINGERPRINT),
                 eq(BiometricConstants.BIOMETRIC_ERROR_LOCKOUT),
                 eq(0 /* vendorCode */));
     }
 
     @Test
     public void testLockout_whenAuthenticating_credentialNotAllowed() throws Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+        setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
         invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
                 false /* requireConfirmation */, null /* authenticators */);
 
-        assertEquals(STATE_AUTH_STARTED, mBiometricService.mCurrentAuthSession.getState());
+        assertEquals(STATE_AUTH_STARTED, mBiometricService.mAuthSession.getState());
 
-        mBiometricService.mBiometricSensorReceiver.onError(
+        mBiometricService.mAuthSession.mSensorReceiver.onError(
                 SENSOR_ID_FINGERPRINT,
-                getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+                getCookieForCurrentSession(mBiometricService.mAuthSession),
                 BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS,
                 0 /* vendorCode */);
         waitForIdle();
 
         assertEquals(STATE_ERROR_PENDING_SYSUI,
-                mBiometricService.mCurrentAuthSession.getState());
+                mBiometricService.mAuthSession.getState());
         verify(mBiometricService.mStatusBarService).onBiometricError(
-                eq(BiometricAuthenticator.TYPE_FINGERPRINT),
+                eq(TYPE_FINGERPRINT),
                 eq(BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS),
                 eq(0 /* vendorCode */));
     }
@@ -1033,20 +1031,20 @@
     @Test
     public void testDismissedReasonUserCancel_whileAuthenticating_cancelsHalAuthentication()
             throws Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+        setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
         invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
                 false /* requireConfirmation */, null /* authenticators */);
 
-        mBiometricService.mSysuiReceiver.onDialogDismissed(
+        mBiometricService.mAuthSession.mSysuiReceiver.onDialogDismissed(
                 BiometricPrompt.DISMISSED_REASON_USER_CANCEL, null /* credentialAttestation */);
         waitForIdle();
         verify(mReceiver1).onError(
-                eq(BiometricAuthenticator.TYPE_FINGERPRINT),
+                eq(TYPE_FINGERPRINT),
                 eq(BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED),
                 eq(0 /* vendorCode */));
         verify(mBiometricService.mSensors.get(0).impl).cancelAuthenticationFromService(
                 any(), any(), anyLong());
-        assertNull(mBiometricService.mCurrentAuthSession);
+        assertNull(mBiometricService.mAuthSession);
     }
 
     @Test
@@ -1055,12 +1053,12 @@
         invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
                 false /* requireConfirmation */, null /* authenticators */);
 
-        mBiometricService.mBiometricSensorReceiver.onError(
+        mBiometricService.mAuthSession.mSensorReceiver.onError(
                 SENSOR_ID_FACE,
-                getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+                getCookieForCurrentSession(mBiometricService.mAuthSession),
                 BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
                 0 /* vendorCode */);
-        mBiometricService.mSysuiReceiver.onDialogDismissed(
+        mBiometricService.mAuthSession.mSysuiReceiver.onDialogDismissed(
                 BiometricPrompt.DISMISSED_REASON_NEGATIVE, null /* credentialAttestation */);
         waitForIdle();
 
@@ -1069,18 +1067,17 @@
     }
 
     @Test
-    public void testDismissedReasonUserCancel_whilePaused_invokesHalCancel() throws
-            Exception {
+    public void testDismissedReasonUserCancel_whilePaused_invokesHalCancel() throws Exception {
         setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
         invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
                 false /* requireConfirmation */, null /* authenticators */);
 
-        mBiometricService.mBiometricSensorReceiver.onError(
+        mBiometricService.mAuthSession.mSensorReceiver.onError(
                 SENSOR_ID_FACE,
-                getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+                getCookieForCurrentSession(mBiometricService.mAuthSession),
                 BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
                 0 /* vendorCode */);
-        mBiometricService.mSysuiReceiver.onDialogDismissed(
+        mBiometricService.mAuthSession.mSysuiReceiver.onDialogDismissed(
                 BiometricPrompt.DISMISSED_REASON_USER_CANCEL, null /* credentialAttestation */);
         waitForIdle();
 
@@ -1094,10 +1091,10 @@
         invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
                 true /* requireConfirmation */, null /* authenticators */);
 
-        mBiometricService.mBiometricSensorReceiver.onAuthenticationSucceeded(
+        mBiometricService.mAuthSession.mSensorReceiver.onAuthenticationSucceeded(
                 SENSOR_ID_FACE,
                 new byte[69] /* HAT */);
-        mBiometricService.mSysuiReceiver.onDialogDismissed(
+        mBiometricService.mAuthSession.mSysuiReceiver.onDialogDismissed(
                 BiometricPrompt.DISMISSED_REASON_USER_CANCEL, null /* credentialAttestation */);
         waitForIdle();
 
@@ -1108,19 +1105,19 @@
                 eq(BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED),
                 eq(0 /* vendorCode */));
         verify(mBiometricService.mKeyStore, never()).addAuthToken(any(byte[].class));
-        assertNull(mBiometricService.mCurrentAuthSession);
+        assertNull(mBiometricService.mAuthSession);
     }
 
     @Test
     public void testAcquire_whenAuthenticating_sentToSystemUI() throws Exception {
         when(mContext.getResources().getString(anyInt())).thenReturn("test string");
 
-        final int modality = BiometricAuthenticator.TYPE_FINGERPRINT;
+        final int modality = TYPE_FINGERPRINT;
         setupAuthForOnly(modality, Authenticators.BIOMETRIC_STRONG);
         invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
                 false /* requireConfirmation */, null /* authenticators */);
 
-        mBiometricService.mBiometricSensorReceiver.onAcquired(
+        mBiometricService.mAuthSession.mSensorReceiver.onAcquired(
                 SENSOR_ID_FINGERPRINT,
                 FingerprintManager.FINGERPRINT_ACQUIRED_IMAGER_DIRTY,
                 0 /* vendorCode */);
@@ -1130,29 +1127,29 @@
         // string is retrieved for now, but it's also very unlikely to break anyway.
         verify(mBiometricService.mStatusBarService)
                 .onBiometricHelp(eq(modality), anyString());
-        assertEquals(STATE_AUTH_STARTED, mBiometricService.mCurrentAuthSession.getState());
+        assertEquals(STATE_AUTH_STARTED, mBiometricService.mAuthSession.getState());
     }
 
     @Test
     public void testCancel_whenAuthenticating() throws Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+        setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
         invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
                 false /* requireConfirmation */, null /* authenticators */);
 
-        mBiometricService.mImpl.cancelAuthentication(mBiometricService.mCurrentAuthSession.mToken,
+        mBiometricService.mImpl.cancelAuthentication(mBiometricService.mAuthSession.mToken,
                 TEST_PACKAGE_NAME, TEST_REQUEST_ID);
         waitForIdle();
 
         // Pretend that the HAL has responded to cancel with ERROR_CANCELED
-        mBiometricService.mBiometricSensorReceiver.onError(
+        mBiometricService.mAuthSession.mSensorReceiver.onError(
                 SENSOR_ID_FINGERPRINT,
-                getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+                getCookieForCurrentSession(mBiometricService.mAuthSession),
                 BiometricConstants.BIOMETRIC_ERROR_CANCELED,
                 0 /* vendorCode */);
         waitForIdle();
 
         // Hides system dialog and invokes the onError callback
-        verify(mReceiver1).onError(eq(BiometricAuthenticator.TYPE_FINGERPRINT),
+        verify(mReceiver1).onError(eq(TYPE_FINGERPRINT),
                 eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED),
                 eq(0 /* vendorCode */));
         verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();
@@ -1161,7 +1158,7 @@
     @Test
     public void testCanAuthenticate_whenDeviceHasRequestedBiometricStrength() throws Exception {
         // When only biometric is requested, and sensor is strong enough
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+        setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
 
         assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
                 invokeCanAuthenticate(mBiometricService, Authenticators.BIOMETRIC_STRONG));
@@ -1170,7 +1167,7 @@
     @Test
     public void testCanAuthenticate_whenDeviceDoesNotHaveRequestedBiometricStrength()
             throws Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_WEAK);
+        setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_WEAK);
 
         // When only biometric is requested, and sensor is not strong enough
         when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
@@ -1208,9 +1205,8 @@
     @Test
     public void testCanAuthenticate_whenNoBiometricsEnrolled() throws Exception {
         // With credential set up, test the following.
-        when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
-                .thenReturn(true);
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
+        when(mTrustManager.isDeviceSecure(anyInt(), anyInt())).thenReturn(true);
+        setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
                 false /* enrolled */);
 
         // When only biometric is requested
@@ -1277,7 +1273,7 @@
     private void testCanAuthenticate_whenLockedOut(@LockoutTracker.LockoutMode int lockoutMode)
             throws Exception {
         // When only biometric is requested, and sensor is strong enough
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+        setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
 
         when(mFingerprintAuthenticator.getLockoutModeForUser(anyInt()))
                 .thenReturn(lockoutMode);
@@ -1311,7 +1307,7 @@
         for (int i = 0; i < testCases.length; i++) {
             final BiometricSensor sensor =
                     new BiometricSensor(mContext, 0 /* id */,
-                            BiometricAuthenticator.TYPE_FINGERPRINT,
+                            TYPE_FINGERPRINT,
                             testCases[i][0],
                             mock(IBiometricAuthenticator.class)) {
                         @Override
@@ -1341,7 +1337,7 @@
                 .thenReturn(true);
         when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
         mBiometricService.mImpl.registerAuthenticator(0 /* testId */,
-                BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
+                TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
                 mFingerprintAuthenticator);
 
         verify(mBiometricService.mBiometricStrengthController).updateStrengths();
@@ -1360,7 +1356,7 @@
                 .thenReturn(true);
         when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
         mBiometricService.mImpl.registerAuthenticator(testId /* id */,
-                BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
+                TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
                 mFingerprintAuthenticator);
 
         // Downgrade the authenticator
@@ -1378,7 +1374,7 @@
                 false /* requireConfirmation */, authenticators);
         waitForIdle();
         verify(mReceiver1).onError(
-                eq(BiometricAuthenticator.TYPE_FINGERPRINT),
+                eq(TYPE_FINGERPRINT),
                 eq(BiometricPrompt.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED),
                 eq(0) /* vendorCode */);
 
@@ -1392,7 +1388,7 @@
                 authenticators);
         waitForIdle();
         verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
-                eq(mBiometricService.mCurrentAuthSession.mPromptInfo),
+                eq(mBiometricService.mAuthSession.mPromptInfo),
                 any(IBiometricSysuiReceiver.class),
                 AdditionalMatchers.aryEq(new int[] {testId}),
                 eq(false) /* credentialAllowed */,
@@ -1414,9 +1410,9 @@
                 false /* requireConfirmation */,
                 authenticators);
         waitForIdle();
-        assertTrue(Utils.isCredentialRequested(mBiometricService.mCurrentAuthSession.mPromptInfo));
+        assertTrue(Utils.isCredentialRequested(mBiometricService.mAuthSession.mPromptInfo));
         verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
-                eq(mBiometricService.mCurrentAuthSession.mPromptInfo),
+                eq(mBiometricService.mAuthSession.mPromptInfo),
                 any(IBiometricSysuiReceiver.class),
                 AdditionalMatchers.aryEq(new int[0]) /* sensorIds */,
                 eq(true) /* credentialAllowed */,
@@ -1442,7 +1438,7 @@
                 false /* requireConfirmation */, authenticators);
         waitForIdle();
         verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
-                eq(mBiometricService.mCurrentAuthSession.mPromptInfo),
+                eq(mBiometricService.mAuthSession.mPromptInfo),
                 any(IBiometricSysuiReceiver.class),
                 AdditionalMatchers.aryEq(new int[] {testId}) /* sensorIds */,
                 eq(false) /* credentialAllowed */,
@@ -1495,29 +1491,29 @@
     @Test
     public void testWorkAuthentication_fingerprintWorksIfNotDisabledByDevicePolicyManager()
             throws Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+        setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
         when(mDevicePolicyManager
                 .getKeyguardDisabledFeatures(any() /* admin */, anyInt() /* userHandle */))
                 .thenReturn(~DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT);
         invokeAuthenticateForWorkApp(mBiometricService.mImpl, mReceiver1,
                 Authenticators.BIOMETRIC_STRONG);
         waitForIdle();
-        assertEquals(STATE_AUTH_CALLED, mBiometricService.mCurrentAuthSession.getState());
+        assertEquals(STATE_AUTH_CALLED, mBiometricService.mAuthSession.getState());
         startPendingAuthSession(mBiometricService);
         waitForIdle();
-        assertEquals(STATE_AUTH_STARTED, mBiometricService.mCurrentAuthSession.getState());
+        assertEquals(STATE_AUTH_STARTED, mBiometricService.mAuthSession.getState());
     }
 
     @Test
     public void testAuthentication_normalAppIgnoresDevicePolicy() throws Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+        setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
         when(mDevicePolicyManager
                 .getKeyguardDisabledFeatures(any() /* admin */, anyInt() /* userHandle */))
                 .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT);
         invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
                 false /* requireConfirmation */, Authenticators.BIOMETRIC_STRONG);
         waitForIdle();
-        assertEquals(STATE_AUTH_STARTED, mBiometricService.mCurrentAuthSession.getState());
+        assertEquals(STATE_AUTH_STARTED, mBiometricService.mAuthSession.getState());
     }
 
     @Test
@@ -1530,18 +1526,17 @@
         invokeAuthenticateForWorkApp(mBiometricService.mImpl, mReceiver1,
                 Authenticators.BIOMETRIC_STRONG);
         waitForIdle();
-        assertEquals(STATE_AUTH_CALLED, mBiometricService.mCurrentAuthSession.getState());
+        assertEquals(STATE_AUTH_CALLED, mBiometricService.mAuthSession.getState());
         startPendingAuthSession(mBiometricService);
         waitForIdle();
-        assertEquals(STATE_AUTH_STARTED, mBiometricService.mCurrentAuthSession.getState());
+        assertEquals(STATE_AUTH_STARTED, mBiometricService.mAuthSession.getState());
     }
 
     @Test
     public void testWorkAuthentication_fingerprintFailsIfDisabledByDevicePolicyManager()
             throws Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
-        when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
-                .thenReturn(true);
+        setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+        when(mTrustManager.isDeviceSecure(anyInt(), anyInt())).thenReturn(true);
         when(mDevicePolicyManager
                 .getKeyguardDisabledFeatures(any() /* admin */, anyInt() /* userHandle */))
                 .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT);
@@ -1555,9 +1550,9 @@
         invokeAuthenticateForWorkApp(mBiometricService.mImpl, mReceiver2,
                 Authenticators.BIOMETRIC_STRONG | Authenticators.DEVICE_CREDENTIAL);
         waitForIdle();
-        assertNotNull(mBiometricService.mCurrentAuthSession);
+        assertNotNull(mBiometricService.mAuthSession);
         assertEquals(STATE_SHOWING_DEVICE_CREDENTIAL,
-                mBiometricService.mCurrentAuthSession.getState());
+                mBiometricService.mAuthSession.getState());
         verify(mReceiver2, never()).onError(anyInt(), anyInt(), anyInt());
     }
 
@@ -1580,7 +1575,7 @@
 
         when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
 
-        if ((modality & BiometricAuthenticator.TYPE_FINGERPRINT) != 0) {
+        if ((modality & TYPE_FINGERPRINT) != 0) {
             when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any()))
                     .thenReturn(enrolled);
             when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
@@ -1614,7 +1609,7 @@
             final int modality = modalities[i];
             final int strength = strengths[i];
 
-            if ((modality & BiometricAuthenticator.TYPE_FINGERPRINT) != 0) {
+            if ((modality & TYPE_FINGERPRINT) != 0) {
                 when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any()))
                         .thenReturn(true);
                 when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
@@ -1654,8 +1649,9 @@
         startPendingAuthSession(mBiometricService);
         waitForIdle();
 
-        assertNotNull(mBiometricService.mCurrentAuthSession);
-        assertEquals(STATE_AUTH_STARTED, mBiometricService.mCurrentAuthSession.getState());
+        assertNotNull(mBiometricService.mAuthSession);
+        assertEquals(TEST_REQUEST_ID, mBiometricService.mAuthSession.getRequestId());
+        assertEquals(STATE_AUTH_STARTED, mBiometricService.mAuthSession.getState());
 
         return requestId;
     }
@@ -1663,14 +1659,14 @@
     private static void startPendingAuthSession(BiometricService service) throws Exception {
         // Get the cookie so we can pretend the hardware is ready to authenticate
         // Currently we only support single modality per auth
-        final PreAuthInfo preAuthInfo = service.mCurrentAuthSession.mPreAuthInfo;
+        final PreAuthInfo preAuthInfo = service.mAuthSession.mPreAuthInfo;
         assertEquals(preAuthInfo.eligibleSensors.size(), 1);
         assertEquals(preAuthInfo.numSensorsWaitingForCookie(), 1);
 
         final int cookie = preAuthInfo.eligibleSensors.get(0).getCookie();
         assertNotEquals(cookie, 0);
 
-        service.mImpl.onReadyForAuthentication(cookie);
+        service.mImpl.onReadyForAuthentication(TEST_REQUEST_ID, cookie);
     }
 
     private static long invokeAuthenticate(IBiometricService.Stub service,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/CoexCoordinatorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/CoexCoordinatorTest.java
index bfb0be7..f40b31a 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/CoexCoordinatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/CoexCoordinatorTest.java
@@ -29,12 +29,8 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
-import static org.mockito.Mockito.withSettings;
 
-import android.content.Context;
 import android.hardware.biometrics.BiometricConstants;
-import android.os.Handler;
-import android.os.Looper;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.InstrumentationRegistry;
@@ -43,9 +39,11 @@
 import com.android.server.biometrics.sensors.fingerprint.Udfps;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 
 import java.util.LinkedList;
 
@@ -53,39 +51,38 @@
 @SmallTest
 public class CoexCoordinatorTest {
 
-    private static final String TAG = "CoexCoordinatorTest";
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
 
-    private CoexCoordinator mCoexCoordinator;
-    private Handler mHandler;
-
-    @Mock
-    private Context mContext;
     @Mock
     private CoexCoordinator.Callback mCallback;
     @Mock
     private CoexCoordinator.ErrorCallback mErrorCallback;
+    @Mock
+    private AuthenticationClient mFaceClient;
+    @Mock
+    private AuthenticationClient mFingerprintClient;
+    @Mock(extraInterfaces = {Udfps.class})
+    private AuthenticationClient mUdfpsClient;
+
+    private CoexCoordinator mCoexCoordinator;
 
     @Before
     public void setUp() {
-        MockitoAnnotations.initMocks(this);
-
-        mHandler = new Handler(Looper.getMainLooper());
-
         mCoexCoordinator = CoexCoordinator.getInstance();
         mCoexCoordinator.setAdvancedLogicEnabled(true);
         mCoexCoordinator.setFaceHapticDisabledWhenNonBypass(true);
+        mCoexCoordinator.reset();
     }
 
     @Test
     public void testBiometricPrompt_authSuccess() {
-        mCoexCoordinator.reset();
+        when(mFaceClient.isBiometricPrompt()).thenReturn(true);
 
-        AuthenticationClient<?> client = mock(AuthenticationClient.class);
-        when(client.isBiometricPrompt()).thenReturn(true);
+        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
 
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, client);
-
-        mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, client, mCallback);
+        mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */,
+                mFaceClient, mCallback);
         verify(mCallback).sendHapticFeedback();
         verify(mCallback).sendAuthenticationResult(eq(false) /* addAuthTokenIfStrong */);
         verify(mCallback).handleLifecycleAfterAuth();
@@ -93,15 +90,12 @@
 
     @Test
     public void testBiometricPrompt_authReject_whenNotLockedOut() {
-        mCoexCoordinator.reset();
+        when(mFaceClient.isBiometricPrompt()).thenReturn(true);
 
-        AuthenticationClient<?> client = mock(AuthenticationClient.class);
-        when(client.isBiometricPrompt()).thenReturn(true);
-
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, client);
+        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
 
         mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */,
-                client, LockoutTracker.LOCKOUT_NONE, mCallback);
+                mFaceClient, LockoutTracker.LOCKOUT_NONE, mCallback);
         verify(mCallback).sendHapticFeedback();
         verify(mCallback).sendAuthenticationResult(eq(false) /* addAuthTokenIfStrong */);
         verify(mCallback).handleLifecycleAfterAuth();
@@ -109,30 +103,97 @@
 
     @Test
     public void testBiometricPrompt_authReject_whenLockedOut() {
-        mCoexCoordinator.reset();
+        when(mFaceClient.isBiometricPrompt()).thenReturn(true);
 
-        AuthenticationClient<?> client = mock(AuthenticationClient.class);
-        when(client.isBiometricPrompt()).thenReturn(true);
-
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, client);
+        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
 
         mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */,
-                client, LockoutTracker.LOCKOUT_TIMED, mCallback);
+                mFaceClient, LockoutTracker.LOCKOUT_TIMED, mCallback);
         verify(mCallback).sendHapticFeedback();
         verify(mCallback, never()).sendAuthenticationResult(anyBoolean());
         verify(mCallback).handleLifecycleAfterAuth();
     }
 
     @Test
+    public void testBiometricPrompt_coex_success() {
+        testBiometricPrompt_coex_success(false /* twice */);
+    }
+
+    @Test
+    public void testBiometricPrompt_coex_successWithoutDouble() {
+        testBiometricPrompt_coex_success(true /* twice */);
+    }
+
+    private void testBiometricPrompt_coex_success(boolean twice) {
+        initFaceAndFingerprintForBiometricPrompt();
+        when(mFaceClient.wasAuthSuccessful()).thenReturn(true);
+        when(mUdfpsClient.wasAuthSuccessful()).thenReturn(twice, true);
+
+        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
+        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient);
+
+        mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */,
+                mFaceClient, mCallback);
+        mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */,
+                mUdfpsClient, mCallback);
+
+        if (twice) {
+            verify(mCallback, never()).sendHapticFeedback();
+        } else {
+            verify(mCallback).sendHapticFeedback();
+        }
+    }
+
+    @Test
+    public void testBiometricPrompt_coex_reject() {
+        initFaceAndFingerprintForBiometricPrompt();
+
+        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
+        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient);
+
+        mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */,
+                mFaceClient, LockoutTracker.LOCKOUT_NONE, mCallback);
+
+        verify(mCallback, never()).sendHapticFeedback();
+
+        mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */,
+                    mUdfpsClient, LockoutTracker.LOCKOUT_NONE, mCallback);
+
+        verify(mCallback).sendHapticFeedback();
+    }
+
+    @Test
+    public void testBiometricPrompt_coex_errorNoHaptics() {
+        initFaceAndFingerprintForBiometricPrompt();
+
+        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
+        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient);
+
+        mCoexCoordinator.onAuthenticationError(mFaceClient,
+                BiometricConstants.BIOMETRIC_ERROR_TIMEOUT, mErrorCallback);
+        mCoexCoordinator.onAuthenticationError(mUdfpsClient,
+                BiometricConstants.BIOMETRIC_ERROR_TIMEOUT, mErrorCallback);
+
+        verify(mErrorCallback, never()).sendHapticFeedback();
+    }
+
+    private void initFaceAndFingerprintForBiometricPrompt() {
+        when(mFaceClient.isKeyguard()).thenReturn(false);
+        when(mFaceClient.isBiometricPrompt()).thenReturn(true);
+        when(mFaceClient.wasAuthAttempted()).thenReturn(true);
+        when(mUdfpsClient.isKeyguard()).thenReturn(false);
+        when(mUdfpsClient.isBiometricPrompt()).thenReturn(true);
+        when(mUdfpsClient.wasAuthAttempted()).thenReturn(true);
+    }
+
+    @Test
     public void testKeyguard_faceAuthOnly_success() {
-        mCoexCoordinator.reset();
+        when(mFaceClient.isKeyguard()).thenReturn(true);
 
-        AuthenticationClient<?> client = mock(AuthenticationClient.class);
-        when(client.isKeyguard()).thenReturn(true);
+        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
 
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, client);
-
-        mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, client, mCallback);
+        mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */,
+                mFaceClient, mCallback);
         verify(mCallback).sendHapticFeedback();
         verify(mCallback).sendAuthenticationResult(eq(true) /* addAuthTokenIfStrong */);
         verify(mCallback).handleLifecycleAfterAuth();
@@ -140,21 +201,16 @@
 
     @Test
     public void testKeyguard_faceAuth_udfpsNotTouching_faceSuccess() {
-        mCoexCoordinator.reset();
+        when(mFaceClient.isKeyguard()).thenReturn(true);
 
-        AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
-        when(faceClient.isKeyguard()).thenReturn(true);
+        when(mUdfpsClient.isKeyguard()).thenReturn(true);
+        when(((Udfps) mUdfpsClient).isPointerDown()).thenReturn(false);
 
-        AuthenticationClient<?> udfpsClient = mock(AuthenticationClient.class,
-                withSettings().extraInterfaces(Udfps.class));
-        when(udfpsClient.isKeyguard()).thenReturn(true);
-        when(((Udfps) udfpsClient).isPointerDown()).thenReturn(false);
+        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
+        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient);
 
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient);
-
-        mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, faceClient,
-                mCallback);
+        mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */,
+                mFaceClient, mCallback);
         // Haptics tested in #testKeyguard_bypass_haptics. Let's leave this commented out (instead
         // of removed) to keep this context.
         // verify(mCallback).sendHapticFeedback();
@@ -192,25 +248,19 @@
 
     private void testKeyguard_bypass_haptics(boolean bypassEnabled, boolean faceAccepted,
             boolean shouldReceiveHaptics) {
-        mCoexCoordinator.reset();
+        when(mFaceClient.isKeyguard()).thenReturn(true);
+        when(mFaceClient.isKeyguardBypassEnabled()).thenReturn(bypassEnabled);
+        when(mUdfpsClient.isKeyguard()).thenReturn(true);
+        when(((Udfps) mUdfpsClient).isPointerDown()).thenReturn(false);
 
-        AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
-        when(faceClient.isKeyguard()).thenReturn(true);
-        when(faceClient.isKeyguardBypassEnabled()).thenReturn(bypassEnabled);
-
-        AuthenticationClient<?> udfpsClient = mock(AuthenticationClient.class,
-                withSettings().extraInterfaces(Udfps.class));
-        when(udfpsClient.isKeyguard()).thenReturn(true);
-        when(((Udfps) udfpsClient).isPointerDown()).thenReturn(false);
-
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient);
+        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
+        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient);
 
         if (faceAccepted) {
-            mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, faceClient,
+            mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, mFaceClient,
                     mCallback);
         } else {
-            mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, faceClient,
+            mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, mFaceClient,
                     LockoutTracker.LOCKOUT_NONE, mCallback);
         }
 
@@ -244,24 +294,18 @@
 
     private void testKeyguard_faceAuth_udfpsTouching_faceSuccess(boolean thenUdfpsAccepted,
             long udfpsRejectedAfterMs) {
-        mCoexCoordinator.reset();
+        when(mFaceClient.isKeyguard()).thenReturn(true);
+        when(mUdfpsClient.isKeyguard()).thenReturn(true);
+        when(((Udfps) mUdfpsClient).isPointerDown()).thenReturn(true);
+        when(mUdfpsClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
 
-        AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
-        when(faceClient.isKeyguard()).thenReturn(true);
-
-        AuthenticationClient<?> udfpsClient = mock(AuthenticationClient.class,
-                withSettings().extraInterfaces(Udfps.class));
-        when(udfpsClient.isKeyguard()).thenReturn(true);
-        when(((Udfps) udfpsClient).isPointerDown()).thenReturn(true);
-        when (udfpsClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
-
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient);
+        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
+        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient);
 
         // For easier reading
         final CoexCoordinator.Callback faceCallback = mCallback;
 
-        mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, faceClient,
+        mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, mFaceClient,
                 faceCallback);
         verify(faceCallback, never()).sendHapticFeedback();
         verify(faceCallback, never()).sendAuthenticationResult(anyBoolean());
@@ -272,9 +316,9 @@
         // Reset the mock
         CoexCoordinator.Callback udfpsCallback = mock(CoexCoordinator.Callback.class);
         assertEquals(1, mCoexCoordinator.mSuccessfulAuths.size());
-        assertEquals(faceClient, mCoexCoordinator.mSuccessfulAuths.get(0).mAuthenticationClient);
+        assertEquals(mFaceClient, mCoexCoordinator.mSuccessfulAuths.get(0).mAuthenticationClient);
         if (thenUdfpsAccepted) {
-            mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, udfpsClient,
+            mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, mUdfpsClient,
                     udfpsCallback);
             verify(udfpsCallback).sendHapticFeedback();
             verify(udfpsCallback).sendAuthenticationResult(true /* addAuthTokenIfStrong */);
@@ -284,7 +328,7 @@
 
             assertTrue(mCoexCoordinator.mSuccessfulAuths.isEmpty());
         } else {
-            mCoexCoordinator.onAuthenticationRejected(udfpsRejectedAfterMs, udfpsClient,
+            mCoexCoordinator.onAuthenticationRejected(udfpsRejectedAfterMs, mUdfpsClient,
                     LockoutTracker.LOCKOUT_NONE, udfpsCallback);
             if (udfpsRejectedAfterMs <= CoexCoordinator.SUCCESSFUL_AUTH_VALID_DURATION_MS) {
                 verify(udfpsCallback, never()).sendHapticFeedback();
@@ -310,56 +354,44 @@
 
     @Test
     public void testKeyguard_udfpsAuthSuccess_whileFaceScanning() {
-        mCoexCoordinator.reset();
+        when(mFaceClient.isKeyguard()).thenReturn(true);
+        when(mFaceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
+        when(mUdfpsClient.isKeyguard()).thenReturn(true);
+        when(((Udfps) mUdfpsClient).isPointerDown()).thenReturn(true);
 
-        AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
-        when(faceClient.isKeyguard()).thenReturn(true);
-        when(faceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
+        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
+        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient);
 
-        AuthenticationClient<?> udfpsClient = mock(AuthenticationClient.class,
-                withSettings().extraInterfaces(Udfps.class));
-        when(udfpsClient.isKeyguard()).thenReturn(true);
-        when(((Udfps) udfpsClient).isPointerDown()).thenReturn(true);
-
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient);
-
-        mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, udfpsClient,
+        mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, mUdfpsClient,
                 mCallback);
         verify(mCallback).sendHapticFeedback();
         verify(mCallback).sendAuthenticationResult(eq(true));
-        verify(faceClient).cancel();
+        verify(mFaceClient).cancel();
         verify(mCallback).handleLifecycleAfterAuth();
     }
 
     @Test
     public void testKeyguard_faceRejectedWhenUdfpsTouching_thenUdfpsRejected() {
-        mCoexCoordinator.reset();
+        when(mFaceClient.isKeyguard()).thenReturn(true);
+        when(mFaceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
+        when(mUdfpsClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
+        when(mUdfpsClient.isKeyguard()).thenReturn(true);
+        when(((Udfps) mUdfpsClient).isPointerDown()).thenReturn(true);
 
-        AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
-        when(faceClient.isKeyguard()).thenReturn(true);
-        when(faceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
+        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
+        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient);
 
-        AuthenticationClient<?> udfpsClient = mock(AuthenticationClient.class,
-                withSettings().extraInterfaces(Udfps.class));
-        when(udfpsClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
-        when(udfpsClient.isKeyguard()).thenReturn(true);
-        when(((Udfps) udfpsClient).isPointerDown()).thenReturn(true);
-
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient);
-
-        mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, faceClient,
+        mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, mFaceClient,
                 LockoutTracker.LOCKOUT_NONE, mCallback);
         verify(mCallback, never()).sendHapticFeedback();
         verify(mCallback).handleLifecycleAfterAuth();
 
         // BiometricScheduler removes the face authentication client after rejection
-        mCoexCoordinator.removeAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
+        mCoexCoordinator.removeAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
 
         // Then UDFPS rejected
         CoexCoordinator.Callback udfpsCallback = mock(CoexCoordinator.Callback.class);
-        mCoexCoordinator.onAuthenticationRejected(1 /* currentTimeMillis */, udfpsClient,
+        mCoexCoordinator.onAuthenticationRejected(1 /* currentTimeMillis */, mUdfpsClient,
                 LockoutTracker.LOCKOUT_NONE, udfpsCallback);
         verify(udfpsCallback).sendHapticFeedback();
         verify(udfpsCallback).sendAuthenticationResult(eq(false) /* addAuthTokenIfStrong */);
@@ -368,26 +400,20 @@
 
     @Test
     public void testKeyguard_udfpsRejected_thenFaceRejected_noKeyguardBypass() {
-        mCoexCoordinator.reset();
+        when(mFaceClient.isKeyguard()).thenReturn(true);
+        when(mFaceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
+        when(mFaceClient.isKeyguardBypassEnabled()).thenReturn(false); // TODO: also test "true" case
+        when(mUdfpsClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
+        when(mUdfpsClient.isKeyguard()).thenReturn(true);
+        when(((Udfps) mUdfpsClient).isPointerDown()).thenReturn(true);
 
-        AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
-        when(faceClient.isKeyguard()).thenReturn(true);
-        when(faceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
-        when(faceClient.isKeyguardBypassEnabled()).thenReturn(false); // TODO: also test "true" case
+        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
+        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient);
 
-        AuthenticationClient<?> udfpsClient = mock(AuthenticationClient.class,
-                withSettings().extraInterfaces(Udfps.class));
-        when(udfpsClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
-        when(udfpsClient.isKeyguard()).thenReturn(true);
-        when(((Udfps) udfpsClient).isPointerDown()).thenReturn(true);
-
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient);
-
-        mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, udfpsClient,
-                LockoutTracker.LOCKOUT_NONE, mCallback);
+        mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */,
+                mUdfpsClient, LockoutTracker.LOCKOUT_NONE, mCallback);
         // Auth was attempted
-        when(udfpsClient.getState())
+        when(mUdfpsClient.getState())
                 .thenReturn(AuthenticationClient.STATE_STARTED_PAUSED_ATTEMPTED);
         verify(mCallback, never()).sendHapticFeedback();
         verify(mCallback).handleLifecycleAfterAuth();
@@ -395,7 +421,7 @@
         // Then face rejected. Note that scheduler leaves UDFPS in the CoexCoordinator since
         // unlike face, its lifecycle becomes "paused" instead of "finished".
         CoexCoordinator.Callback faceCallback = mock(CoexCoordinator.Callback.class);
-        mCoexCoordinator.onAuthenticationRejected(1 /* currentTimeMillis */, faceClient,
+        mCoexCoordinator.onAuthenticationRejected(1 /* currentTimeMillis */, mFaceClient,
                 LockoutTracker.LOCKOUT_NONE, faceCallback);
         verify(faceCallback).sendHapticFeedback();
         verify(faceCallback).sendAuthenticationResult(eq(false) /* addAuthTokenIfStrong */);
@@ -404,20 +430,16 @@
 
     @Test
     public void testKeyguard_capacitiveAccepted_whenFaceScanning() {
-        mCoexCoordinator.reset();
+        when(mFaceClient.isKeyguard()).thenReturn(true);
+        when(mFaceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
+        when(mFingerprintClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
+        when(mFingerprintClient.isKeyguard()).thenReturn(true);
 
-        AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
-        when(faceClient.isKeyguard()).thenReturn(true);
-        when(faceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
+        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
+        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FP_OTHER, mFingerprintClient);
 
-        AuthenticationClient<?> fpClient = mock(AuthenticationClient.class);
-        when(fpClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
-        when(fpClient.isKeyguard()).thenReturn(true);
-
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FP_OTHER, fpClient);
-
-        mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, fpClient, mCallback);
+        mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */,
+                mFingerprintClient, mCallback);
         verify(mCallback).sendHapticFeedback();
         verify(mCallback).sendAuthenticationResult(eq(true) /* addAuthTokenIfStrong */);
         verify(mCallback).handleLifecycleAfterAuth();
@@ -425,21 +447,16 @@
 
     @Test
     public void testKeyguard_capacitiveRejected_whenFaceScanning() {
-        mCoexCoordinator.reset();
+        when(mFaceClient.isKeyguard()).thenReturn(true);
+        when(mFaceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
+        when(mFingerprintClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
+        when(mFingerprintClient.isKeyguard()).thenReturn(true);
 
-        AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
-        when(faceClient.isKeyguard()).thenReturn(true);
-        when(faceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
+        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
+        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FP_OTHER, mFingerprintClient);
 
-        AuthenticationClient<?> fpClient = mock(AuthenticationClient.class);
-        when(fpClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
-        when(fpClient.isKeyguard()).thenReturn(true);
-
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FP_OTHER, fpClient);
-
-        mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, fpClient,
-                LockoutTracker.LOCKOUT_NONE, mCallback);
+        mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */,
+                mFingerprintClient, LockoutTracker.LOCKOUT_NONE, mCallback);
         verify(mCallback).sendHapticFeedback();
         verify(mCallback).sendAuthenticationResult(eq(false) /* addAuthTokenIfStrong */);
         verify(mCallback).handleLifecycleAfterAuth();
@@ -447,14 +464,11 @@
 
     @Test
     public void testNonKeyguard_rejectAndNotLockedOut() {
-        mCoexCoordinator.reset();
+        when(mFaceClient.isKeyguard()).thenReturn(false);
+        when(mFaceClient.isBiometricPrompt()).thenReturn(true);
 
-        AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
-        when(faceClient.isKeyguard()).thenReturn(false);
-        when(faceClient.isBiometricPrompt()).thenReturn(true);
-
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
-        mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, faceClient,
+        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
+        mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, mFaceClient,
                 LockoutTracker.LOCKOUT_NONE, mCallback);
 
         verify(mCallback).sendHapticFeedback();
@@ -464,14 +478,11 @@
 
     @Test
     public void testNonKeyguard_rejectLockedOut() {
-        mCoexCoordinator.reset();
+        when(mFaceClient.isKeyguard()).thenReturn(false);
+        when(mFaceClient.isBiometricPrompt()).thenReturn(true);
 
-        AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
-        when(faceClient.isKeyguard()).thenReturn(false);
-        when(faceClient.isBiometricPrompt()).thenReturn(true);
-
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
-        mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, faceClient,
+        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
+        mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, mFaceClient,
                 LockoutTracker.LOCKOUT_TIMED, mCallback);
 
         verify(mCallback).sendHapticFeedback();
@@ -496,16 +507,13 @@
 
     @Test
     public void testBiometricPrompt_FaceError() {
-        mCoexCoordinator.reset();
+        when(mFaceClient.isBiometricPrompt()).thenReturn(true);
+        when(mFaceClient.wasAuthAttempted()).thenReturn(true);
 
-        AuthenticationClient<?> client = mock(AuthenticationClient.class);
-        when(client.isBiometricPrompt()).thenReturn(true);
-        when(client.wasAuthAttempted()).thenReturn(true);
+        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
 
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, client);
-
-        mCoexCoordinator.onAuthenticationError(client, BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
-                mErrorCallback);
+        mCoexCoordinator.onAuthenticationError(mFaceClient,
+                BiometricConstants.BIOMETRIC_ERROR_TIMEOUT, mErrorCallback);
         verify(mErrorCallback).sendHapticFeedback();
     }
 
@@ -520,18 +528,15 @@
     }
 
     private void testKeyguard_faceAuthOnly(boolean bypassEnabled) {
-        mCoexCoordinator.reset();
+        when(mFaceClient.isKeyguard()).thenReturn(true);
+        when(mFaceClient.isKeyguardBypassEnabled()).thenReturn(bypassEnabled);
+        when(mFaceClient.wasAuthAttempted()).thenReturn(true);
+        when(mFaceClient.wasUserDetected()).thenReturn(true);
 
-        AuthenticationClient<?> client = mock(AuthenticationClient.class);
-        when(client.isKeyguard()).thenReturn(true);
-        when(client.isKeyguardBypassEnabled()).thenReturn(bypassEnabled);
-        when(client.wasAuthAttempted()).thenReturn(true);
-        when(client.wasUserDetected()).thenReturn(true);
+        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
 
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, client);
-
-        mCoexCoordinator.onAuthenticationError(client, BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
-                mErrorCallback);
+        mCoexCoordinator.onAuthenticationError(mFaceClient,
+                BiometricConstants.BIOMETRIC_ERROR_TIMEOUT, mErrorCallback);
         verify(mErrorCallback).sendHapticFeedback();
     }
 
@@ -546,23 +551,17 @@
     }
 
     private void testKeyguard_coex_faceError(boolean bypassEnabled) {
-        mCoexCoordinator.reset();
+        when(mFaceClient.isKeyguard()).thenReturn(true);
+        when(mFaceClient.isKeyguardBypassEnabled()).thenReturn(bypassEnabled);
+        when(mFaceClient.wasAuthAttempted()).thenReturn(true);
+        when(mFaceClient.wasUserDetected()).thenReturn(true);
+        when(mUdfpsClient.isKeyguard()).thenReturn(true);
+        when(((Udfps) mUdfpsClient).isPointerDown()).thenReturn(false);
 
-        AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
-        when(faceClient.isKeyguard()).thenReturn(true);
-        when(faceClient.isKeyguardBypassEnabled()).thenReturn(bypassEnabled);
-        when(faceClient.wasAuthAttempted()).thenReturn(true);
-        when(faceClient.wasUserDetected()).thenReturn(true);
+        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
+        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient);
 
-        AuthenticationClient<?> udfpsClient = mock(AuthenticationClient.class,
-                withSettings().extraInterfaces(Udfps.class));
-        when(udfpsClient.isKeyguard()).thenReturn(true);
-        when(((Udfps) udfpsClient).isPointerDown()).thenReturn(false);
-
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient);
-
-        mCoexCoordinator.onAuthenticationError(faceClient,
+        mCoexCoordinator.onAuthenticationError(mFaceClient,
                 BiometricConstants.BIOMETRIC_ERROR_TIMEOUT, mErrorCallback);
 
         if (bypassEnabled) {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClientTest.java
new file mode 100644
index 0000000..76a5acc
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClientTest.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2022 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.aidl;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.face.ISession;
+import android.hardware.face.Face;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.testing.TestableContext;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.BiometricUtils;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Presubmit
+@SmallTest
+public class FaceRemovalClientTest {
+
+    private static final int USER_ID = 12;
+
+    @Rule
+    public final TestableContext mContext = new TestableContext(
+            InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private ISession mHal;
+    @Mock
+    private IBinder mToken;
+    @Mock
+    private ClientMonitorCallbackConverter mClientMonitorCallbackConverter;
+    @Mock
+    private BiometricLogger mBiometricLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
+    @Mock
+    private ClientMonitorCallback mCallback;
+    @Mock
+    private Sensor.HalSessionCallback mHalSessionCallback;
+    @Mock
+    private BiometricUtils<Face> mUtils;
+    @Mock
+    private BiometricAuthenticator.Identifier mIdentifier;
+    private Map<Integer, Long> mAuthenticatorIds = new HashMap<Integer, Long>();
+
+    @Before
+    public void setup() {
+        when(mBiometricContext.updateContext(any(), anyBoolean())).thenAnswer(
+                i -> i.getArgument(0));
+    }
+
+    @Test
+    public void testFaceRemovalClient() throws RemoteException {
+        final int authenticatorId = 1;
+        int[] authenticatorIds = new int[]{authenticatorId};
+        final FaceRemovalClient client = createClient(1, authenticatorIds);
+        when(mIdentifier.getBiometricId()).thenReturn(authenticatorId);
+        client.start(mCallback);
+        verify(mHal).removeEnrollments(authenticatorIds);
+        client.onRemoved(mIdentifier, 0 /* remaining */);
+        verify(mClientMonitorCallbackConverter).onRemoved(
+                eq(mIdentifier) /* identifier */, eq(0) /* remaining */);
+        verify(mCallback).onClientFinished(client, true);
+    }
+
+    @Test
+    public void clientSendsErrorWhenHALFailsToRemoveEnrollment() throws RemoteException {
+        final FaceRemovalClient client = createClient(1, new int[0]);
+        client.start(mCallback);
+        client.onRemoved(null, 0 /* remaining */);
+        verify(mClientMonitorCallbackConverter).onError(eq(5) /* sensorId */, anyInt(),
+                eq(BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_REMOVE), eq(0) /* vendorCode*/);
+        verify(mCallback).onClientFinished(client, false);
+    }
+
+    private FaceRemovalClient createClient(int version, int[] biometricIds) throws RemoteException {
+        when(mHal.getInterfaceVersion()).thenReturn(version);
+        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+        return new FaceRemovalClient(mContext, () -> aidl, mToken,
+                mClientMonitorCallbackConverter, biometricIds, USER_ID,
+                "own-it", mUtils /* utils */, 5 /* sensorId */, mBiometricLogger, mBiometricContext,
+                mAuthenticatorIds);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java
index 533fb2d..4f6fc3d 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java
@@ -309,7 +309,7 @@
 
     @Test
     public void testGetNonRequiredApps_managedProfile_roleHolder_works() {
-        when(mInjector.getDeviceManagerRoleHolderPackageName(any()))
+        when(mInjector.getDevicePolicyManagementRoleHolderPackageName(any()))
                 .thenReturn(ROLE_HOLDER_PACKAGE_NAME);
         setSystemAppsWithLauncher("package1", "package2", ROLE_HOLDER_PACKAGE_NAME);
 
@@ -319,7 +319,7 @@
 
     @Test
     public void testGetNonRequiredApps_managedDevice_roleHolder_works() {
-        when(mInjector.getDeviceManagerRoleHolderPackageName(any()))
+        when(mInjector.getDevicePolicyManagementRoleHolderPackageName(any()))
                 .thenReturn(ROLE_HOLDER_PACKAGE_NAME);
         setSystemAppsWithLauncher("package1", "package2", ROLE_HOLDER_PACKAGE_NAME);
 
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStrongAuthTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStrongAuthTest.java
index acb20ed..6de7fdd 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStrongAuthTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStrongAuthTest.java
@@ -229,8 +229,8 @@
     }
 
     private void verifyAlarm(long when, String tag, AlarmManager.OnAlarmListener alarm) {
-        verify(mAlarmManager).set(
-                eq(AlarmManager.ELAPSED_REALTIME),
+        verify(mAlarmManager).setExact(
+                eq(AlarmManager.ELAPSED_REALTIME_WAKEUP),
                 eq(when),
                 eq(tag),
                 eq(alarm),
diff --git a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
index eeaf781..bfdffc0 100644
--- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
@@ -39,9 +39,11 @@
 import org.junit.rules.TemporaryFolder;
 import org.junit.runner.RunWith;
 import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.BufferedWriter;
 import java.io.File;
+import java.io.FileReader;
 import java.io.FileWriter;
 import java.io.IOException;
 import java.util.Arrays;
@@ -137,13 +139,14 @@
                 new ArraySet<>(Arrays.asList("GUEST", "PROFILE")));
 
         final File folder1 = createTempSubfolder("folder1");
-        createTempFile(folder1, "permFile1.xml", contents1);
+        createTempFile(folder1, "permissionFile1.xml", contents1);
 
         final File folder2 = createTempSubfolder("folder2");
-        createTempFile(folder2, "permFile2.xml", contents2);
+        createTempFile(folder2, "permissionFile2.xml", contents2);
 
-        // Also, make a third file, but with the name folder1/permFile2.xml, to prove no conflicts.
-        createTempFile(folder1, "permFile2.xml", contents3);
+        // Also, make a third file, but with the name folder1/permissionFile2.xml, to prove no
+        // conflicts.
+        createTempFile(folder1, "permissionFile2.xml", contents3);
 
         readPermissions(folder1, /* No permission needed anyway */ 0);
         readPermissions(folder2, /* No permission needed anyway */ 0);
@@ -333,6 +336,91 @@
         assertThat(mSysConfig.getAllowedVendorApexes()).isEmpty();
     }
 
+    @Test
+    public void readApexPrivAppPermissions_addAllPermissions()
+            throws Exception {
+        final String contents =
+                "<privapp-permissions package=\"com.android.apk_in_apex\">"
+                        + "<permission name=\"android.permission.FOO\"/>"
+                        + "<deny-permission name=\"android.permission.BAR\"/>"
+                        + "</privapp-permissions>";
+        File apexDir = createTempSubfolder("apex");
+        File permissionFile = createTempFile(
+                createTempSubfolder("apex/com.android.my_module/etc/permissions"),
+                    "permissions.xml", contents);
+        XmlPullParser parser = readXmlUntilStartTag(permissionFile);
+
+        mSysConfig.readApexPrivAppPermissions(parser, permissionFile, apexDir.toPath());
+
+        assertThat(mSysConfig.getApexPrivAppPermissions("com.android.my_module",
+                "com.android.apk_in_apex"))
+            .containsExactly("android.permission.FOO");
+        assertThat(mSysConfig.getApexPrivAppDenyPermissions("com.android.my_module",
+                "com.android.apk_in_apex"))
+            .containsExactly("android.permission.BAR");
+    }
+
+    @Test
+    public void pruneVendorApexPrivappAllowlists_removeVendor()
+            throws Exception {
+        File apexDir = createTempSubfolder("apex");
+
+        // Read non-vendor apex permission allowlists
+        final String allowlistNonVendorContents =
+                "<privapp-permissions package=\"com.android.apk_in_non_vendor_apex\">"
+                        + "<permission name=\"android.permission.FOO\"/>"
+                        + "<deny-permission name=\"android.permission.BAR\"/>"
+                        + "</privapp-permissions>";
+        File nonVendorPermDir =
+                createTempSubfolder("apex/com.android.non_vendor/etc/permissions");
+        File nonVendorPermissionFile =
+                createTempFile(nonVendorPermDir, "permissions.xml", allowlistNonVendorContents);
+        XmlPullParser nonVendorParser = readXmlUntilStartTag(nonVendorPermissionFile);
+        mSysConfig.readApexPrivAppPermissions(nonVendorParser, nonVendorPermissionFile,
+                apexDir.toPath());
+
+        // Read vendor apex permission allowlists
+        final String allowlistVendorContents =
+                "<privapp-permissions package=\"com.android.apk_in_vendor_apex\">"
+                        + "<permission name=\"android.permission.BAZ\"/>"
+                        + "<deny-permission name=\"android.permission.BAT\"/>"
+                        + "</privapp-permissions>";
+        File vendorPermissionFile =
+                createTempFile(createTempSubfolder("apex/com.android.vendor/etc/permissions"),
+                        "permissions.xml", allowlistNonVendorContents);
+        XmlPullParser vendorParser = readXmlUntilStartTag(vendorPermissionFile);
+        mSysConfig.readApexPrivAppPermissions(vendorParser, vendorPermissionFile,
+                apexDir.toPath());
+
+        // Read allowed vendor apex list
+        final String allowedVendorContents =
+                "<config>\n"
+                        + "    <allowed-vendor-apex package=\"com.android.vendor\" "
+                        + "installerPackage=\"com.installer\" />\n"
+                        + "</config>";
+        final File allowedVendorFolder = createTempSubfolder("folder");
+        createTempFile(allowedVendorFolder, "vendor-apex-allowlist.xml", allowedVendorContents);
+        readPermissions(allowedVendorFolder, /* Grant all permission flags */ ~0);
+
+        // Finally, prune non-vendor allowlists.
+        // There is no guarantee in which order the above reads will be done, however pruning
+        // will always happen last.
+        mSysConfig.pruneVendorApexPrivappAllowlists();
+
+        assertThat(mSysConfig.getApexPrivAppPermissions("com.android.non_vendor",
+                "com.android.apk_in_non_vendor_apex"))
+            .containsExactly("android.permission.FOO");
+        assertThat(mSysConfig.getApexPrivAppDenyPermissions("com.android.non_vendor",
+                "com.android.apk_in_non_vendor_apex"))
+            .containsExactly("android.permission.BAR");
+        assertThat(mSysConfig.getApexPrivAppPermissions("com.android.vendor",
+                "com.android.apk_in_vendor_apex"))
+            .isNull();
+        assertThat(mSysConfig.getApexPrivAppDenyPermissions("com.android.vendor",
+                "com.android.apk_in_vendor_apex"))
+            .isNull();
+    }
+
     /**
      * Tests that readPermissions works correctly for a library with on-bootclasspath-before
      * and on-bootclasspath-since.
@@ -492,6 +580,25 @@
     }
 
     /**
+     * Create an {@link XmlPullParser} for {@param permissionFile} and begin parsing it until
+     * reaching the root tag.
+     */
+    private XmlPullParser readXmlUntilStartTag(File permissionFile)
+            throws IOException, XmlPullParserException {
+        FileReader permReader = new FileReader(permissionFile);
+        XmlPullParser parser = Xml.newPullParser();
+        parser.setInput(permReader);
+        int type;
+        do {
+            type = parser.next();
+        } while (type != parser.START_TAG && type != parser.END_DOCUMENT);
+        if (type != parser.START_TAG) {
+            throw new XmlPullParserException("No start tag found");
+        }
+        return parser;
+    }
+
+    /**
      * Creates folderName/fileName in the mTemporaryFolder and fills it with the contents.
      *
      * @param folderName subdirectory of mTemporaryFolder to put the file, creating if needed
@@ -500,7 +607,7 @@
     private File createTempSubfolder(String folderName)
             throws IOException {
         File folder = new File(mTemporaryFolder.getRoot(), folderName);
-        folder.mkdir();
+        folder.mkdirs();
         return folder;
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
index 55d6df9..0c8394e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
@@ -352,6 +352,6 @@
         spyOn(callback);
         mInterceptor.onActivityLaunched(null, null);
 
-        verify(callback, times(1)).onActivityLaunched(any(), any());
+        verify(callback, times(1)).onActivityLaunched(any(), any(), any());
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 16c5bfe..6fe2d33 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -1085,7 +1085,7 @@
         starter.setActivityOptions(options.toBundle())
                 .setReason("testWindowingModeOptionsLaunchAdjacent")
                 .setOutActivity(outActivity).execute();
-        assertThat(outActivity[0].inSplitScreenWindowingMode()).isFalse();
+        assertThat(outActivity[0].inMultiWindowMode()).isFalse();
 
         // Move activity to split-screen-primary stack and make sure it has the focus.
         TestSplitOrganizer splitOrg = new TestSplitOrganizer(mAtm, top.getDisplayContent());
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index c083870..925f4f5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1914,7 +1914,7 @@
         final WindowState nextImeTargetApp = createWindow(null /* parent */,
                 TYPE_BASE_APPLICATION, "nextImeTargetApp");
         spyOn(child1);
-        doReturn(true).when(child1).inSplitScreenWindowingMode();
+        doReturn(false).when(mDisplayContent).shouldImeAttachedToApp();
         mDisplayContent.setImeLayeringTarget(child1);
 
         spyOn(nextImeTargetApp);
@@ -2418,10 +2418,10 @@
     public void testKeepClearAreasMultipleWindows() {
         final WindowState w1 = createWindow(null, TYPE_NAVIGATION_BAR, mDisplayContent, "w1");
         final Rect rect1 = new Rect(0, 0, 10, 10);
-        w1.setKeepClearAreas(Arrays.asList(rect1));
+        w1.setKeepClearAreas(Arrays.asList(rect1), Collections.emptyList());
         final WindowState w2 = createWindow(null, TYPE_NOTIFICATION_SHADE, mDisplayContent, "w2");
         final Rect rect2 = new Rect(10, 10, 20, 20);
-        w2.setKeepClearAreas(Arrays.asList(rect2));
+        w2.setKeepClearAreas(Arrays.asList(rect2), Collections.emptyList());
 
         // No keep clear areas on display, because the windows are not visible
         assertEquals(Arrays.asList(), mDisplayContent.getKeepClearAreas());
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index 3c3351c0..021568d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -451,7 +451,7 @@
         final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation();
         final int topOrientation = DisplayContentTests.getRotatedOrientation(mDefaultDisplay);
         assertFalse(displayRotation.updateOrientation(topOrientation, false /* forceUpdate */));
-        assertEquals(recents.mOrientation, displayRotation.getLastOrientation());
+        assertEquals(ActivityInfo.SCREEN_ORIENTATION_UNSET, displayRotation.getLastOrientation());
         final int prevRotation = mDisplayContent.getRotation();
         mWm.cleanupRecentsAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 501f0c4..8474c38 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -20,6 +20,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
@@ -691,7 +692,7 @@
                 anyInt() /* orientation */, anyInt() /* lastRotation */);
         // Rotation update is skipped while the recents animation is running.
         assertFalse(mDisplayContent.updateRotationUnchecked());
-        assertEquals(SCREEN_ORIENTATION_NOSENSOR, displayRotation.getLastOrientation());
+        assertEquals(SCREEN_ORIENTATION_UNSET, displayRotation.getLastOrientation());
         // Return to the app without fixed orientation from recents.
         app.moveFocusableActivityToTop("test");
         player.finish();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java
index 338555e..7d2e9bf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java
@@ -30,6 +30,7 @@
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
 
 import static org.junit.Assert.assertEquals;
 
@@ -330,6 +331,23 @@
     }
 
     @Test
+    public void layoutExtendedToDisplayCutout() {
+        addDisplayCutout();
+        final int height = DISPLAY_HEIGHT / 2;
+        mRequestedHeight = UNSPECIFIED_LENGTH;
+        mAttrs.height = height;
+        mAttrs.gravity = Gravity.TOP;
+        mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+        mAttrs.setFitInsetsTypes(0);
+        mAttrs.privateFlags |= PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
+        computeFrames();
+
+        assertRect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayFrame);
+        assertRect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, mParentFrame);
+        assertRect(0, 0, DISPLAY_WIDTH, height + DISPLAY_CUTOUT_HEIGHT, mFrame);
+    }
+
+    @Test
     public void layoutInDisplayCutoutModeDefaultWithInvisibleSystemBars() {
         addDisplayCutout();
         mState.getSource(ITYPE_STATUS_BAR).setVisible(false);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 4d5fb6d..25d7334 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -30,6 +30,7 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
 import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED;
+import static android.view.InsetsState.ITYPE_LOCAL_NAVIGATION_BAR_1;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
@@ -610,6 +611,50 @@
         assertEquals(ACTIVITY_TYPE_UNDEFINED, info1.topActivityType);
     }
 
+    @Test
+    public void testAddRectInsetsProvider() {
+        final Task rootTask = createTask(mDisplayContent);
+
+        final Task navigationBarInsetsReceiverTask = createTaskInRootTask(rootTask, 0);
+        navigationBarInsetsReceiverTask.getConfiguration().windowConfiguration.setBounds(new Rect(
+                0, 200, 1080, 700));
+
+        final Rect navigationBarInsetsProviderRect = new Rect(0, 0, 1080, 200);
+
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
+        wct.addRectInsetsProvider(navigationBarInsetsReceiverTask.mRemoteToken
+                        .toWindowContainerToken(), navigationBarInsetsProviderRect,
+                new int[]{ITYPE_LOCAL_NAVIGATION_BAR_1});
+        mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
+
+        assertThat(navigationBarInsetsReceiverTask.mLocalInsetsSourceProviders
+                .valueAt(0).getSource().getType()).isEqualTo(ITYPE_LOCAL_NAVIGATION_BAR_1);
+    }
+
+    @Test
+    public void testRemoveInsetsProvider() {
+        final Task rootTask = createTask(mDisplayContent);
+
+        final Task navigationBarInsetsReceiverTask = createTaskInRootTask(rootTask, 0);
+        navigationBarInsetsReceiverTask.getConfiguration().windowConfiguration.setBounds(new Rect(
+                0, 200, 1080, 700));
+
+        final Rect navigationBarInsetsProviderRect = new Rect(0, 0, 1080, 200);
+
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
+        wct.addRectInsetsProvider(navigationBarInsetsReceiverTask.mRemoteToken
+                        .toWindowContainerToken(), navigationBarInsetsProviderRect,
+                new int[]{ITYPE_LOCAL_NAVIGATION_BAR_1});
+        mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
+
+        final WindowContainerTransaction wct2 = new WindowContainerTransaction();
+        wct2.removeInsetsProvider(navigationBarInsetsReceiverTask.mRemoteToken
+                .toWindowContainerToken(), new int[]{ITYPE_LOCAL_NAVIGATION_BAR_1});
+        mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct2);
+
+        assertThat(navigationBarInsetsReceiverTask.mLocalInsetsSourceProviders.size()).isEqualTo(0);
+    }
+
     @UseTestDisplay
     @Test
     public void testTaskInfoCallback() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index ef600f0..a554fab 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -97,7 +97,9 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
 
@@ -992,14 +994,18 @@
         final Rect keepClearArea1 = new Rect(0, 0, 10, 10);
         final Rect keepClearArea2 = new Rect(5, 10, 15, 20);
         final List<Rect> keepClearAreas = Arrays.asList(keepClearArea1, keepClearArea2);
-        window.setKeepClearAreas(keepClearAreas);
+        window.setKeepClearAreas(keepClearAreas, Collections.emptyList());
 
         // Test that the keep-clear rects are stored and returned
-        assertEquals(new ArraySet(keepClearAreas), new ArraySet(window.getKeepClearAreas()));
+        final List<Rect> windowKeepClearAreas = new ArrayList();
+        window.getKeepClearAreas(windowKeepClearAreas, new ArrayList());
+        assertEquals(new ArraySet(keepClearAreas), new ArraySet(windowKeepClearAreas));
 
         // Test that keep-clear rects are overwritten
-        window.setKeepClearAreas(Arrays.asList());
-        assertEquals(0, window.getKeepClearAreas().size());
+        window.setKeepClearAreas(Collections.emptyList(), Collections.emptyList());
+        windowKeepClearAreas.clear();
+        window.getKeepClearAreas(windowKeepClearAreas, new ArrayList());
+        assertEquals(0, windowKeepClearAreas.size());
 
         // Move the window position
         final SurfaceControl.Transaction t = spy(StubTransaction.class);
@@ -1010,13 +1016,60 @@
         assertEquals(new Point(frame.left, frame.top), window.mLastSurfacePosition);
 
         // Test that the returned keep-clear rects are translated to display space
-        window.setKeepClearAreas(keepClearAreas);
+        window.setKeepClearAreas(keepClearAreas, Collections.emptyList());
         Rect expectedArea1 = new Rect(keepClearArea1);
         expectedArea1.offset(frame.left, frame.top);
         Rect expectedArea2 = new Rect(keepClearArea2);
         expectedArea2.offset(frame.left, frame.top);
 
+        windowKeepClearAreas.clear();
+        window.getKeepClearAreas(windowKeepClearAreas, new ArrayList());
         assertEquals(new ArraySet(Arrays.asList(expectedArea1, expectedArea2)),
-                     new ArraySet(window.getKeepClearAreas()));
+                     new ArraySet(windowKeepClearAreas));
+    }
+
+    @Test
+    public void testUnrestrictedKeepClearAreas() {
+        final WindowState window = createWindow(null, TYPE_APPLICATION, "window");
+        makeWindowVisible(window);
+
+        final Rect keepClearArea1 = new Rect(0, 0, 10, 10);
+        final Rect keepClearArea2 = new Rect(5, 10, 15, 20);
+        final List<Rect> keepClearAreas = Arrays.asList(keepClearArea1, keepClearArea2);
+        window.setKeepClearAreas(Collections.emptyList(), keepClearAreas);
+
+        // Test that the keep-clear rects are stored and returned
+        final List<Rect> restrictedKeepClearAreas = new ArrayList();
+        final List<Rect> unrestrictedKeepClearAreas = new ArrayList();
+        window.getKeepClearAreas(restrictedKeepClearAreas, unrestrictedKeepClearAreas);
+        assertEquals(Collections.emptySet(), new ArraySet(restrictedKeepClearAreas));
+        assertEquals(new ArraySet(keepClearAreas), new ArraySet(unrestrictedKeepClearAreas));
+
+        // Test that keep-clear rects are overwritten
+        window.setKeepClearAreas(Collections.emptyList(), Collections.emptyList());
+        unrestrictedKeepClearAreas.clear();
+        window.getKeepClearAreas(unrestrictedKeepClearAreas, new ArrayList());
+        assertEquals(0, unrestrictedKeepClearAreas.size());
+
+        // Move the window position
+        final SurfaceControl.Transaction t = spy(StubTransaction.class);
+        window.mSurfaceControl = mock(SurfaceControl.class);
+        final Rect frame = window.getFrame();
+        frame.set(10, 20, 60, 80);
+        window.updateSurfacePosition(t);
+        assertEquals(new Point(frame.left, frame.top), window.mLastSurfacePosition);
+
+        // Test that the returned keep-clear rects are translated to display space
+        window.setKeepClearAreas(Collections.emptyList(), keepClearAreas);
+        Rect expectedArea1 = new Rect(keepClearArea1);
+        expectedArea1.offset(frame.left, frame.top);
+        Rect expectedArea2 = new Rect(keepClearArea2);
+        expectedArea2.offset(frame.left, frame.top);
+
+        unrestrictedKeepClearAreas.clear();
+        window.getKeepClearAreas(restrictedKeepClearAreas, unrestrictedKeepClearAreas);
+        assertEquals(Collections.emptySet(), new ArraySet(restrictedKeepClearAreas));
+        assertEquals(new ArraySet(Arrays.asList(expectedArea1, expectedArea2)),
+                     new ArraySet(unrestrictedKeepClearAreas));
     }
 }
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index f43e5aa..df114db 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -1292,31 +1292,22 @@
     }
 
     /**
-     * Returns a list of {@link PhoneAccountHandle}s for self-managed {@link ConnectionService}s.
+     * Returns a list of {@link PhoneAccountHandle}s for all self-managed
+     * {@link ConnectionService}s owned by the calling {@link UserHandle}.
      * <p>
      * Self-Managed {@link ConnectionService}s have a {@link PhoneAccount} with
      * {@link PhoneAccount#CAPABILITY_SELF_MANAGED}.
      * <p>
      * Requires permission {@link android.Manifest.permission#READ_PHONE_STATE}, or that the caller
-     * is the default dialer app to get all phone account handles.
-     * <P>
-     * If the caller doesn't meet any of the above requirements and has {@link
-     * android.Manifest.permission#MANAGE_OWN_CALLS}, the caller can get only the phone account
-     * handles they have registered.
+     * is the default dialer app.
      * <p>
-     * A {@link SecurityException} will be thrown if the caller is not the default dialer
-     * or the caller does not have at least one of the following permissions:
-     * {@link android.Manifest.permission#READ_PHONE_STATE} permission,
-     * {@link android.Manifest.permission#MANAGE_OWN_CALLS} permission
+     * A {@link SecurityException} will be thrown if a called is not the default dialer, or lacks
+     * the {@link android.Manifest.permission#READ_PHONE_STATE} permission.
      *
      * @return A list of {@code PhoneAccountHandle} objects.
      */
-    @RequiresPermission(anyOf = {
-            READ_PRIVILEGED_PHONE_STATE,
-            android.Manifest.permission.READ_PHONE_STATE,
-            android.Manifest.permission.MANAGE_OWN_CALLS
-    })
-    public List<PhoneAccountHandle> getSelfManagedPhoneAccounts() {
+    @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+    public @NonNull List<PhoneAccountHandle> getSelfManagedPhoneAccounts() {
         ITelecomService service = getTelecomService();
         if (service != null) {
             try {
@@ -1330,6 +1321,34 @@
     }
 
     /**
+     * Returns a list of {@link PhoneAccountHandle}s owned by the calling self-managed
+     * {@link ConnectionService}.
+     * <p>
+     * Self-Managed {@link ConnectionService}s have a {@link PhoneAccount} with
+     * {@link PhoneAccount#CAPABILITY_SELF_MANAGED}.
+     * <p>
+     * Requires permission {@link android.Manifest.permission#MANAGE_OWN_CALLS}
+     * <p>
+     * A {@link SecurityException} will be thrown if a caller lacks the
+     * {@link android.Manifest.permission#MANAGE_OWN_CALLS} permission.
+     *
+     * @return A list of {@code PhoneAccountHandle} objects.
+     */
+    @RequiresPermission(Manifest.permission.MANAGE_OWN_CALLS)
+    public @NonNull List<PhoneAccountHandle> getOwnSelfManagedPhoneAccounts() {
+        ITelecomService service = getTelecomService();
+        if (service != null) {
+            try {
+                return service.getOwnSelfManagedPhoneAccounts(mContext.getOpPackageName(),
+                        mContext.getAttributionTag());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        throw new IllegalStateException("Telecom is not available");
+    }
+
+    /**
      * Returns a list of {@link PhoneAccountHandle}s including those which have not been enabled
      * by the user.
      *
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index b9936ce..9999c89 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -66,6 +66,12 @@
             String callingFeatureId);
 
     /**
+     * @see TelecomServiceImpl#getOwnSelfManagedPhoneAccounts
+     */
+    List<PhoneAccountHandle> getOwnSelfManagedPhoneAccounts(String callingPackage,
+            String callingFeatureId);
+
+    /**
      * @see TelecomManager#getPhoneAccountsSupportingScheme
      */
     List<PhoneAccountHandle> getPhoneAccountsSupportingScheme(in String uriScheme,
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index 8e10f6b..fc94ebf 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -369,6 +369,10 @@
     public @interface AuthType {}
 
     // Possible values for protocol which is defined in TS 27.007 section 10.1.1.
+    /** Unknown protocol.
+     * @hide
+     */
+    public static final int PROTOCOL_UNKNOWN = -1;
     /** Internet protocol. */
     public static final int PROTOCOL_IP = 0;
     /** Internet protocol, version 6. */
diff --git a/test-runner/src/android/test/IsolatedContext.java b/test-runner/src/android/test/IsolatedContext.java
index dd4a9a3..d5f92a3 100644
--- a/test-runner/src/android/test/IsolatedContext.java
+++ b/test-runner/src/android/test/IsolatedContext.java
@@ -17,6 +17,7 @@
 package android.test;
 
 import android.accounts.AccountManager;
+import android.content.AttributionSource;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -26,6 +27,7 @@
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
 import android.net.Uri;
+import android.os.Process;
 import android.test.mock.MockAccountManager;
 
 import java.io.File;
@@ -64,6 +66,15 @@
     }
 
     @Override
+    public AttributionSource getAttributionSource() {
+        AttributionSource attributionSource = super.getAttributionSource();
+        if (attributionSource == null) {
+            return new AttributionSource.Builder(Process.myUid()).build();
+        }
+        return attributionSource;
+    }
+
+    @Override
     public ContentResolver getContentResolver() {
         // We need to return the real resolver so that MailEngine.makeRight can get to the
         // subscribed feeds provider. TODO: mock out subscribed feeds too.
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
index c89e6a4..48b8779 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
@@ -97,6 +97,7 @@
             transitions {
                 taplInstrumentation.launchedAppState.quickSwitchToPreviousApp()
                 wmHelper.waitForFullScreenApp(testApp1.component)
+                wmHelper.waitSnapshotGone()
                 wmHelper.waitForAppTransitionIdle()
                 wmHelper.waitForNavBarStatusBarVisible()
             }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
index 5d172e2..d6c8f46 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
@@ -51,7 +51,7 @@
 /**
  * Test quick switching back to previous app from last opened app
  *
- * To run this test: `atest FlickerTests:QuickSwitchBetweenTwoAppsBackTest`
+ * To run this test: `atest FlickerTests:QuickSwitchBetweenTwoAppsForwardTest`
  *
  * Actions:
  *     Launch an app [testApp1]
@@ -101,6 +101,7 @@
                 taplInstrumentation.launchedAppState.quickSwitchToPreviousAppSwipeLeft()
 
                 wmHelper.waitForFullScreenApp(testApp2.component)
+                wmHelper.waitSnapshotGone()
                 wmHelper.waitForAppTransitionIdle()
                 wmHelper.waitForNavBarStatusBarVisible()
             }
diff --git a/tests/TrustTests/Android.bp b/tests/TrustTests/Android.bp
new file mode 100644
index 0000000..c9c6c5c
--- /dev/null
+++ b/tests/TrustTests/Android.bp
@@ -0,0 +1,39 @@
+// Copyright (C) 2022 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "TrustTests",
+    srcs: [
+        "src/**/*.kt",
+    ],
+    static_libs: [
+        "androidx.test.rules",
+        "androidx.test.ext.junit",
+        "androidx.test.uiautomator",
+        "truth-prebuilt",
+    ],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+    ],
+    test_suites: [
+        "device-tests",
+    ],
+    platform_apis: true,
+    certificate: "platform",
+}
diff --git a/tests/TrustTests/AndroidManifest.xml b/tests/TrustTests/AndroidManifest.xml
new file mode 100644
index 0000000..c94152d
--- /dev/null
+++ b/tests/TrustTests/AndroidManifest.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.trust.test"
+          android:targetSandboxVersion="2">
+
+    <uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" />
+    <uses-permission android:name="android.permission.BIND_DEVICE_ADMIN" />
+    <uses-permission android:name="android.permission.CONTROL_KEYGUARD" />
+    <uses-permission android:name="android.permission.DEVICE_POWER" />
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+    <uses-permission android:name="android.permission.PROVIDE_TRUST_AGENT" />
+    <uses-permission android:name="android.permission.TRUST_LISTENER" />
+
+    <application>
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name="android.trust.TrustTestActivity"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+
+        <service
+            android:name=".UserUnlockRequestTrustAgent"
+            android:exported="true"
+            android:label="Test Agent"
+            android:permission="android.permission.BIND_TRUST_AGENT">
+            <intent-filter>
+                <action android:name="android.service.trust.TrustAgentService" />
+            </intent-filter>
+        </service>
+
+        <service
+            android:name=".LockUserTrustAgent"
+            android:exported="true"
+            android:label="Test Agent"
+            android:permission="android.permission.BIND_TRUST_AGENT">
+            <intent-filter>
+                <action android:name="android.service.trust.TrustAgentService" />
+            </intent-filter>
+        </service>
+
+        <service
+            android:name=".GrantAndRevokeTrustAgent"
+            android:exported="true"
+            android:label="Test Agent"
+            android:permission="android.permission.BIND_TRUST_AGENT">
+            <intent-filter>
+                <action android:name="android.service.trust.TrustAgentService" />
+            </intent-filter>
+        </service>
+    </application>
+
+    <!--  self-instrumenting test package. -->
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.trust.test">
+    </instrumentation>
+</manifest>
diff --git a/tests/TrustTests/AndroidTest.xml b/tests/TrustTests/AndroidTest.xml
new file mode 100644
index 0000000..61b711e
--- /dev/null
+++ b/tests/TrustTests/AndroidTest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+<configuration description="TrustTests configuration">
+    <option name="test-tag" value="TrustTests" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="TrustTests.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.trust.test" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="hidden-api-checks" value="false" />
+    </test>
+</configuration>
diff --git a/tests/TrustTests/OWNERS b/tests/TrustTests/OWNERS
new file mode 100644
index 0000000..e2c6ce1
--- /dev/null
+++ b/tests/TrustTests/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/service/trust/OWNERS
diff --git a/tests/TrustTests/README.md b/tests/TrustTests/README.md
new file mode 100644
index 0000000..3427e30
--- /dev/null
+++ b/tests/TrustTests/README.md
@@ -0,0 +1,40 @@
+# TrustTests framework tests
+
+These tests test the "trust" part of the platform primarily implemented via TrustManagerService in
+the system server and TrustAgentService in system apps.
+
+Tests are separated into separate files based on major groupings. When creating new tests, find a
+_closely_ matching existing test file or create a new test file. Prefer many test files over large
+test files.
+
+Each test file has its own trust agent. To create a new trust agent:
+
+1. Create a new class extending from `BaseTrustAgentService` class in your test file
+2. Add a new `<service>` stanza to `AndroidManifest.xml` in this directory for the new agent
+   following the pattern fo the existing agents.
+
+To run:
+
+```atest TrustTests```
+
+## Testing approach:
+
+1. Test the agent service as a black box; avoid inspecting internal state of the service or
+   modifying the system code outside of this directory.
+2. The primary interface to the system is through these three points:
+    1. `TrustAgentService`, your agent created by the `TrustAgentRule` and accessible via
+       the `agent` property of the rule.
+        1. Call command methods (e.g. `grantTrust`) directly on the agent
+        2. Listen to events (e.g. `onUserRequestedUnlock`) by implementing the method in
+           your test's agent class and tracking invocations. See `UserUnlockRequestTest` for an
+           example.
+    2. `TrustManager` which is the interface the rest of the system (e.g. SystemUI) has to the
+       service.
+        1. Through this API, simulate system events that the service cares about
+           (e.g. `reportUnlockAttempt`).
+    3. `TrustListener` which is the interface the rest of the system (e.g. SystemUI) uses to receive
+       events from the service.
+        1. Through this, verify behavior that affects the rest of the system. For example,
+           see `LockStateTrackingRule`.
+3. To re-use code between tests, prefer creating new rules alongside the existing rules or adding
+   functionality to a _closely_ matching existing rule.
diff --git a/tests/TrustTests/src/android/trust/BaseTrustAgentService.kt b/tests/TrustTests/src/android/trust/BaseTrustAgentService.kt
new file mode 100644
index 0000000..493f3bd
--- /dev/null
+++ b/tests/TrustTests/src/android/trust/BaseTrustAgentService.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.trust
+
+import android.service.trust.TrustAgentService
+import android.util.Log
+import kotlin.reflect.KClass
+
+/**
+ * Base class for test trust agents.
+ */
+abstract class BaseTrustAgentService : TrustAgentService() {
+
+    override fun onCreate() {
+        super.onCreate()
+        Log.d(TAG, "${this::class.simpleName} created")
+        instances[this::class] = this
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+        instances.remove(this::class)
+    }
+
+    companion object {
+        private val instances =
+            mutableMapOf<KClass<out BaseTrustAgentService>, BaseTrustAgentService>()
+        private const val TAG = "BaseTrustAgentService"
+
+        fun instance(serviceClass: KClass<out BaseTrustAgentService>): BaseTrustAgentService? {
+            return instances[serviceClass]!!
+        }
+    }
+}
diff --git a/tests/TrustTests/src/android/trust/TrustTestActivity.kt b/tests/TrustTests/src/android/trust/TrustTestActivity.kt
new file mode 100644
index 0000000..6c56fea
--- /dev/null
+++ b/tests/TrustTests/src/android/trust/TrustTestActivity.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.trust
+
+import android.app.Activity
+import android.os.Bundle
+
+/**
+ * Activity for testing Trust.
+ */
+class TrustTestActivity : Activity() {
+
+    public override fun onCreate(icicle: Bundle?) {
+        super.onCreate(icicle)
+        setTurnScreenOn(true)
+    }
+}
diff --git a/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt b/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt
new file mode 100644
index 0000000..790afd3
--- /dev/null
+++ b/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.trust.test
+
+import android.trust.BaseTrustAgentService
+import android.trust.TrustTestActivity
+import android.trust.test.lib.LockStateTrackingRule
+import android.trust.test.lib.ScreenLockRule
+import android.trust.test.lib.TrustAgentRule
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import androidx.test.uiautomator.UiDevice
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.RuleChain
+import org.junit.runner.RunWith
+
+/**
+ * Test for testing revokeTrust & grantTrust for non-renewable trust.
+ *
+ * atest TrustTests:GrantAndRevokeTrustTest
+ */
+@RunWith(AndroidJUnit4::class)
+class GrantAndRevokeTrustTest {
+    private val uiDevice = UiDevice.getInstance(getInstrumentation())
+    private val activityScenarioRule = ActivityScenarioRule(TrustTestActivity::class.java)
+    private val lockStateTrackingRule = LockStateTrackingRule()
+    private val trustAgentRule = TrustAgentRule<GrantAndRevokeTrustAgent>()
+
+    @get:Rule
+    val rule: RuleChain = RuleChain
+        .outerRule(activityScenarioRule)
+        .around(ScreenLockRule())
+        .around(lockStateTrackingRule)
+        .around(trustAgentRule)
+
+    @Before
+    fun manageTrust() {
+        trustAgentRule.agent.setManagingTrust(true)
+    }
+
+    // This test serves a baseline for Grant tests, verifying that the default behavior of the
+    // device is to lock when put to sleep
+    @Test
+    fun sleepingDeviceWithoutGrantLocksDevice() {
+        uiDevice.sleep()
+        await()
+
+        lockStateTrackingRule.assertLocked()
+    }
+
+    @Test
+    fun grantKeepsDeviceUnlocked() {
+        trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 10000, 0)
+        uiDevice.sleep()
+        await()
+
+        lockStateTrackingRule.assertUnlocked()
+    }
+
+    @Test
+    fun grantKeepsDeviceUnlocked_untilRevoked() {
+        trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 0, 0)
+        await()
+        uiDevice.sleep()
+        trustAgentRule.agent.revokeTrust()
+        await()
+
+        lockStateTrackingRule.assertLocked()
+    }
+
+    companion object {
+        private const val TAG = "GrantAndRevokeTrustTest"
+        private const val GRANT_MESSAGE = "granted by test"
+        private fun await() = Thread.sleep(250)
+    }
+}
+
+class GrantAndRevokeTrustAgent : BaseTrustAgentService()
diff --git a/tests/TrustTests/src/android/trust/test/LockUserTest.kt b/tests/TrustTests/src/android/trust/test/LockUserTest.kt
new file mode 100644
index 0000000..83fc28f
--- /dev/null
+++ b/tests/TrustTests/src/android/trust/test/LockUserTest.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.trust.test
+
+import android.trust.BaseTrustAgentService
+import android.trust.TrustTestActivity
+import android.trust.test.lib.LockStateTrackingRule
+import android.trust.test.lib.ScreenLockRule
+import android.trust.test.lib.TrustAgentRule
+import android.util.Log
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.RuleChain
+import org.junit.runner.RunWith
+
+/**
+ * Test for testing lockUser.
+ *
+ * atest TrustTests:LockUserTest
+ */
+@RunWith(AndroidJUnit4::class)
+class LockUserTest {
+    private val activityScenarioRule = ActivityScenarioRule(TrustTestActivity::class.java)
+    private val lockStateTrackingRule = LockStateTrackingRule()
+    private val trustAgentRule = TrustAgentRule<LockUserTrustAgent>()
+
+    @get:Rule
+    val rule: RuleChain = RuleChain
+        .outerRule(activityScenarioRule)
+        .around(ScreenLockRule())
+        .around(lockStateTrackingRule)
+        .around(trustAgentRule)
+
+    @Ignore("Causes issues with subsequent tests") // TODO: Enable test
+    @Test
+    fun lockUser_locksTheDevice() {
+        Log.i(TAG, "Locking user")
+        trustAgentRule.agent.lockUser()
+        await()
+
+        assertThat(lockStateTrackingRule.lockState.locked).isTrue()
+    }
+
+    companion object {
+        private const val TAG = "LockUserTest"
+        private fun await() = Thread.sleep(250)
+    }
+}
+
+class LockUserTrustAgent : BaseTrustAgentService()
diff --git a/tests/TrustTests/src/android/trust/test/UserUnlockRequestTest.kt b/tests/TrustTests/src/android/trust/test/UserUnlockRequestTest.kt
new file mode 100644
index 0000000..f8783fb
--- /dev/null
+++ b/tests/TrustTests/src/android/trust/test/UserUnlockRequestTest.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.trust.test
+
+import android.app.trust.TrustManager
+import android.content.Context
+import android.trust.BaseTrustAgentService
+import android.trust.TrustTestActivity
+import android.trust.test.lib.ScreenLockRule
+import android.trust.test.lib.TrustAgentRule
+import android.util.Log
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.RuleChain
+import org.junit.runner.RunWith
+
+/**
+ * Test for testing the user unlock trigger.
+ *
+ * atest TrustTests:UserUnlockRequestTest
+ */
+@RunWith(AndroidJUnit4::class)
+class UserUnlockRequestTest {
+    private val context: Context = getApplicationContext()
+    private val trustManager = context.getSystemService(TrustManager::class.java) as TrustManager
+    private val userId = context.userId
+    private val activityScenarioRule = ActivityScenarioRule(TrustTestActivity::class.java)
+    private val trustAgentRule = TrustAgentRule<UserUnlockRequestTrustAgent>()
+
+    @get:Rule
+    val rule: RuleChain = RuleChain
+        .outerRule(activityScenarioRule)
+        .around(ScreenLockRule())
+        .around(trustAgentRule)
+
+    @Test
+    fun reportUserRequestedUnlock_propagatesToAgent() {
+        val oldCount = trustAgentRule.agent.onUserRequestedUnlockCallCount
+        trustManager.reportUserRequestedUnlock(userId)
+        await()
+
+        assertThat(trustAgentRule.agent.onUserRequestedUnlockCallCount)
+            .isEqualTo(oldCount + 1)
+    }
+
+    companion object {
+        private const val TAG = "UserUnlockRequestTest"
+        private fun await() = Thread.sleep(250)
+    }
+}
+
+class UserUnlockRequestTrustAgent : BaseTrustAgentService() {
+    var onUserRequestedUnlockCallCount: Long = 0
+        private set
+
+    override fun onUserRequestedUnlock() {
+        Log.i(TAG, "onUserRequestedUnlock")
+        onUserRequestedUnlockCallCount++
+    }
+
+    companion object {
+        private const val TAG = "UserUnlockRequestTrustAgent"
+    }
+}
diff --git a/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt b/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt
new file mode 100644
index 0000000..0023af8
--- /dev/null
+++ b/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.trust.test.lib
+
+import android.app.trust.TrustManager
+import android.app.trust.TrustManager.TrustListener
+import android.content.Context
+import android.util.Log
+import android.view.WindowManagerGlobal
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import com.google.common.truth.Truth.assertThat
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * Rule for tracking the lock state of the device based on events emitted to [TrustListener].
+ */
+class LockStateTrackingRule : TestRule {
+    private val context: Context = getApplicationContext()
+    private val windowManager = WindowManagerGlobal.getWindowManagerService()
+
+    @Volatile lateinit var lockState: LockState
+        private set
+
+    override fun apply(base: Statement, description: Description) = object : Statement() {
+        override fun evaluate() {
+            lockState = LockState(locked = windowManager.isKeyguardLocked)
+            val trustManager = context.getSystemService(TrustManager::class.java) as TrustManager
+            val listener = Listener()
+
+            trustManager.registerTrustListener(listener)
+            try {
+                base.evaluate()
+            } finally {
+                trustManager.unregisterTrustListener(listener)
+            }
+        }
+    }
+
+    fun assertLocked() = assertThat(lockState.locked).isTrue()
+    fun assertUnlocked() = assertThat(lockState.locked).isFalse()
+
+    inner class Listener : TrustListener {
+        override fun onTrustChanged(
+            enabled: Boolean,
+            userId: Int,
+            flags: Int,
+            trustGrantedMessages: MutableList<String>
+        ) {
+            Log.d(TAG, "Device became trusted=$enabled")
+            lockState = lockState.copy(locked = !enabled)
+        }
+
+        override fun onTrustManagedChanged(enabled: Boolean, userId: Int) {
+        }
+
+        override fun onTrustError(message: CharSequence) {
+        }
+    }
+
+    data class LockState(
+        val locked: Boolean? = null
+    )
+
+    companion object {
+        private const val TAG = "LockStateTrackingRule"
+    }
+}
diff --git a/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt b/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt
new file mode 100644
index 0000000..c682a00
--- /dev/null
+++ b/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.trust.test.lib
+
+import android.content.Context
+import android.util.Log
+import android.view.WindowManagerGlobal
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import com.android.internal.widget.LockPatternUtils
+import com.android.internal.widget.LockscreenCredential
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * Sets a screen lock on the device for the duration of the test.
+ */
+class ScreenLockRule : TestRule {
+    private val context: Context = getApplicationContext()
+    private val windowManager = WindowManagerGlobal.getWindowManagerService()
+    private val lockPatternUtils = LockPatternUtils(context)
+    private var instantLockSavedValue = false
+
+    override fun apply(base: Statement, description: Description) = object : Statement() {
+        override fun evaluate() {
+            verifyNoScreenLockAlreadySet()
+            verifyKeyguardDismissed()
+            setScreenLock()
+            setLockOnPowerButton()
+
+            try {
+                base.evaluate()
+            } finally {
+                removeScreenLock()
+                revertLockOnPowerButton()
+            }
+        }
+    }
+
+    private fun verifyNoScreenLockAlreadySet() {
+        assertWithMessage("Screen Lock must not already be set on device")
+            .that(lockPatternUtils.isSecure(context.userId))
+            .isFalse()
+    }
+
+    private fun verifyKeyguardDismissed() {
+        windowManager.dismissKeyguard(null, null)
+        Thread.sleep(250)
+        assertWithMessage("Keyguard should be unlocked")
+            .that(windowManager.isKeyguardLocked)
+            .isFalse()
+    }
+
+    private fun setScreenLock() {
+        lockPatternUtils.setLockCredential(
+            LockscreenCredential.createPin(PIN),
+            LockscreenCredential.createNone(),
+            context.userId
+        )
+        assertWithMessage("Screen Lock should now be set")
+            .that(lockPatternUtils.isSecure(context.userId))
+            .isTrue()
+        Log.i(TAG, "Device PIN set to $PIN")
+    }
+
+    private fun setLockOnPowerButton() {
+        instantLockSavedValue = lockPatternUtils.getPowerButtonInstantlyLocks(context.userId)
+        lockPatternUtils.setPowerButtonInstantlyLocks(true, context.userId)
+    }
+
+    private fun removeScreenLock() {
+        lockPatternUtils.setLockCredential(
+            LockscreenCredential.createNone(),
+            LockscreenCredential.createPin(PIN),
+            context.userId
+        )
+        Log.i(TAG, "Device PIN cleared; waiting 50 ms then dismissing Keyguard")
+        Thread.sleep(50)
+        windowManager.dismissKeyguard(null, null)
+    }
+
+    private fun revertLockOnPowerButton() {
+        lockPatternUtils.setPowerButtonInstantlyLocks(instantLockSavedValue, context.userId)
+    }
+
+    companion object {
+        private const val TAG = "ScreenLockRule"
+        private const val PIN = "0000"
+    }
+}
diff --git a/tests/TrustTests/src/android/trust/test/lib/TrustAgentRule.kt b/tests/TrustTests/src/android/trust/test/lib/TrustAgentRule.kt
new file mode 100644
index 0000000..2a9e002
--- /dev/null
+++ b/tests/TrustTests/src/android/trust/test/lib/TrustAgentRule.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.trust.test.lib
+
+import android.app.trust.TrustManager
+import android.content.ComponentName
+import android.content.Context
+import android.trust.BaseTrustAgentService
+import android.util.Log
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import com.android.internal.widget.LockPatternUtils
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+import kotlin.reflect.KClass
+
+/**
+ * Enables a trust agent and causes the system service to bind to it.
+ *
+ * The enabled agent can be accessed during the test via the [agent] property.
+ *
+ * @constructor Creates the rule. Do not use; instead, use [invoke].
+ */
+class TrustAgentRule<T : BaseTrustAgentService>(
+    private val serviceClass: KClass<T>
+) : TestRule {
+    private val context: Context = getApplicationContext()
+    private val trustManager = context.getSystemService(TrustManager::class.java) as TrustManager
+    private val lockPatternUtils = LockPatternUtils(context)
+
+    val agent get() = BaseTrustAgentService.instance(serviceClass) as T
+
+    override fun apply(base: Statement, description: Description) = object : Statement() {
+        override fun evaluate() {
+            verifyTrustServiceRunning()
+            unlockDeviceWithCredential()
+            enableTrustAgent()
+            waitForEnablement()
+
+            try {
+                verifyAgentIsRunning()
+                base.evaluate()
+            } finally {
+                disableTrustAgent()
+            }
+        }
+    }
+
+    private fun verifyTrustServiceRunning() {
+        assertWithMessage("Trust service is not running").that(trustManager).isNotNull()
+    }
+
+    private fun unlockDeviceWithCredential() {
+        Log.d(TAG, "Unlocking device with credential")
+        trustManager.reportUnlockAttempt(true, context.userId)
+    }
+
+    private fun enableTrustAgent() {
+        val componentName = ComponentName(context, serviceClass.java)
+        val userId = context.userId
+        Log.i(TAG, "Enabling trust agent ${componentName.flattenToString()} for user $userId")
+        val agents = mutableListOf(componentName)
+            .plus(lockPatternUtils.getEnabledTrustAgents(userId))
+            .distinct()
+        lockPatternUtils.setEnabledTrustAgents(agents, userId)
+    }
+
+    private fun waitForEnablement() {
+        Log.d(TAG, "Waiting for $WAIT_TIME ms")
+        Thread.sleep(WAIT_TIME)
+        Log.d(TAG, "Done waiting")
+    }
+
+    private fun verifyAgentIsRunning() {
+        assertWithMessage("${serviceClass.simpleName} should be running")
+            .that(BaseTrustAgentService.instance(serviceClass)).isNotNull()
+    }
+
+    private fun disableTrustAgent() {
+        val componentName = ComponentName(context, serviceClass.java)
+        val userId = context.userId
+        Log.i(TAG, "Disabling trust agent ${componentName.flattenToString()} for user $userId")
+        val agents = lockPatternUtils.getEnabledTrustAgents(userId).toMutableList()
+            .distinct()
+            .minus(componentName)
+        lockPatternUtils.setEnabledTrustAgents(agents, userId)
+    }
+
+    companion object {
+        /**
+         * Creates a new rule for the specified agent class. Example usage:
+         * ```
+         *   @get:Rule val rule = TrustAgentRule<MyTestAgent>()
+         * ```
+         */
+        inline operator fun <reified T : BaseTrustAgentService> invoke() =
+            TrustAgentRule(T::class)
+
+        private const val TAG = "TrustAgentRule"
+        private val WAIT_TIME = 1000L
+    }
+}