Merge "Implement center aligned parallax resizng effect" into tm-dev
diff --git a/Android.bp b/Android.bp
index cd110de..df6fdaa 100644
--- a/Android.bp
+++ b/Android.bp
@@ -65,7 +65,6 @@
         // Java/AIDL sources under frameworks/base
         ":framework-annotations",
         ":framework-blobstore-sources",
-        ":framework-connectivity-tiramisu-sources",
         ":framework-core-sources",
         ":framework-drm-sources",
         ":framework-graphics-nonupdatable-sources",
@@ -324,6 +323,7 @@
         "av-types-aidl-java",
         "tv_tuner_resource_manager_aidl_interface-java",
         "soundtrigger_middleware-aidl-java",
+        "modules-utils-build",
         "modules-utils-preconditions",
         "modules-utils-synchronous-result-receiver",
         "modules-utils-os",
diff --git a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
index c4795f5..53a3889 100644
--- a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
+++ b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
@@ -32,6 +32,8 @@
 import android.annotation.UserHandleAware;
 import android.content.Context;
 
+import com.android.internal.util.FrameworkStatsLog;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.Collections;
@@ -451,6 +453,62 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface ReasonCode {}
 
+    private static final int EXEMPTION_REASON_SYSTEM_UID = FrameworkStatsLog
+            .APP_BACKGROUND_RESTRICTIONS_INFO__EXEMPTION_REASON__REASON_SYSTEM_UID;
+    private static final int EXEMPTION_REASON_ALLOWLISTED_PACKAGE = FrameworkStatsLog
+            .APP_BACKGROUND_RESTRICTIONS_INFO__EXEMPTION_REASON__REASON_ALLOWLISTED_PACKAGE;
+    private static final int EXEMPTION_REASON_COMPANION_DEVICE_MANAGER = FrameworkStatsLog
+            .APP_BACKGROUND_RESTRICTIONS_INFO__EXEMPTION_REASON__REASON_COMPANION_DEVICE_MANAGER;
+    private static final int EXEMPTION_REASON_DEVICE_DEMO_MODE = FrameworkStatsLog
+            .APP_BACKGROUND_RESTRICTIONS_INFO__EXEMPTION_REASON__REASON_DEVICE_DEMO_MODE;
+    private static final int EXEMPTION_REASON_DEVICE_OWNER = FrameworkStatsLog
+            .APP_BACKGROUND_RESTRICTIONS_INFO__EXEMPTION_REASON__REASON_DEVICE_OWNER;
+    private static final int EXEMPTION_REASON_PROFILE_OWNER = FrameworkStatsLog
+            .APP_BACKGROUND_RESTRICTIONS_INFO__EXEMPTION_REASON__REASON_PROFILE_OWNER;
+    private static final int EXEMPTION_REASON_PROC_STATE_PERSISTENT = FrameworkStatsLog
+            .APP_BACKGROUND_RESTRICTIONS_INFO__EXEMPTION_REASON__REASON_PROC_STATE_PERSISTENT;
+    private static final int EXEMPTION_REASON_PROC_STATE_PERSISTENT_UI = FrameworkStatsLog
+            .APP_BACKGROUND_RESTRICTIONS_INFO__EXEMPTION_REASON__REASON_PROC_STATE_PERSISTENT_UI;
+    private static final int EXEMPTION_REASON_OP_ACTIVATE_VPN = FrameworkStatsLog
+            .APP_BACKGROUND_RESTRICTIONS_INFO__EXEMPTION_REASON__REASON_OP_ACTIVATE_VPN;
+    private static final int EXEMPTION_REASON_OP_ACTIVATE_PLATFORM_VPN = FrameworkStatsLog
+            .APP_BACKGROUND_RESTRICTIONS_INFO__EXEMPTION_REASON__REASON_OP_ACTIVATE_PLATFORM_VPN;
+    private static final int EXEMPTION_REASON_SYSTEM_MODULE = FrameworkStatsLog
+            .APP_BACKGROUND_RESTRICTIONS_INFO__EXEMPTION_REASON__REASON_SYSTEM_MODULE;
+    private static final int EXEMPTION_REASON_CARRIER_PRIVILEGED_APP = FrameworkStatsLog
+            .APP_BACKGROUND_RESTRICTIONS_INFO__EXEMPTION_REASON__REASON_CARRIER_PRIVILEGED_APP;
+    private static final int EXEMPTION_REASON_SYSTEM_ALLOW_LISTED = FrameworkStatsLog
+            .APP_BACKGROUND_RESTRICTIONS_INFO__EXEMPTION_REASON__REASON_SYSTEM_ALLOW_LISTED;
+    private static final int EXEMPTION_REASON_ROLE_DIALER = FrameworkStatsLog
+            .APP_BACKGROUND_RESTRICTIONS_INFO__EXEMPTION_REASON__REASON_ROLE_DIALER;
+    private static final int EXEMPTION_REASON_ROLE_EMERGENCY = FrameworkStatsLog
+            .APP_BACKGROUND_RESTRICTIONS_INFO__EXEMPTION_REASON__REASON_ROLE_EMERGENCY;
+    private static final int EXEMPTION_REASON_DENIED = FrameworkStatsLog
+            .APP_BACKGROUND_RESTRICTIONS_INFO__EXEMPTION_REASON__REASON_DENIED;
+    /**
+     * List of exemption reason codes used for statsd logging in AppBackgroundRestrictionsInfo atom.
+     * @hide
+     */
+    @IntDef(prefix = { "EXEMPTION_REASON_" }, value = {
+            EXEMPTION_REASON_SYSTEM_UID,
+            EXEMPTION_REASON_ALLOWLISTED_PACKAGE,
+            EXEMPTION_REASON_COMPANION_DEVICE_MANAGER,
+            EXEMPTION_REASON_DEVICE_DEMO_MODE,
+            EXEMPTION_REASON_DEVICE_OWNER,
+            EXEMPTION_REASON_PROFILE_OWNER,
+            EXEMPTION_REASON_PROC_STATE_PERSISTENT,
+            EXEMPTION_REASON_PROC_STATE_PERSISTENT_UI,
+            EXEMPTION_REASON_OP_ACTIVATE_VPN,
+            EXEMPTION_REASON_OP_ACTIVATE_PLATFORM_VPN,
+            EXEMPTION_REASON_SYSTEM_MODULE,
+            EXEMPTION_REASON_CARRIER_PRIVILEGED_APP,
+            EXEMPTION_REASON_SYSTEM_ALLOW_LISTED,
+            EXEMPTION_REASON_ROLE_DIALER,
+            EXEMPTION_REASON_ROLE_EMERGENCY,
+            EXEMPTION_REASON_DENIED,
+    })
+    public @interface ExemptionReason {}
+
     /**
      * @hide
      */
@@ -618,6 +676,47 @@
     }
 
     /**
+     * @hide
+     * @return the reason code mapped to statsd for the AppBackgroundRestrictionsInfo atom.
+     */
+    public static @ExemptionReason int getExemptionReasonForStatsd(@ReasonCode int reasonCode) {
+        switch (reasonCode) {
+            case REASON_SYSTEM_UID:
+                return EXEMPTION_REASON_SYSTEM_UID;
+            case REASON_ALLOWLISTED_PACKAGE:
+                return EXEMPTION_REASON_ALLOWLISTED_PACKAGE;
+            case REASON_COMPANION_DEVICE_MANAGER:
+                return EXEMPTION_REASON_COMPANION_DEVICE_MANAGER;
+            case REASON_DEVICE_DEMO_MODE:
+                return EXEMPTION_REASON_DEVICE_DEMO_MODE;
+            case REASON_DEVICE_OWNER:
+                return EXEMPTION_REASON_DEVICE_OWNER;
+            case REASON_PROFILE_OWNER:
+                return EXEMPTION_REASON_PROFILE_OWNER;
+            case REASON_PROC_STATE_PERSISTENT:
+                return EXEMPTION_REASON_PROC_STATE_PERSISTENT;
+            case REASON_PROC_STATE_PERSISTENT_UI:
+                return EXEMPTION_REASON_PROC_STATE_PERSISTENT_UI;
+            case REASON_OP_ACTIVATE_VPN:
+                return EXEMPTION_REASON_OP_ACTIVATE_VPN;
+            case REASON_OP_ACTIVATE_PLATFORM_VPN:
+                return EXEMPTION_REASON_OP_ACTIVATE_PLATFORM_VPN;
+            case REASON_SYSTEM_MODULE:
+                return EXEMPTION_REASON_SYSTEM_MODULE;
+            case REASON_CARRIER_PRIVILEGED_APP:
+                return EXEMPTION_REASON_CARRIER_PRIVILEGED_APP;
+            case REASON_SYSTEM_ALLOW_LISTED:
+                return EXEMPTION_REASON_SYSTEM_ALLOW_LISTED;
+            case REASON_ROLE_DIALER:
+                return EXEMPTION_REASON_ROLE_DIALER;
+            case REASON_ROLE_EMERGENCY:
+                return EXEMPTION_REASON_ROLE_EMERGENCY;
+            default:
+                return EXEMPTION_REASON_DENIED;
+        }
+    }
+
+    /**
      * Return string name of the integer reason code.
      * @hide
      * @param reasonCode
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index f26e051..c6ba1ea 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -307,7 +307,7 @@
     private final SparseBooleanArray mTempAllowlistCache = new SparseBooleanArray();
 
     /**
-     * Mapping of UIDs to the when their temp allowlist grace period ends (in the elapsed
+     * Mapping of UIDs to when their temp allowlist grace period ends (in the elapsed
      * realtime timebase).
      */
     private final SparseLongArray mTempAllowlistGraceCache = new SparseLongArray();
@@ -815,6 +815,19 @@
                 jobStatus.getSourceUserId(), jobStatus.getSourcePackageName());
     }
 
+    private boolean hasTempAllowlistExemptionLocked(int sourceUid, int standbyBucket,
+            long nowElapsed) {
+        if (standbyBucket == RESTRICTED_INDEX || standbyBucket == NEVER_INDEX) {
+            // Don't let RESTRICTED apps get free quota from the temp allowlist.
+            // TODO: consider granting the exemption to RESTRICTED apps if the temp allowlist allows
+            // them to start FGS
+            return false;
+        }
+        final long tempAllowlistGracePeriodEndElapsed = mTempAllowlistGraceCache.get(sourceUid);
+        return mTempAllowlistCache.get(sourceUid)
+                || nowElapsed < tempAllowlistGracePeriodEndElapsed;
+    }
+
     /** @return true if the job is within expedited job quota. */
     @GuardedBy("mLock")
     public boolean isWithinEJQuotaLocked(@NonNull final JobStatus jobStatus) {
@@ -833,11 +846,8 @@
         }
 
         final long nowElapsed = sElapsedRealtimeClock.millis();
-        final long tempAllowlistGracePeriodEndElapsed =
-                mTempAllowlistGraceCache.get(jobStatus.getSourceUid());
-        final boolean hasTempAllowlistExemption = mTempAllowlistCache.get(jobStatus.getSourceUid())
-                || nowElapsed < tempAllowlistGracePeriodEndElapsed;
-        if (hasTempAllowlistExemption) {
+        if (hasTempAllowlistExemptionLocked(jobStatus.getSourceUid(),
+                jobStatus.getEffectiveStandbyBucket(), nowElapsed)) {
             return true;
         }
 
@@ -2127,10 +2137,8 @@
             final long nowElapsed = sElapsedRealtimeClock.millis();
             final int standbyBucket = JobSchedulerService.standbyBucketForPackage(mPkg.packageName,
                     mPkg.userId, nowElapsed);
-            final long tempAllowlistGracePeriodEndElapsed = mTempAllowlistGraceCache.get(mUid);
             final boolean hasTempAllowlistExemption = !mRegularJobTimer
-                    && (mTempAllowlistCache.get(mUid)
-                    || nowElapsed < tempAllowlistGracePeriodEndElapsed);
+                    && hasTempAllowlistExemptionLocked(mUid, standbyBucket, nowElapsed);
             final long topAppGracePeriodEndElapsed = mTopAppGraceCache.get(mUid);
             final boolean hasTopAppExemption = !mRegularJobTimer
                     && (mTopAppCache.get(mUid) || nowElapsed < topAppGracePeriodEndElapsed);
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/README.md b/apex/jobscheduler/service/java/com/android/server/tare/README.md
index a4933a1..33eadff 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/README.md
+++ b/apex/jobscheduler/service/java/com/android/server/tare/README.md
@@ -1,10 +1,12 @@
+# Overview
+
 Welcome to The Android Resource Economy (TARE for short). If you're reading this, you may be
 wondering what all of this code is for and what it means. TARE is an attempt to apply economic
 principles to resource (principally battery) management. It acknowledges that battery is a limited
 resource on mobile devices and that the system must allocate and apportion those resources
-accordingly. Every action (running a job, firing an alarm, using the network, using the CPU,
-etc.) has a cost. Once that action has been performed and that bit of battery has been drained, it's
-no longer available for someone else (another app) to use until the user charges the device again.
+accordingly. Every action (running a job, firing an alarm, using the network, using the CPU, etc.)
+has a cost. Once that action has been performed and that bit of battery has been drained, it's no
+longer available for someone else (another app) to use until the user charges the device again.
 
 The key tenets of TARE are:
 
@@ -13,10 +15,22 @@
 1. Reward for good actions --- reward and encourage behavior that provides value to the user
 1. Fine bad actions --- fine and discourage behavior that is bad for the user
 
-# Details
+In an ideal world, the system could be said to most efficiently allocate resources by maximizing its
+profits &mdash; by maximizing the aggregate sum of the difference between an action's price (that
+the app ends up paying) and the cost to produce by the system. This assumes that more important
+actions have a higher price than less important actions. With this assumption, maximizing profits
+implies that the system runs the most important work first and proceeds in decreasing order of
+importance. Of course, that also means the system will not run anything where an app would pay less
+for the action than the system's cost to produce that action. Some of this breaks down when we throw
+TOP apps into the mix &mdash; TOP apps pay 0 for all actions, even though the CTP may be greater
+than 0. This is to ensure ideal user experience for the app the user is actively interacting with.
+Similar caveats exist for system-critical processes (such as the OS itself) and apps running
+foreground services (since those could be critical to user experience, as is the case for media and
+navigation apps). Excluding those caveats/special situations, maximizing profits of actions
+performed by apps in the background should be the target.
 
-To achieve the goal laid out by TARE, we introduce the concept of Android Resource Credits
-(ARCs for short).
+To achieve the goal laid out by TARE, we use Android Resource Credits (ARCs for short) as the
+internal/representative currency of the system.
 
 ## How do ARCs work?
 
@@ -36,6 +50,57 @@
 With the ARC system, we can limit the total number of ARCs in circulation, thus limiting how much
 total work can be done, regardless of how many apps the user has installed.
 
+## EconomicPolicy
+
+An EconomicPolicy defines the actions and rewards a specific subsystem makes use of. Each subsystem
+will likely have a unique set of actions that apps can perform, and may choose to reward apps for
+certain behaviors. Generally, the app should be rewarded with ARCs for behaviors that indicate that
+the app provided value to the user. The current set of behaviors that apps may be rewarded for
+include 1) a user seeing a notification, 2) a user interacting with a notification, 3) the user
+opening the app and/or staying in the app for some period of time, 4) the user interacting with a
+widget, and 5) the user explicitly interacting with the app in some other way. These behaviors may
+change as we determine better ways of identifying providing value to the user and/or user desire for
+the app to perform the actions it's requesting.
+
+### Consumption Limit
+
+The consumption limit represents the maximum amount of resources available to be consumed. When the
+battery is satiated (at 100%), then the amount of resources available to be consumed is equal to the
+consumption limit. Each action has a cost to produce that action. When the action is performed,
+those resources are consumed. Thus, when an action is performed, the action's CTP is deducted from
+the remaining amount of resources available. In keeping with the tenet that resources are limited
+and ARCs are a proxy for battery consumption, the amount of resources available to be consumed are
+adjusted as the battery level changes. That is, the consumption limit is scaled based on the current
+battery level, and if the amount currently available to be consumed is greater than the scaled
+consumption limit, then the available resources are decreased to match the scaled limit.
+
+### Regulation
+
+Regulations are unique events invoked by the ~~government~~ system in order to get the whole economy
+moving smoothly.
+
+# Previous Implementations
+
+## V0
+
+The initial implementation/proposal combined the supply of resources with the allocation in a single
+mechanism. It defined the maximum number of resources (ARCs) available at a time, and then divided
+(allocated) that number among the installed apps, intending to have some left over that could be
+allocated as part of the rewards. There were several problems with that mechanism:
+
+1. Not all apps used their credits, which meant that allocating credits to those packages
+   effectively permanently reduced the number of usable/re-allocatable ARCs.
+1. Having a global maximum circulation spread across multiple apps meant that as more apps were
+   installed, the allocation to each app decreased. Eventually (with enough apps installed), no app
+   would be given enough credits to perform any actions.
+
+These problems effectively meant that misallocation was a big problem, demand wasn't well reflected,
+and some apps may not have been able to perform work even though they otherwise should have been.
+
+Tare Improvement Proposal #1 (TIP1) separated allocation (to apps) from supply (by the system) and
+allowed apps to accrue credits as appropriate while still limiting the total number of credits
+consumed.
+
 # Definitions
 
 * ARC: Android Resource Credits are the "currency" units used as an abstraction layer over the real
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index fde59ea..c69e901 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -281,7 +281,7 @@
     private static final long NETWORK_SCORER_CACHE_DURATION_MILLIS = 5000L;
 
     // Cache the device provisioning package queried from resource config_deviceProvisioningPackage.
-    // Note that there is no synchronization on this method which is okay since in the worst case
+    // Note that there is no synchronization on this variable which is okay since in the worst case
     // scenario, they might be a few extra reads from resources.
     private String mCachedDeviceProvisioningPackage = null;
 
@@ -394,7 +394,7 @@
     private boolean mAllowRestrictedBucket;
 
     private volatile boolean mAppIdleEnabled;
-    private boolean mIsCharging;
+    private volatile boolean mIsCharging;
     private boolean mSystemServicesReady = false;
     // There was a system update, defaults need to be initialized after services are ready
     private boolean mPendingInitializeDefaults;
@@ -721,12 +721,10 @@
 
     @VisibleForTesting
     void setChargingState(boolean isCharging) {
-        synchronized (mAppIdleLock) {
-            if (mIsCharging != isCharging) {
-                if (DEBUG) Slog.d(TAG, "Setting mIsCharging to " + isCharging);
-                mIsCharging = isCharging;
-                postParoleStateChanged();
-            }
+        if (mIsCharging != isCharging) {
+            if (DEBUG) Slog.d(TAG, "Setting mIsCharging to " + isCharging);
+            mIsCharging = isCharging;
+            postParoleStateChanged();
         }
     }
 
@@ -1340,16 +1338,12 @@
     @Override
     public boolean isAppIdleFiltered(String packageName, int appId, int userId,
             long elapsedRealtime) {
-        if (getAppMinBucket(packageName, appId, userId) < AppIdleHistory.IDLE_BUCKET_CUTOFF) {
+        if (!mAppIdleEnabled || mIsCharging) {
             return false;
-        } else {
-            synchronized (mAppIdleLock) {
-                if (!mAppIdleEnabled || mIsCharging) {
-                    return false;
-                }
-            }
-            return isAppIdleUnfiltered(packageName, userId, elapsedRealtime);
         }
+
+        return isAppIdleUnfiltered(packageName, userId, elapsedRealtime)
+                && getAppMinBucket(packageName, appId, userId) >= AppIdleHistory.IDLE_BUCKET_CUTOFF;
     }
 
     static boolean isUserUsage(int reason) {
@@ -1803,7 +1797,7 @@
         }
     }
 
-    private boolean isActiveNetworkScorer(String packageName) {
+    private boolean isActiveNetworkScorer(@NonNull String packageName) {
         // Validity of network scorer cache is limited to a few seconds. Fetch it again
         // if longer since query.
         // This is a temporary optimization until there's a callback mechanism for changes to network scorer.
@@ -1813,7 +1807,7 @@
             mCachedNetworkScorer = mInjector.getActiveNetworkScorer();
             mCachedNetworkScorerAtMillis = now;
         }
-        return packageName != null && packageName.equals(mCachedNetworkScorer);
+        return packageName.equals(mCachedNetworkScorer);
     }
 
     private void informListeners(String packageName, int userId, int bucket, int reason,
@@ -2322,8 +2316,8 @@
          * Returns {@code true} if the supplied package is the wellbeing app. Otherwise,
          * returns {@code false}.
          */
-        boolean isWellbeingPackage(String packageName) {
-            return mWellbeingApp != null && mWellbeingApp.equals(packageName);
+        boolean isWellbeingPackage(@NonNull String packageName) {
+            return packageName.equals(mWellbeingApp);
         }
 
         boolean hasExactAlarmPermission(String packageName, int uid) {
diff --git a/cmds/locksettings/src/com/android/commands/locksettings/LockSettingsCmd.java b/cmds/locksettings/src/com/android/commands/locksettings/LockSettingsCmd.java
index 6a4a4be..7d9260a 100644
--- a/cmds/locksettings/src/com/android/commands/locksettings/LockSettingsCmd.java
+++ b/cmds/locksettings/src/com/android/commands/locksettings/LockSettingsCmd.java
@@ -28,43 +28,13 @@
 
 public final class LockSettingsCmd extends BaseCommand {
 
-    private static final String USAGE =
-            "usage: locksettings set-pattern [--old OLD_CREDENTIAL] NEW_PATTERN\n" +
-            "       locksettings set-pin [--old OLD_CREDENTIAL] NEW_PIN\n" +
-            "       locksettings set-password [--old OLD_CREDENTIAL] NEW_PASSWORD\n" +
-            "       locksettings clear [--old OLD_CREDENTIAL]\n" +
-            "       locksettings verify [--old OLD_CREDENTIAL]\n" +
-            "       locksettings set-disabled DISABLED\n" +
-            "       locksettings get-disabled\n" +
-            "\n" +
-            "flags: \n" +
-            "       --user USER_ID: specify the user, default value is current user\n" +
-            "\n" +
-            "locksettings set-pattern: sets a pattern\n" +
-            "    A pattern is specified by a non-separated list of numbers that index the cell\n" +
-            "    on the pattern in a 1-based manner in left to right and top to bottom order,\n" +
-            "    i.e. the top-left cell is indexed with 1, whereas the bottom-right cell\n" +
-            "    is indexed with 9. Example: 1234\n" +
-            "\n" +
-            "locksettings set-pin: sets a PIN\n" +
-            "\n" +
-            "locksettings set-password: sets a password\n" +
-            "\n" +
-            "locksettings clear: clears the unlock credential\n" +
-            "\n" +
-            "locksettings verify: verifies the credential and unlocks the user\n" +
-            "\n" +
-            "locksettings set-disabled: sets whether the lock screen should be disabled\n" +
-            "\n" +
-            "locksettings get-disabled: retrieves whether the lock screen is disabled\n";
-
     public static void main(String[] args) {
         (new LockSettingsCmd()).run(args);
     }
 
     @Override
     public void onShowUsage(PrintStream out) {
-        out.println(USAGE);
+        main(new String[] { "help" });
     }
 
     @Override
diff --git a/cmds/svc/src/com/android/commands/svc/UsbCommand.java b/cmds/svc/src/com/android/commands/svc/UsbCommand.java
index 115c1f2..7d80493 100644
--- a/cmds/svc/src/com/android/commands/svc/UsbCommand.java
+++ b/cmds/svc/src/com/android/commands/svc/UsbCommand.java
@@ -16,12 +16,20 @@
 
 package com.android.commands.svc;
 
+import android.app.ActivityThread;
 import android.content.Context;
 import android.hardware.usb.IUsbManager;
 import android.hardware.usb.UsbManager;
+import android.hardware.usb.UsbPort;
+import android.hardware.usb.UsbPortStatus;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 
+import java.util.function.Consumer;
+import java.util.concurrent.Executor;
+import java.util.List;
+
 public class UsbCommand extends Svc.Command {
     public UsbCommand() {
         super("usb");
@@ -39,8 +47,9 @@
                 + "usage: svc usb setFunctions [function]\n"
                 + "         Set the current usb function. If function is blank, sets to charging.\n"
                 + "       svc usb setScreenUnlockedFunctions [function]\n"
-                + "         Sets the functions which, if the device was charging, become current on"
-                    + "screen unlock. If function is blank, turn off this feature.\n"
+                + "         Sets the functions which, if the device was charging,\n"
+                + "         become current on screen unlock.\n"
+                + "         If function is blank, turn off this feature.\n"
                 + "       svc usb getFunctions\n"
                 + "         Gets the list of currently enabled functions\n"
                 + "         possible values of [function] are any of 'mtp', 'ptp', 'rndis',\n"
@@ -59,14 +68,28 @@
                 + "       svc usb getUsbHalVersion\n"
                 + "         Gets current USB Hal Version\n"
                 + "         possible values of Hal version are any of 'unknown', 'V1_0', 'V1_1',\n"
-                + "         'V1_2', 'V1_3'\n";
+                + "         'V1_2', 'V1_3'\n"
+                + "       svc usb resetUsbPort [port number]\n"
+                + "         Reset the specified connected usb port\n"
+                + "         default: the first connected usb port\n";
     }
 
     @Override
     public void run(String[] args) {
         if (args.length >= 2) {
+            Looper.prepareMainLooper();
+            Context context = ActivityThread.systemMain().getSystemContext();
+            UsbManager usbManager = context.getSystemService(UsbManager.class);
             IUsbManager usbMgr = IUsbManager.Stub.asInterface(ServiceManager.getService(
                     Context.USB_SERVICE));
+
+            Executor executor = context.getMainExecutor();
+            Consumer<Integer> consumer = new Consumer<Integer>(){
+                public void accept(Integer status){
+                    System.out.println("Consumer status: " + status);
+                };
+            };
+
             if ("setFunctions".equals(args[1])) {
                 try {
                     usbMgr.setCurrentFunctions(UsbManager.usbFunctionsFromString(
@@ -134,6 +157,49 @@
                     System.err.println("Error communicating with UsbManager: " + e);
                 }
                 return;
+            } else if ("resetUsbPort".equals(args[1])) {
+                try {
+                    int portNum = args.length >= 3 ? Integer.parseInt(args[2]) : -1;
+                    UsbPort port = null;
+                    UsbPortStatus portStatus = null;
+                    List<UsbPort> ports = usbManager.getPorts();
+                    final int numPorts = ports.size();
+
+                    if (numPorts > 0) {
+                        if (portNum != -1 && portNum < numPorts) {
+                            portStatus = ports.get(portNum).getStatus();
+                            if (portStatus.isConnected()) {
+                                port = ports.get(portNum);
+                                System.err.println(
+                                        "Get the USB port: port" + portNum);
+                            }
+                        } else {
+                            for (portNum = 0; portNum < numPorts; portNum++) {
+                                UsbPortStatus status = ports.get(portNum).getStatus();
+                                if (status.isConnected()) {
+                                    port = ports.get(portNum);
+                                    portStatus = status;
+                                    System.err.println(
+                                            "Use the default USB port: port" + portNum);
+                                    break;
+                                }
+                            }
+                        }
+                        if (port != null && portStatus.isConnected()) {
+                            System.err.println(
+                                    "Reset the USB port: port" + portNum);
+                            port.resetUsbPort(executor, consumer);
+                        } else {
+                            System.err.println(
+                                    "There is no available reset USB port");
+                        }
+                    } else {
+                        System.err.println("No USB ports");
+                    }
+                } catch (Exception e) {
+                    System.err.println("Error communicating with UsbManager: " + e);
+                }
+                return;
             }
         }
         System.err.println(longHelp());
diff --git a/core/api/current.txt b/core/api/current.txt
index 84aaa44..16d83a4 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -390,6 +390,7 @@
     field public static final int autoVerify = 16844014; // 0x10104ee
     field public static final int autofillHints = 16844118; // 0x1010556
     field public static final int autofilledHighlight = 16844136; // 0x1010568
+    field public static final int backdropColor;
     field public static final int background = 16842964; // 0x10100d4
     field public static final int backgroundDimAmount = 16842802; // 0x1010032
     field public static final int backgroundDimEnabled = 16843295; // 0x101021f
@@ -1345,7 +1346,7 @@
     field public static final int shouldDisableView = 16843246; // 0x10101ee
     field public static final int shouldUseDefaultUnfoldTransition = 16844364; // 0x101064c
     field public static final int showAsAction = 16843481; // 0x10102d9
-    field public static final int showBackground;
+    field public static final int showBackdrop;
     field public static final int showClockAndComplications;
     field public static final int showDefault = 16843258; // 0x10101fa
     field public static final int showDividers = 16843561; // 0x1010329
@@ -4073,7 +4074,7 @@
     method @Deprecated public void onTabUnselected(android.app.ActionBar.Tab, android.app.FragmentTransaction);
   }
 
-  @UiContext public class Activity extends android.view.ContextThemeWrapper implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback android.view.LayoutInflater.Factory2 android.window.OnBackInvokedDispatcherOwner android.view.View.OnCreateContextMenuListener android.view.Window.Callback {
+  @UiContext public class Activity extends android.view.ContextThemeWrapper implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback android.view.LayoutInflater.Factory2 android.view.View.OnCreateContextMenuListener android.view.Window.Callback {
     ctor public Activity();
     method public void addContentView(android.view.View, android.view.ViewGroup.LayoutParams);
     method public void closeContextMenu();
@@ -5017,7 +5018,7 @@
     method public void onDateSet(android.widget.DatePicker, int, int, int);
   }
 
-  public class Dialog implements android.content.DialogInterface android.view.KeyEvent.Callback android.window.OnBackInvokedDispatcherOwner android.view.View.OnCreateContextMenuListener android.view.Window.Callback {
+  public class Dialog implements android.content.DialogInterface android.view.KeyEvent.Callback android.view.View.OnCreateContextMenuListener android.view.Window.Callback {
     ctor public Dialog(@NonNull @UiContext android.content.Context);
     ctor public Dialog(@NonNull @UiContext android.content.Context, @StyleRes int);
     ctor protected Dialog(@NonNull @UiContext android.content.Context, boolean, @Nullable android.content.DialogInterface.OnCancelListener);
@@ -49644,6 +49645,7 @@
     method @CallSuper public void drawableHotspotChanged(float, float);
     method @CallSuper protected void drawableStateChanged();
     method public android.view.View findFocus();
+    method @Nullable public final android.window.OnBackInvokedDispatcher findOnBackInvokedDispatcher();
     method public final <T extends android.view.View> T findViewById(@IdRes int);
     method public final <T extends android.view.View> T findViewWithTag(Object);
     method public void findViewsWithText(java.util.ArrayList<android.view.View>, CharSequence, int);
@@ -50821,6 +50823,7 @@
     method public void childHasTransientStateChanged(@NonNull android.view.View, boolean);
     method public void clearChildFocus(android.view.View);
     method public void createContextMenu(android.view.ContextMenu);
+    method @Nullable public default android.window.OnBackInvokedDispatcher findOnBackInvokedDispatcherForChild(@NonNull android.view.View, @NonNull android.view.View);
     method public android.view.View focusSearch(android.view.View, int);
     method public void focusableViewAvailable(android.view.View);
     method public boolean getChildVisibleRect(android.view.View, android.graphics.Rect, android.graphics.Point);
@@ -51084,6 +51087,7 @@
     method public android.media.session.MediaController getMediaController();
     method @ColorInt public abstract int getNavigationBarColor();
     method @ColorInt public int getNavigationBarDividerColor();
+    method @NonNull public android.window.OnBackInvokedDispatcher getOnBackInvokedDispatcher();
     method public android.transition.Transition getReenterTransition();
     method public android.transition.Transition getReturnTransition();
     method @Nullable public android.view.AttachedSurfaceControl getRootSurfaceControl();
@@ -51602,7 +51606,7 @@
     field public String packageName;
     field public boolean preferMinimalPostProcessing;
     field public int preferredDisplayModeId;
-    field @Deprecated public float preferredRefreshRate;
+    field public float preferredRefreshRate;
     field public int rotationAnimation;
     field public float screenBrightness;
     field public int screenOrientation;
@@ -52277,7 +52281,8 @@
     method protected android.view.animation.Animation clone() throws java.lang.CloneNotSupportedException;
     method public long computeDurationHint();
     method protected void ensureInterpolator();
-    method @ColorInt public int getBackgroundColor();
+    method @ColorInt public int getBackdropColor();
+    method @Deprecated @ColorInt public int getBackgroundColor();
     method @Deprecated public boolean getDetachWallpaper();
     method public long getDuration();
     method public boolean getFillAfter();
@@ -52286,7 +52291,7 @@
     method public int getRepeatCount();
     method public int getRepeatMode();
     method protected float getScaleFactor();
-    method public boolean getShowBackground();
+    method public boolean getShowBackdrop();
     method public long getStartOffset();
     method public long getStartTime();
     method public boolean getTransformation(long, android.view.animation.Transformation);
@@ -52302,7 +52307,8 @@
     method public void restrictDuration(long);
     method public void scaleCurrentDuration(float);
     method public void setAnimationListener(android.view.animation.Animation.AnimationListener);
-    method public void setBackgroundColor(@ColorInt int);
+    method public void setBackdropColor(@ColorInt int);
+    method @Deprecated public void setBackgroundColor(@ColorInt int);
     method @Deprecated public void setDetachWallpaper(boolean);
     method public void setDuration(long);
     method public void setFillAfter(boolean);
@@ -52312,7 +52318,7 @@
     method public void setInterpolator(android.view.animation.Interpolator);
     method public void setRepeatCount(int);
     method public void setRepeatMode(int);
-    method public void setShowBackground(boolean);
+    method public void setShowBackdrop(boolean);
     method public void setStartOffset(long);
     method public void setStartTime(long);
     method public void setZAdjustment(int);
@@ -57976,7 +57982,7 @@
 package android.window {
 
   public interface OnBackInvokedCallback {
-    method public default void onBackInvoked();
+    method public void onBackInvoked();
   }
 
   public interface OnBackInvokedDispatcher {
@@ -57986,10 +57992,6 @@
     field public static final int PRIORITY_OVERLAY = 1000000; // 0xf4240
   }
 
-  public interface OnBackInvokedDispatcherOwner {
-    method @NonNull public android.window.OnBackInvokedDispatcher getOnBackInvokedDispatcher();
-  }
-
   public interface SplashScreen {
     method public void clearOnExitAnimationListener();
     method public void setOnExitAnimationListener(@NonNull android.window.SplashScreen.OnExitAnimationListener);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 44a79c6..b952216 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -11,7 +11,7 @@
 
 package android.app {
 
-  @UiContext public class Activity extends android.view.ContextThemeWrapper implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback android.view.LayoutInflater.Factory2 android.window.OnBackInvokedDispatcherOwner android.view.View.OnCreateContextMenuListener android.view.Window.Callback {
+  @UiContext public class Activity extends android.view.ContextThemeWrapper implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback android.view.LayoutInflater.Factory2 android.view.View.OnCreateContextMenuListener android.view.Window.Callback {
     method public final boolean addDumpable(@NonNull android.util.Dumpable);
   }
 
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 3f8fc56..650de2e0 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -469,7 +469,7 @@
 
 package android.app {
 
-  @UiContext public class Activity extends android.view.ContextThemeWrapper implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback android.view.LayoutInflater.Factory2 android.window.OnBackInvokedDispatcherOwner android.view.View.OnCreateContextMenuListener android.view.Window.Callback {
+  @UiContext public class Activity extends android.view.ContextThemeWrapper implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback android.view.LayoutInflater.Factory2 android.view.View.OnCreateContextMenuListener android.view.Window.Callback {
     method public void convertFromTranslucent();
     method public boolean convertToTranslucent(android.app.Activity.TranslucentConversionListener, android.app.ActivityOptions);
     method @Deprecated public boolean isBackgroundVisibleBehind();
@@ -5365,7 +5365,7 @@
     method @CheckResult @RequiresPermission(android.Manifest.permission.MANAGE_USB) public int enableUsbData(boolean);
     method @CheckResult @RequiresPermission(android.Manifest.permission.MANAGE_USB) public int enableUsbDataWhileDocked();
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USB) public android.hardware.usb.UsbPortStatus getStatus();
-    method @CheckResult @RequiresPermission(android.Manifest.permission.MANAGE_USB) public int resetUsbPort();
+    method @CheckResult @RequiresPermission(android.Manifest.permission.MANAGE_USB) public void resetUsbPort(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public void setRoles(int, int);
     field public static final int ENABLE_LIMIT_POWER_TRANSFER_ERROR_INTERNAL = 1; // 0x1
     field public static final int ENABLE_LIMIT_POWER_TRANSFER_ERROR_NOT_SUPPORTED = 2; // 0x2
@@ -5383,6 +5383,11 @@
     field public static final int ENABLE_USB_DATA_WHILE_DOCKED_ERROR_OTHER = 5; // 0x5
     field public static final int ENABLE_USB_DATA_WHILE_DOCKED_ERROR_PORT_MISMATCH = 3; // 0x3
     field public static final int ENABLE_USB_DATA_WHILE_DOCKED_SUCCESS = 0; // 0x0
+    field public static final int RESET_USB_PORT_ERROR_INTERNAL = 1; // 0x1
+    field public static final int RESET_USB_PORT_ERROR_NOT_SUPPORTED = 2; // 0x2
+    field public static final int RESET_USB_PORT_ERROR_OTHER = 4; // 0x4
+    field public static final int RESET_USB_PORT_ERROR_PORT_MISMATCH = 3; // 0x3
+    field public static final int RESET_USB_PORT_SUCCESS = 0; // 0x0
   }
 
   public final class UsbPortStatus implements android.os.Parcelable {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 1a34ef5..cd5ed21 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -104,7 +104,7 @@
 
 package android.app {
 
-  @UiContext public class Activity extends android.view.ContextThemeWrapper implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback android.view.LayoutInflater.Factory2 android.window.OnBackInvokedDispatcherOwner android.view.View.OnCreateContextMenuListener android.view.Window.Callback {
+  @UiContext public class Activity extends android.view.ContextThemeWrapper implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback android.view.LayoutInflater.Factory2 android.view.View.OnCreateContextMenuListener android.view.Window.Callback {
     method public final boolean addDumpable(@NonNull android.util.Dumpable);
     method public void dumpInternal(@NonNull String, @Nullable java.io.FileDescriptor, @NonNull java.io.PrintWriter, @Nullable String[]);
     method public void onMovedToDisplay(int, android.content.res.Configuration);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index dff3c07..acdab1b 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -148,7 +148,6 @@
 import android.widget.Toolbar;
 import android.window.OnBackInvokedCallback;
 import android.window.OnBackInvokedDispatcher;
-import android.window.OnBackInvokedDispatcherOwner;
 import android.window.SplashScreen;
 import android.window.WindowOnBackInvokedDispatcher;
 
@@ -749,8 +748,7 @@
         Window.Callback, KeyEvent.Callback,
         OnCreateContextMenuListener, ComponentCallbacks2,
         Window.OnWindowDismissedCallback,
-        ContentCaptureManager.ContentCaptureClient,
-        OnBackInvokedDispatcherOwner {
+        ContentCaptureManager.ContentCaptureClient {
     private static final String TAG = "Activity";
     private static final boolean DEBUG_LIFECYCLE = false;
 
@@ -1663,12 +1661,7 @@
                 .isOnBackInvokedCallbackEnabled(this);
         if (aheadOfTimeBack) {
             // Add onBackPressed as default back behavior.
-            mDefaultBackCallback = new OnBackInvokedCallback() {
-                @Override
-                public void onBackInvoked() {
-                    navigateBack();
-                }
-            };
+            mDefaultBackCallback = this::navigateBack;
             getOnBackInvokedDispatcher().registerSystemOnBackInvokedCallback(mDefaultBackCallback);
         }
     }
@@ -8998,12 +8991,11 @@
      * @throws IllegalStateException if this Activity is not visual.
      */
     @NonNull
-    @Override
     public OnBackInvokedDispatcher getOnBackInvokedDispatcher() {
         if (mWindow == null) {
             throw new IllegalStateException("OnBackInvokedDispatcher are not available on "
                     + "non-visual activities");
         }
-        return ((OnBackInvokedDispatcherOwner) mWindow).getOnBackInvokedDispatcher();
+        return mWindow.getOnBackInvokedDispatcher();
     }
 }
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 95ac799..9c391ab 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -562,14 +562,15 @@
     public abstract void unregisterProcessObserver(IProcessObserver processObserver);
 
     /**
-     * Checks if there is an unfinished instrumentation that targets the given uid.
+     * Gets the uid of the instrumentation source if there is an unfinished instrumentation that
+     * targets the given uid.
      *
      * @param uid The uid to be checked for
      *
-     * @return True, if there is an instrumentation whose target application uid matches the given
-     * uid, false otherwise
+     * @return the uid of the instrumentation source, if there is an instrumentation whose target
+     * application uid matches the given uid, and {@link android.os.Process#INVALID_UID} otherwise.
      */
-    public abstract boolean isUidCurrentlyInstrumented(int uid);
+    public abstract int getInstrumentationSourceUid(int uid);
 
     /** Is this a device owner app? */
     public abstract boolean isDeviceOwner(int uid);
@@ -791,10 +792,11 @@
          *
          * @param packageName The package name of the process.
          * @param uid The UID of the process.
-         * @param foregroundId The current foreground service notification ID, a negative value
-         *                     means this notification is being removed.
+         * @param foregroundId The current foreground service notification ID.
+         * @param canceling The given notification is being canceled.
          */
-        void onForegroundServiceNotificationUpdated(String packageName, int uid, int foregroundId);
+        void onForegroundServiceNotificationUpdated(String packageName, int uid, int foregroundId,
+                boolean canceling);
     }
 
     /**
@@ -842,4 +844,15 @@
      * Returns some summary statistics of the current PendingIntent queue - sizes and counts.
      */
     public abstract List<PendingIntentStats> getPendingIntentStats();
+
+    /**
+     * Register the UidObserver for NetworkPolicyManager service.
+     *
+     * This is equivalent to calling
+     * {@link IActivityManager#registerUidObserver(IUidObserver, int, int, String)} but having a
+     * separate method for NetworkPolicyManager service so that it's UidObserver can be called
+     * separately outside the usual UidObserver flow.
+     */
+    public abstract void registerNetworkPolicyUidObserver(@NonNull IUidObserver observer,
+            int which, int cutpoint, @NonNull String callingPackage);
 }
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 3b843a9..852dd97 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -536,6 +536,9 @@
         // A reusable token for other purposes, e.g. content capture, translation. It shouldn't be
         // used without security checks
         public IBinder shareableActivityToken;
+        // The token of the initial TaskFragment that embedded this activity. Do not rely on it
+        // after creation because the activity could be reparented.
+        @Nullable public IBinder mInitialTaskFragmentToken;
         int ident;
         @UnsupportedAppUsage
         Intent intent;
@@ -618,7 +621,8 @@
                 PersistableBundle persistentState, List<ResultInfo> pendingResults,
                 List<ReferrerIntent> pendingNewIntents, ActivityOptions activityOptions,
                 boolean isForward, ProfilerInfo profilerInfo, ClientTransactionHandler client,
-                IBinder assistToken, IBinder shareableActivityToken, boolean launchedFromBubble) {
+                IBinder assistToken, IBinder shareableActivityToken, boolean launchedFromBubble,
+                IBinder initialTaskFragmentToken) {
             this.token = token;
             this.assistToken = assistToken;
             this.shareableActivityToken = shareableActivityToken;
@@ -639,6 +643,7 @@
                     compatInfo);
             mActivityOptions = activityOptions;
             mLaunchedFromBubble = launchedFromBubble;
+            mInitialTaskFragmentToken = initialTaskFragmentToken;
             init();
         }
 
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index 72c7fe4..f0af9ba 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -62,7 +62,6 @@
 import android.view.accessibility.AccessibilityEvent;
 import android.window.OnBackInvokedCallback;
 import android.window.OnBackInvokedDispatcher;
-import android.window.OnBackInvokedDispatcherOwner;
 import android.window.WindowOnBackInvokedDispatcher;
 
 import com.android.internal.R;
@@ -98,8 +97,7 @@
  * </div>
  */
 public class Dialog implements DialogInterface, Window.Callback,
-        KeyEvent.Callback, OnCreateContextMenuListener, Window.OnWindowDismissedCallback,
-        OnBackInvokedDispatcherOwner {
+        KeyEvent.Callback, OnCreateContextMenuListener, Window.OnWindowDismissedCallback {
     private static final String TAG = "Dialog";
     @UnsupportedAppUsage
     private Activity mOwnerActivity;
@@ -459,12 +457,7 @@
         if (mContext != null
                 && WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(mContext)) {
             // Add onBackPressed as default back behavior.
-            mDefaultBackCallback = new OnBackInvokedCallback() {
-                @Override
-                public void onBackInvoked() {
-                    onBackPressed();
-                }
-            };
+            mDefaultBackCallback = this::onBackPressed;
             getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
                     OnBackInvokedDispatcher.PRIORITY_DEFAULT, mDefaultBackCallback);
             mDefaultBackCallback = null;
@@ -1474,8 +1467,7 @@
      * Returns null if the dialog is not attached to a window with a decor.
      */
     @NonNull
-    @Override
     public OnBackInvokedDispatcher getOnBackInvokedDispatcher() {
-        return ((OnBackInvokedDispatcherOwner) mWindow).getOnBackInvokedDispatcher();
+        return mWindow.getOnBackInvokedDispatcher();
     }
 }
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 995a9f3..961135f 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1058,10 +1058,11 @@
     }
     
     /**
-     * Sends the key events corresponding to the text to the app being
-     * instrumented.
-     * 
-     * @param text The text to be sent. 
+     * Sends the key events that result in the given text being typed into the currently focused
+     * window, and waits for it to be processed.
+     *
+     * @param text The text to be sent.
+     * @see #sendKeySync(KeyEvent)
      */
     public void sendStringSync(String text) {
         if (text == null) {
@@ -1084,11 +1085,12 @@
     }
 
     /**
-     * Send a key event to the currently focused window/view and wait for it to
-     * be processed.  Finished at some point after the recipient has returned
-     * from its event processing, though it may <em>not</em> have completely
-     * finished reacting from the event -- for example, if it needs to update
-     * its display as a result, it may still be in the process of doing that.
+     * Sends a key event to the currently focused window, and waits for it to be processed.
+     * <p>
+     * This method blocks until the recipient has finished handling the event. Note that the
+     * recipient may <em>not</em> have completely finished reacting from the event when this method
+     * returns. For example, it may still be in the process of updating its display or UI contents
+     * upon reacting to the injected event.
      *
      * @param event The event to send to the current focus.
      */
@@ -1116,34 +1118,42 @@
     }
 
     /**
-     * Sends an up and down key event sync to the currently focused window.
+     * Sends up and down key events with the given key code to the currently focused window, and
+     * waits for it to be processed.
      * 
-     * @param key The integer keycode for the event.
+     * @param keyCode The key code for the events to send.
+     * @see #sendKeySync(KeyEvent)
      */
-    public void sendKeyDownUpSync(int key) {        
-        sendKeySync(new KeyEvent(KeyEvent.ACTION_DOWN, key));
-        sendKeySync(new KeyEvent(KeyEvent.ACTION_UP, key));
-    }
-
-    /**
-     * Higher-level method for sending both the down and up key events for a
-     * particular character key code.  Equivalent to creating both KeyEvent
-     * objects by hand and calling {@link #sendKeySync}.  The event appears
-     * as if it came from keyboard 0, the built in one.
-     * 
-     * @param keyCode The key code of the character to send.
-     */
-    public void sendCharacterSync(int keyCode) {
+    public void sendKeyDownUpSync(int keyCode) {
         sendKeySync(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
         sendKeySync(new KeyEvent(KeyEvent.ACTION_UP, keyCode));
     }
-    
+
     /**
-     * Dispatch a pointer event. Finished at some point after the recipient has
-     * returned from its event processing, though it may <em>not</em> have
-     * completely finished reacting from the event -- for example, if it needs
-     * to update its display as a result, it may still be in the process of
-     * doing that.
+     * Sends up and down key events with the given key code to the currently focused window, and
+     * waits for it to be processed.
+     * <p>
+     * Equivalent to {@link #sendKeyDownUpSync(int)}.
+     *
+     * @param keyCode The key code of the character to send.
+     * @see #sendKeySync(KeyEvent)
+     */
+    public void sendCharacterSync(int keyCode) {
+        sendKeyDownUpSync(keyCode);
+    }
+
+    /**
+     * Dispatches a pointer event into a window owned by the instrumented application, and waits for
+     * it to be processed.
+     * <p>
+     * If the motion event being injected is targeted at a window that is not owned by the
+     * instrumented application, the input injection will fail. See {@link #getUiAutomation()} for
+     * injecting events into all windows.
+     * <p>
+     * This method blocks until the recipient has finished handling the event. Note that the
+     * recipient may <em>not</em> have completely finished reacting from the event when this method
+     * returns. For example, it may still be in the process of updating its display or UI contents
+     * upon reacting to the injected event.
      * 
      * @param event A motion event describing the pointer action.  (As noted in 
      * {@link MotionEvent#obtain(long, long, int, float, float, int)}, be sure to use 
@@ -1154,28 +1164,50 @@
         if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) == 0) {
             event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
         }
+
+        syncInputTransactionsAndInjectEventIntoSelf(event);
+    }
+
+    private void syncInputTransactionsAndInjectEventIntoSelf(MotionEvent event) {
+        final boolean syncBefore = event.getAction() == MotionEvent.ACTION_DOWN
+                || event.isFromSource(InputDevice.SOURCE_MOUSE);
+        final boolean syncAfter = event.getAction() == MotionEvent.ACTION_UP;
+
         try {
-            WindowManagerGlobal.getWindowManagerService().injectInputAfterTransactionsApplied(event,
-                    InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH,
-                    true /* waitForAnimations */);
+            if (syncBefore) {
+                WindowManagerGlobal.getWindowManagerService()
+                        .syncInputTransactions(true /*waitForAnimations*/);
+            }
+
+            // Direct the injected event into windows owned by the instrumentation target.
+            InputManager.getInstance().injectInputEvent(
+                    event, InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT, Process.myUid());
+
+            if (syncAfter) {
+                WindowManagerGlobal.getWindowManagerService()
+                        .syncInputTransactions(true /*waitForAnimations*/);
+            }
         } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
         }
     }
 
     /**
-     * Dispatch a trackball event. Finished at some point after the recipient has
-     * returned from its event processing, though it may <em>not</em> have
-     * completely finished reacting from the event -- for example, if it needs
-     * to update its display as a result, it may still be in the process of
-     * doing that.
-     * 
+     * Dispatches a trackball event into the currently focused window, and waits for it to be
+     * processed.
+     * <p>
+     * This method blocks until the recipient has finished handling the event. Note that the
+     * recipient may <em>not</em> have completely finished reacting from the event when this method
+     * returns. For example, it may still be in the process of updating its display or UI contents
+     * upon reacting to the injected event.
+     *
      * @param event A motion event describing the trackball action.  (As noted in 
      * {@link MotionEvent#obtain(long, long, int, float, float, int)}, be sure to use 
      * {@link SystemClock#uptimeMillis()} as the timebase.
      */
     public void sendTrackballEventSync(MotionEvent event) {
         validateNotAppThread();
-        if ((event.getSource() & InputDevice.SOURCE_CLASS_TRACKBALL) == 0) {
+        if (!event.isFromSource(InputDevice.SOURCE_CLASS_TRACKBALL)) {
             event.setSource(InputDevice.SOURCE_TRACKBALL);
         }
         InputManager.getInstance().injectInputEvent(event,
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index e016027..5d6e2bd 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -36,7 +36,10 @@
 import android.permission.IPermissionManager;
 import android.util.Log;
 import android.view.IWindowManager;
+import android.view.InputDevice;
 import android.view.InputEvent;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
 import android.view.SurfaceControl;
 import android.view.WindowAnimationFrameStats;
 import android.view.WindowContentFrameStats;
@@ -132,13 +135,36 @@
             throwIfShutdownLocked();
             throwIfNotConnectedLocked();
         }
-        final int mode = (sync) ? InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH
-                : InputManager.INJECT_INPUT_EVENT_MODE_ASYNC;
+
+        final boolean syncTransactionsBefore;
+        final boolean syncTransactionsAfter;
+        if (event instanceof KeyEvent) {
+            KeyEvent keyEvent = (KeyEvent) event;
+            syncTransactionsBefore = keyEvent.getAction() == KeyEvent.ACTION_DOWN;
+            syncTransactionsAfter = keyEvent.getAction() == KeyEvent.ACTION_UP;
+        } else {
+            MotionEvent motionEvent = (MotionEvent) event;
+            syncTransactionsBefore = motionEvent.getAction() == MotionEvent.ACTION_DOWN
+                    || motionEvent.isFromSource(InputDevice.SOURCE_MOUSE);
+            syncTransactionsAfter = motionEvent.getAction() == MotionEvent.ACTION_UP;
+        }
+
         final long identity = Binder.clearCallingIdentity();
         try {
-            return mWindowManager.injectInputAfterTransactionsApplied(event, mode,
-                    waitForAnimations);
+            if (syncTransactionsBefore) {
+                mWindowManager.syncInputTransactions(waitForAnimations);
+            }
+
+            final boolean result = InputManager.getInstance().injectInputEvent(event,
+                    sync ? InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH
+                            : InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
+
+            if (syncTransactionsAfter) {
+                mWindowManager.syncInputTransactions(waitForAnimations);
+            }
+            return result;
         } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 88ffdec..d375a9e 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1682,7 +1682,7 @@
     public @interface ProvisioningConfiguration {}
 
     /**
-     * A String extra holding the provisioning trigger. It could be one of
+     * An int extra holding the provisioning trigger. It could be one of
      * {@link #PROVISIONING_TRIGGER_CLOUD_ENROLLMENT}, {@link #PROVISIONING_TRIGGER_QR_CODE},
      * {@link #PROVISIONING_TRIGGER_MANAGED_ACCOUNT} or {@link
      * #PROVISIONING_TRIGGER_UNSPECIFIED}.
@@ -3298,9 +3298,9 @@
      * Activity action: Starts the device policy management role holder updater.
      *
      * <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_POLICY_MANAGEMENT_ROLE_HOLDER_RECOVERABLE_ERROR} if it encounters a
-     * problem that may be solved by relaunching it again, {@link
+     * intent result to either {@link Activity#RESULT_OK} if the update was successful or not
+     * necessary, {@link #RESULT_UPDATE_DEVICE_POLICY_MANAGEMENT_ROLE_HOLDER_RECOVERABLE_ERROR} if
+     * it encounters a problem that may be solved by relaunching it again, {@link
      * #RESULT_UPDATE_DEVICE_POLICY_MANAGEMENT_ROLE_HOLDER_PROVISIONING_DISABLED} if role holder
      * provisioning is disabled, or {@link
      * #RESULT_UPDATE_DEVICE_POLICY_MANAGEMENT_ROLE_HOLDER_UNRECOVERABLE_ERROR} if it encounters
diff --git a/core/java/android/app/servertransaction/LaunchActivityItem.java b/core/java/android/app/servertransaction/LaunchActivityItem.java
index d7e0951..076dbef 100644
--- a/core/java/android/app/servertransaction/LaunchActivityItem.java
+++ b/core/java/android/app/servertransaction/LaunchActivityItem.java
@@ -72,6 +72,7 @@
     private IBinder mAssistToken;
     private IBinder mShareableActivityToken;
     private boolean mLaunchedFromBubble;
+    private IBinder mTaskFragmentToken;
     /**
      * It is only non-null if the process is the first time to launch activity. It is only an
      * optimization for quick look up of the interface so the field is ignored for comparison.
@@ -95,7 +96,8 @@
         ActivityClientRecord r = new ActivityClientRecord(token, mIntent, mIdent, mInfo,
                 mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor, mState, mPersistentState,
                 mPendingResults, mPendingNewIntents, mActivityOptions, mIsForward, mProfilerInfo,
-                client, mAssistToken, mShareableActivityToken, mLaunchedFromBubble);
+                client, mAssistToken, mShareableActivityToken, mLaunchedFromBubble,
+                mTaskFragmentToken);
         client.handleLaunchActivity(r, pendingActions, null /* customIntent */);
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
     }
@@ -119,7 +121,7 @@
             List<ReferrerIntent> pendingNewIntents, ActivityOptions activityOptions,
             boolean isForward, ProfilerInfo profilerInfo, IBinder assistToken,
             IActivityClientController activityClientController, IBinder shareableActivityToken,
-            boolean launchedFromBubble) {
+            boolean launchedFromBubble, IBinder taskFragmentToken) {
         LaunchActivityItem instance = ObjectPool.obtain(LaunchActivityItem.class);
         if (instance == null) {
             instance = new LaunchActivityItem();
@@ -128,7 +130,7 @@
                 voiceInteractor, procState, state, persistentState, pendingResults,
                 pendingNewIntents, activityOptions, isForward, profilerInfo, assistToken,
                 activityClientController, shareableActivityToken,
-                launchedFromBubble);
+                launchedFromBubble, taskFragmentToken);
 
         return instance;
     }
@@ -136,7 +138,7 @@
     @Override
     public void recycle() {
         setValues(this, null, 0, null, null, null, null, null, null, 0, null, null, null, null,
-                null, false, null, null, null, null, false);
+                null, false, null, null, null, null, false, null);
         ObjectPool.recycle(this);
     }
 
@@ -166,6 +168,7 @@
         dest.writeStrongInterface(mActivityClientController);
         dest.writeStrongBinder(mShareableActivityToken);
         dest.writeBoolean(mLaunchedFromBubble);
+        dest.writeStrongBinder(mTaskFragmentToken);
     }
 
     /** Read from Parcel. */
@@ -184,7 +187,8 @@
                 in.readStrongBinder(),
                 IActivityClientController.Stub.asInterface(in.readStrongBinder()),
                 in.readStrongBinder(),
-                in.readBoolean());
+                in.readBoolean(),
+                in.readStrongBinder());
     }
 
     public static final @NonNull Creator<LaunchActivityItem> CREATOR =
@@ -222,7 +226,8 @@
                 && mIsForward == other.mIsForward
                 && Objects.equals(mProfilerInfo, other.mProfilerInfo)
                 && Objects.equals(mAssistToken, other.mAssistToken)
-                && Objects.equals(mShareableActivityToken, other.mShareableActivityToken);
+                && Objects.equals(mShareableActivityToken, other.mShareableActivityToken)
+                && Objects.equals(mTaskFragmentToken, other.mTaskFragmentToken);
     }
 
     @Override
@@ -244,6 +249,7 @@
         result = 31 * result + Objects.hashCode(mProfilerInfo);
         result = 31 * result + Objects.hashCode(mAssistToken);
         result = 31 * result + Objects.hashCode(mShareableActivityToken);
+        result = 31 * result + Objects.hashCode(mTaskFragmentToken);
         return result;
     }
 
@@ -291,7 +297,7 @@
             List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
             ActivityOptions activityOptions, boolean isForward, ProfilerInfo profilerInfo,
             IBinder assistToken, IActivityClientController activityClientController,
-            IBinder shareableActivityToken, boolean launchedFromBubble) {
+            IBinder shareableActivityToken, boolean launchedFromBubble, IBinder taskFragmentToken) {
         instance.mIntent = intent;
         instance.mIdent = ident;
         instance.mInfo = info;
@@ -312,5 +318,6 @@
         instance.mActivityClientController = activityClientController;
         instance.mShareableActivityToken = shareableActivityToken;
         instance.mLaunchedFromBubble = launchedFromBubble;
+        instance.mTaskFragmentToken = taskFragmentToken;
     }
 }
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 5dd68a9..0d258ce 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3054,13 +3054,11 @@
      *
      * @param receiver The BroadcastReceiver to handle the broadcast.
      * @param filter Selects the Intent broadcasts to be received.
-     * @param flags Additional options for the receiver. For apps targeting
-     * {@link android.os.Build.VERSION_CODES#TIRAMISU},
-     *              either {@link #RECEIVER_EXPORTED} or
-     * {@link #RECEIVER_NOT_EXPORTED} must be specified if the receiver isn't being registered
-     *              for <a href="{@docRoot}guide/components/broadcasts#system-broadcasts">system
-     *              broadcasts</a> or an exception will be thrown. If
-     *              {@link #RECEIVER_EXPORTED} is specified, a receiver may additionally
+     * @param flags Additional options for the receiver. In a future release, either
+     * {@link #RECEIVER_EXPORTED} or {@link #RECEIVER_NOT_EXPORTED} must be specified if the
+     *             receiver isn't being registered for <a href="{@docRoot}guide/components
+     *              /broadcasts#system-broadcasts">system broadcasts</a> or an exception will be
+     *              thrown. If {@link #RECEIVER_EXPORTED} is specified, a receiver may additionally
      *              specify {@link #RECEIVER_VISIBLE_TO_INSTANT_APPS}. For a complete list of
      *              system broadcast actions, see the BROADCAST_ACTIONS.TXT file in the
      *              Android SDK. If both {@link #RECEIVER_EXPORTED} and
@@ -3137,13 +3135,11 @@
      *      no permission is required.
      * @param scheduler Handler identifying the thread that will receive
      *      the Intent.  If null, the main thread of the process will be used.
-     * @param flags Additional options for the receiver. For apps targeting
-     * {@link android.os.Build.VERSION_CODES#TIRAMISU},
-     *              either {@link #RECEIVER_EXPORTED} or
-     * {@link #RECEIVER_NOT_EXPORTED} must be specified if the receiver isn't being registered
-     *              for <a href="{@docRoot}guide/components/broadcasts#system-broadcasts">system
-     *              broadcasts</a> or an exception will be thrown. If
-     *              {@link #RECEIVER_EXPORTED} is specified, a receiver may additionally
+     * @param flags Additional options for the receiver. In a future release, either
+     * {@link #RECEIVER_EXPORTED} or {@link #RECEIVER_NOT_EXPORTED} must be specified if the
+     *             receiver isn't being registered for <a href="{@docRoot}guide/components
+     *              /broadcasts#system-broadcasts">system broadcasts</a> or an exception will be
+     *              thrown. If {@link #RECEIVER_EXPORTED} is specified, a receiver may additionally
      *              specify {@link #RECEIVER_VISIBLE_TO_INSTANT_APPS}. For a complete list of
      *              system broadcast actions, see the BROADCAST_ACTIONS.TXT file in the
      *              Android SDK. If both {@link #RECEIVER_EXPORTED} and
@@ -3207,13 +3203,11 @@
      *      no permission is required.
      * @param scheduler Handler identifying the thread that will receive
      *      the Intent. If {@code null}, the main thread of the process will be used.
-     * @param flags Additional options for the receiver. For apps targeting
-     * {@link android.os.Build.VERSION_CODES#TIRAMISU},
-     *              either {@link #RECEIVER_EXPORTED} or
-     * {@link #RECEIVER_NOT_EXPORTED} must be specified if the receiver isn't being registered
-     *              for <a href="{@docRoot}guide/components/broadcasts#system-broadcasts">system
-     *              broadcasts</a> or an exception will be thrown. If
-     *              {@link #RECEIVER_EXPORTED} is specified, a receiver may additionally
+     * @param flags Additional options for the receiver. In a future release, either
+     * {@link #RECEIVER_EXPORTED} or {@link #RECEIVER_NOT_EXPORTED} must be specified if the
+     *             receiver isn't being registered for <a href="{@docRoot}guide/components
+     *              /broadcasts#system-broadcasts">system broadcasts</a> or an exception will be
+     *              thrown. If {@link #RECEIVER_EXPORTED} is specified, a receiver may additionally
      *              specify {@link #RECEIVER_VISIBLE_TO_INSTANT_APPS}. For a complete list of
      *              system broadcast actions, see the BROADCAST_ACTIONS.TXT file in the
      *              Android SDK. If both {@link #RECEIVER_EXPORTED} and
@@ -3282,13 +3276,11 @@
      *      no permission is required.
      * @param scheduler Handler identifying the thread that will receive
      *      the Intent.  If null, the main thread of the process will be used.
-     * @param flags Additional options for the receiver. For apps targeting
-     * {@link android.os.Build.VERSION_CODES#TIRAMISU},
-     *              either {@link #RECEIVER_EXPORTED} or
-     * {@link #RECEIVER_NOT_EXPORTED} must be specified if the receiver isn't being registered
-     *              for <a href="{@docRoot}guide/components/broadcasts#system-broadcasts">system
-     *              broadcasts</a> or an exception will be thrown. If
-     *              {@link #RECEIVER_EXPORTED} is specified, a receiver may additionally
+     * @param flags Additional options for the receiver. In a future release, either
+     * {@link #RECEIVER_EXPORTED} or {@link #RECEIVER_NOT_EXPORTED} must be specified if the
+     *             receiver isn't being registered for <a href="{@docRoot}guide/components
+     *              /broadcasts#system-broadcasts">system broadcasts</a> or an exception will be
+     *              thrown. If {@link #RECEIVER_EXPORTED} is specified, a receiver may additionally
      *              specify {@link #RECEIVER_VISIBLE_TO_INSTANT_APPS}. For a complete list of
      *              system broadcast actions, see the BROADCAST_ACTIONS.TXT file in the
      *              Android SDK. If both {@link #RECEIVER_EXPORTED} and
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index 5680bcd..cb55e30 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -568,7 +568,8 @@
                 }
 
                 ParseResult<Integer> targetResult = FrameworkParsingPackageUtils.computeTargetSdkVersion(
-                        targetVer, targetCode, SDK_CODENAMES, input);
+                        targetVer, targetCode, SDK_CODENAMES, input,
+                        /* allowUnknownCodenames= */ false);
                 if (targetResult.isError()) {
                     return input.error(targetResult);
                 }
diff --git a/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java b/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
index a65b681..6d74b81 100644
--- a/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
@@ -36,6 +36,7 @@
 import android.util.apk.ApkSignatureVerifier;
 
 import com.android.internal.util.ArrayUtils;
+import com.android.modules.utils.build.UnboundedSdkLevel;
 
 import java.security.KeyFactory;
 import java.security.NoSuchAlgorithmException;
@@ -334,8 +335,9 @@
      * If {@code targetCode} is not specified, e.g. the value is {@code null}, then the {@code
      * targetVers} will be returned unmodified.
      * <p>
-     * Otherwise, the behavior varies based on whether the current platform is a pre-release
-     * version, e.g. the {@code platformSdkCodenames} array has length > 0:
+     * When {@code allowUnknownCodenames} is false, the behavior varies based on whether the
+     * current platform is a pre-release version, e.g. the {@code platformSdkCodenames} array has
+     * length > 0:
      * <ul>
      * <li>If this is a pre-release platform and the value specified by
      * {@code targetCode} is contained within the array of allowed pre-release
@@ -343,22 +345,32 @@
      * <li>If this is a released platform, this method will return -1 to
      * indicate that the package is not compatible with this platform.
      * </ul>
+     * <p>
+     * When {@code allowUnknownCodenames} is true, any codename that is not known (presumed to be
+     * a codename announced after the build of the current device) is allowed and this method will
+     * return {@link Build.VERSION_CODES#CUR_DEVELOPMENT}.
      *
-     * @param targetVers           targetSdkVersion number, if specified in the application
-     *                             manifest, or 0 otherwise
-     * @param targetCode           targetSdkVersion code, if specified in the application manifest,
-     *                             or {@code null} otherwise
-     * @param platformSdkCodenames array of allowed pre-release SDK codenames for this platform
+     * @param targetVers            targetSdkVersion number, if specified in the application
+     *                              manifest, or 0 otherwise
+     * @param targetCode            targetSdkVersion code, if specified in the application manifest,
+     *                              or {@code null} otherwise
+     * @param platformSdkCodenames  array of allowed pre-release SDK codenames for this platform
+     * @param allowUnknownCodenames allow unknown codenames, if true this method will accept unknown
+     *                              (presumed to be future) codenames
      * @return the targetSdkVersion to use at runtime if successful
      */
     public static ParseResult<Integer> computeTargetSdkVersion(@IntRange(from = 0) int targetVers,
             @Nullable String targetCode, @NonNull String[] platformSdkCodenames,
-            @NonNull ParseInput input) {
+            @NonNull ParseInput input, boolean allowUnknownCodenames) {
         // If it's a release SDK, return the version number unmodified.
         if (targetCode == null) {
             return input.success(targetVers);
         }
 
+        if (allowUnknownCodenames && UnboundedSdkLevel.isAtMost(targetCode)) {
+            return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT);
+        }
+
         // If it's a pre-release SDK and the codename matches this platform, it
         // definitely targets this SDK.
         if (matchTargetCode(platformSdkCodenames, targetCode)) {
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 01bf49e..dd3ddaf 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -751,11 +751,25 @@
     public static final int SCREEN_WIDTH_DP_UNDEFINED = 0;
 
     /**
-     * The current width of the available screen space, in dp units,
-     * corresponding to
-     * <a href="{@docRoot}guide/topics/resources/providing-resources.html#ScreenWidthQualifier">screen
-     * width</a> resource qualifier.  Set to
+     * The current width of the available screen space in dp units, excluding
+     * the area occupied by screen decorations at the edges of the display.
+     * Corresponds to the
+     * <a href="{@docRoot}guide/topics/resources/providing-resources.html#AvailableWidthHeightQualifier">
+     * available width</a> resource qualifier. Defaults to
      * {@link #SCREEN_WIDTH_DP_UNDEFINED} if no width is specified.
+     *
+     * <p>In multi-window mode, equals the width of the available display area
+     * of the app window, not the available display area of the device screen
+     * (for example, when apps are displayed side by side in split-screen mode
+     * in landscape orientation).
+     *
+     * <p>Differs from {@link android.view.WindowMetrics} by not including
+     * screen decorations in the width measurement and by expressing the
+     * measurement in dp rather than px. Use {@code screenWidthDp} to obtain the
+     * horizontal display area available to the app, excluding the area occupied
+     * by screen decorations. Use {@link android.view.WindowMetrics#getBounds()}
+     * to obtain the width of the display area available to the app, including
+     * the area occupied by screen decorations.
      */
     public int screenWidthDp;
 
@@ -766,11 +780,26 @@
     public static final int SCREEN_HEIGHT_DP_UNDEFINED = 0;
 
     /**
-     * The current height of the available screen space, in dp units,
-     * corresponding to
-     * <a href="{@docRoot}guide/topics/resources/providing-resources.html#ScreenHeightQualifier">screen
-     * height</a> resource qualifier.  Set to
+     * The current height of the available screen space in dp units, excluding
+     * the area occupied by screen decorations at the edges of the display (such
+     * as the status bar, navigation bar, and cutouts). Corresponds to the
+     * <a href="{@docRoot}guide/topics/resources/providing-resources.html#AvailableWidthHeightQualifier">
+     * available height</a> resource qualifier. Defaults to
      * {@link #SCREEN_HEIGHT_DP_UNDEFINED} if no height is specified.
+     *
+     * <p>In multi-window mode, equals the height of the available display area
+     * of the app window, not the available display area of the device screen
+     * (for example, when apps are displayed one above another in split-screen
+     * mode in portrait orientation).
+     *
+     * <p>Differs from {@link android.view.WindowMetrics} by not including
+     * screen decorations in the height measurement and by expressing the
+     * measurement in dp rather than px. Use {@code screenHeightDp} to obtain
+     * the vertical display area available to the app, excluding the area
+     * occupied by screen decorations. Use
+     * {@link android.view.WindowMetrics#getBounds()} to obtain the height of
+     * the display area available to the app, including the area occupied by
+     * screen decorations.
      */
     public int screenHeightDp;
 
diff --git a/core/java/android/hardware/biometrics/BiometricConstants.java b/core/java/android/hardware/biometrics/BiometricConstants.java
index 28046c5..99e4feb 100644
--- a/core/java/android/hardware/biometrics/BiometricConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricConstants.java
@@ -268,4 +268,27 @@
      */
     int BIOMETRIC_SYSTEM_EVENT_EARLY_USER_CANCEL = 1;
 
+    /**
+     * No lockout.
+     * @hide
+     */
+    int BIOMETRIC_LOCKOUT_NONE = 0;
+    /**
+     * The biometric is in a temporary lockout state that will expire after some time.
+     * @hide
+     */
+    int BIOMETRIC_LOCKOUT_TIMED = 1;
+    /**
+     * The biometric is locked out until a reset occurs. Resets are typically triggered by
+     * successfully authenticating via a stronger method than the one that is locked out.
+     * @hide
+     */
+    int BIOMETRIC_LOCKOUT_PERMANENT = 2;
+
+    /**
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({BIOMETRIC_LOCKOUT_NONE, BIOMETRIC_LOCKOUT_TIMED, BIOMETRIC_LOCKOUT_PERMANENT})
+    @interface LockoutMode {}
 }
diff --git a/core/java/android/hardware/biometrics/ParentalControlsUtilsInternal.java b/core/java/android/hardware/biometrics/ParentalControlsUtilsInternal.java
index 4ec6f0d..de93234 100644
--- a/core/java/android/hardware/biometrics/ParentalControlsUtilsInternal.java
+++ b/core/java/android/hardware/biometrics/ParentalControlsUtilsInternal.java
@@ -32,21 +32,33 @@
  */
 public class ParentalControlsUtilsInternal {
 
-    private static final String TEST_ALWAYS_REQUIRE_CONSENT =
-            "android.hardware.biometrics.ParentalControlsUtilsInternal.always_require_consent";
+    private static final String TEST_ALWAYS_REQUIRE_CONSENT_PACKAGE =
+            "android.hardware.biometrics.ParentalControlsUtilsInternal.require_consent_package";
+    private static final String TEST_ALWAYS_REQUIRE_CONSENT_CLASS =
+            "android.hardware.biometrics.ParentalControlsUtilsInternal.require_consent_class";
 
-    public static boolean isTestModeEnabled(@NonNull Context context) {
+    /**
+     * ComponentName of Parent Consent activity for testing Biometric authentication disabled by
+     * Parental Controls.
+     *
+     * <p>Component could be defined by values of {@link #TEST_ALWAYS_REQUIRE_CONSENT_PACKAGE} and
+     * {@link #TEST_ALWAYS_REQUIRE_CONSENT_CLASS} Secure settings.
+     */
+    public static ComponentName getTestComponentName(@NonNull Context context, int userId) {
         if (Build.IS_USERDEBUG || Build.IS_ENG) {
-            return Settings.Secure.getInt(context.getContentResolver(),
-                    TEST_ALWAYS_REQUIRE_CONSENT, 0) != 0;
+            final String pkg = Settings.Secure.getStringForUser(context.getContentResolver(),
+                    TEST_ALWAYS_REQUIRE_CONSENT_PACKAGE, userId);
+            final String cls = Settings.Secure.getStringForUser(context.getContentResolver(),
+                    TEST_ALWAYS_REQUIRE_CONSENT_CLASS, userId);
+            return pkg != null && cls != null ? new ComponentName(pkg, cls) : null;
         }
-        return false;
+        return null;
     }
 
     public static boolean parentConsentRequired(@NonNull Context context,
             @NonNull DevicePolicyManager dpm, @BiometricAuthenticator.Modality int modality,
             @NonNull UserHandle userHandle) {
-        if (isTestModeEnabled(context)) {
+        if (getTestComponentName(context, userHandle.getIdentifier()) != null) {
             return true;
         }
 
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 5df64e3..d94ad3a 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -623,6 +623,11 @@
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
+        if (!mPhysicalCameraSettings.containsKey(mLogicalCameraId)) {
+            throw new IllegalStateException("Physical camera settings map must contain a key for "
+                    + "the logical camera id.");
+        }
+
         int physicalCameraCount = mPhysicalCameraSettings.size();
         dest.writeInt(physicalCameraCount);
         //Logical camera id and settings always come first.
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index b970559..31f3b6a 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -19,6 +19,7 @@
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 import static android.Manifest.permission.MANAGE_BIOMETRIC;
 import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
+import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_NONE;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -676,6 +677,22 @@
      * @hide
      */
     @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+    @BiometricConstants.LockoutMode
+    public int getLockoutModeForUser(int sensorId, int userId) {
+        if (mService != null) {
+            try {
+                return mService.getLockoutModeForUser(sensorId, userId);
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
+        }
+        return BIOMETRIC_LOCKOUT_NONE;
+    }
+
+    /**
+     * @hide
+     */
+    @RequiresPermission(USE_BIOMETRIC_INTERNAL)
     public void addLockoutResetCallback(final LockoutResetCallback callback) {
         if (mService != null) {
             try {
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 6d2cdf3..60c4b52 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -23,6 +23,7 @@
 import static android.Manifest.permission.USE_BIOMETRIC;
 import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
 import static android.Manifest.permission.USE_FINGERPRINT;
+import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_NONE;
 import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_POWER_BUTTON;
 
 import static com.android.internal.util.FrameworkStatsLog.AUTH_DEPRECATED_APIUSED__DEPRECATED_API__API_FINGERPRINT_MANAGER_AUTHENTICATE;
@@ -41,6 +42,7 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricFingerprintConstants;
 import android.hardware.biometrics.BiometricPrompt;
 import android.hardware.biometrics.BiometricTestSession;
@@ -1097,6 +1099,22 @@
     /**
      * @hide
      */
+    @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+    @BiometricConstants.LockoutMode
+    public int getLockoutModeForUser(int sensorId, int userId) {
+        if (mService != null) {
+            try {
+                return mService.getLockoutModeForUser(sensorId, userId);
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
+        }
+        return BIOMETRIC_LOCKOUT_NONE;
+    }
+
+    /**
+     * @hide
+     */
     public void addLockoutResetCallback(final LockoutResetCallback callback) {
         if (mService != null) {
             try {
diff --git a/core/java/android/hardware/fingerprint/IUdfpsHbmListener.aidl b/core/java/android/hardware/fingerprint/IUdfpsHbmListener.aidl
index f4d22da..9c2aa66 100644
--- a/core/java/android/hardware/fingerprint/IUdfpsHbmListener.aidl
+++ b/core/java/android/hardware/fingerprint/IUdfpsHbmListener.aidl
@@ -24,31 +24,20 @@
  * @hide
  */
 oneway interface IUdfpsHbmListener {
-
-    /** HBM that applies to the whole screen. */
-    const int GLOBAL_HBM = 0;
-
-    /** HBM that only applies to a portion of the screen. */
-    const int LOCAL_HBM = 1;
-
     /**
      * UdfpsController will call this method when the HBM is enabled.
      *
-     * @param hbmType The type of HBM that was enabled. See
-     *        {@link com.android.systemui.biometrics.UdfpsHbmTypes}.
      * @param displayId The displayId for which the HBM is enabled. See
      *        {@link android.view.Display#getDisplayId()}.
      */
-    void onHbmEnabled(int hbmType, int displayId);
+    void onHbmEnabled(int displayId);
 
     /**
      * UdfpsController will call this method when the HBM is disabled.
      *
-     * @param hbmType The type of HBM that was disabled. See
-     *        {@link com.android.systemui.biometrics.UdfpsHbmTypes}.
      * @param displayId The displayId for which the HBM is disabled. See
      *        {@link android.view.Display#getDisplayId()}.
      */
-    void onHbmDisabled(int hbmType, int displayId);
+    void onHbmDisabled(int displayId);
 }
 
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index e1ffd4a..57e84bd 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -57,10 +57,11 @@
     // Temporarily changes the pointer speed.
     void tryPointerSpeed(int speed);
 
-    // Injects an input event into the system.  To inject into windows owned by other
-    // applications, the caller must have the INJECT_EVENTS permission.
+    // Injects an input event into the system. The caller must have the INJECT_EVENTS permission.
+    // The caller can target windows owned by a certain UID by providing a valid UID, or by
+    // providing {@link android.os.Process#INVALID_UID} to target all windows.
     @UnsupportedAppUsage
-    boolean injectInputEvent(in InputEvent ev, int mode);
+    boolean injectInputEvent(in InputEvent ev, int mode, int targetUid);
 
     VerifiedInputEvent verifyInputEvent(in InputEvent ev);
 
diff --git a/core/java/android/hardware/input/InputDeviceSensorManager.java b/core/java/android/hardware/input/InputDeviceSensorManager.java
index 89db857..8a40d00 100644
--- a/core/java/android/hardware/input/InputDeviceSensorManager.java
+++ b/core/java/android/hardware/input/InputDeviceSensorManager.java
@@ -98,7 +98,7 @@
      */
     private void updateInputDeviceSensorInfoLocked(int deviceId) {
         final InputDevice inputDevice = InputDevice.getDevice(deviceId);
-        if (inputDevice.hasSensor()) {
+        if (inputDevice != null && inputDevice.hasSensor()) {
             final InputSensorInfo[] sensorInfos =
                     mInputManager.getSensorList(deviceId);
             populateSensorsForInputDeviceLocked(deviceId, sensorInfos);
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index c38a847..0bcabdd 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -45,6 +45,7 @@
 import android.os.InputEventInjectionSync;
 import android.os.Looper;
 import android.os.Message;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.ServiceManager.ServiceNotFoundException;
@@ -1107,14 +1108,58 @@
         }
     }
 
+    /**
+     * Injects an input event into the event system, targeting windows owned by the provided uid.
+     *
+     * If a valid targetUid is provided, the system will only consider injecting the input event
+     * into windows owned by the provided uid. If the input event is targeted at a window that is
+     * not owned by the provided uid, input injection will fail and a RemoteException will be
+     * thrown.
+     *
+     * The synchronization mode determines whether the method blocks while waiting for
+     * input injection to proceed.
+     * <p>
+     * Requires the {@link android.Manifest.permission.INJECT_EVENTS} permission.
+     * </p><p>
+     * Make sure you correctly set the event time and input source of the event
+     * before calling this method.
+     * </p>
+     *
+     * @param event The event to inject.
+     * @param mode The synchronization mode.  One of:
+     * {@link android.os.InputEventInjectionSync.NONE},
+     * {@link android.os.InputEventInjectionSync.WAIT_FOR_RESULT}, or
+     * {@link android.os.InputEventInjectionSync.WAIT_FOR_FINISHED}.
+     * @param targetUid The uid to target, or {@link android.os.Process#INVALID_UID} to target all
+     *                 windows.
+     * @return True if input event injection succeeded.
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.INJECT_EVENTS)
+    public boolean injectInputEvent(InputEvent event, int mode, int targetUid) {
+        if (event == null) {
+            throw new IllegalArgumentException("event must not be null");
+        }
+        if (mode != InputEventInjectionSync.NONE
+                && mode != InputEventInjectionSync.WAIT_FOR_FINISHED
+                && mode != InputEventInjectionSync.WAIT_FOR_RESULT) {
+            throw new IllegalArgumentException("mode is invalid");
+        }
+
+        try {
+            return mIm.injectInputEvent(event, mode, targetUid);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
 
     /**
      * Injects an input event into the event system on behalf of an application.
      * The synchronization mode determines whether the method blocks while waiting for
      * input injection to proceed.
      * <p>
-     * Requires {@link android.Manifest.permission.INJECT_EVENTS} to inject into
-     * windows that are owned by other applications.
+     * Requires the {@link android.Manifest.permission.INJECT_EVENTS} permission.
      * </p><p>
      * Make sure you correctly set the event time and input source of the event
      * before calling this method.
@@ -1129,22 +1174,10 @@
      *
      * @hide
      */
+    @RequiresPermission(Manifest.permission.INJECT_EVENTS)
     @UnsupportedAppUsage
     public boolean injectInputEvent(InputEvent event, int mode) {
-        if (event == null) {
-            throw new IllegalArgumentException("event must not be null");
-        }
-        if (mode != InputEventInjectionSync.NONE
-                && mode != InputEventInjectionSync.WAIT_FOR_FINISHED
-                && mode != InputEventInjectionSync.WAIT_FOR_RESULT) {
-            throw new IllegalArgumentException("mode is invalid");
-        }
-
-        try {
-            return mIm.injectInputEvent(event, mode);
-        } catch (RemoteException ex) {
-            throw ex.rethrowFromSystemServer();
-        }
+        return injectInputEvent(event, mode, Process.INVALID_UID);
     }
 
     /**
@@ -1421,8 +1454,11 @@
             }
 
             mInputDevices = new SparseArray<InputDevice>();
-            for (int i = 0; i < ids.length; i++) {
-                mInputDevices.put(ids[i], null);
+            // TODO(b/223905476): remove when the rootcause is fixed.
+            if (ids != null) {
+                for (int i = 0; i < ids.length; i++) {
+                    mInputDevices.put(ids[i], null);
+                }
             }
         }
     }
diff --git a/core/java/android/hardware/input/InputManagerInternal.java b/core/java/android/hardware/input/InputManagerInternal.java
index 3f20002..b37c27c 100644
--- a/core/java/android/hardware/input/InputManagerInternal.java
+++ b/core/java/android/hardware/input/InputManagerInternal.java
@@ -21,7 +21,6 @@
 import android.hardware.display.DisplayViewport;
 import android.os.IBinder;
 import android.view.InputChannel;
-import android.view.InputEvent;
 
 import java.util.List;
 
@@ -31,14 +30,6 @@
  * @hide Only for use within the system server.
  */
 public abstract class InputManagerInternal {
-    /**
-     * Inject an input event.
-     *
-     * @param event The InputEvent to inject
-     * @param mode Synchronous or asynchronous mode
-     * @return True if injection has succeeded
-     */
-    public abstract boolean injectInputEvent(InputEvent event, int mode);
 
     /**
      * Called by the display manager to set information about the displays as needed
diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl
index b617e05..5a44244 100644
--- a/core/java/android/hardware/usb/IUsbManager.aidl
+++ b/core/java/android/hardware/usb/IUsbManager.aidl
@@ -137,7 +137,7 @@
     void resetUsbGadget();
 
     /* Resets the USB port. */
-    boolean resetUsbPort(in String portId, int operationId, in IUsbOperationInternal callback);
+    void resetUsbPort(in String portId, int operationId, in IUsbOperationInternal callback);
 
     /* Set USB data on or off */
     boolean enableUsbData(in String portId, boolean enable, int operationId, in IUsbOperationInternal callback);
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index 4fb6abd..2c38f70 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -1360,11 +1360,11 @@
      * @hide
      */
     @RequiresPermission(Manifest.permission.MANAGE_USB)
-    boolean resetUsbPort(@NonNull UsbPort port, int operationId,
+    void resetUsbPort(@NonNull UsbPort port, int operationId,
             IUsbOperationInternal callback) {
         Objects.requireNonNull(port, "resetUsbPort: port must not be null. opId:" + operationId);
         try {
-            return mService.resetUsbPort(port.getId(), operationId, callback);
+            mService.resetUsbPort(port.getId(), operationId, callback);
         } catch (RemoteException e) {
             Log.e(TAG, "resetUsbPort: failed. ", e);
             try {
diff --git a/core/java/android/hardware/usb/UsbOperationInternal.java b/core/java/android/hardware/usb/UsbOperationInternal.java
index 9bc2b38..5b6dbbf 100644
--- a/core/java/android/hardware/usb/UsbOperationInternal.java
+++ b/core/java/android/hardware/usb/UsbOperationInternal.java
@@ -15,6 +15,7 @@
  */
 package android.hardware.usb;
 
+import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.hardware.usb.IUsbOperationInternal;
 import android.hardware.usb.UsbPort;
@@ -24,7 +25,9 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.concurrent.locks.Condition;
 import java.util.concurrent.locks.ReentrantLock;
+import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
 /**
  * UsbOperationInternal allows UsbPort to support both synchronous and
  * asynchronous function irrespective of whether the underlying hal
@@ -39,6 +42,10 @@
     private final String mId;
     // True implies operation did not timeout.
     private boolean mOperationComplete;
+    private boolean mAsynchronous = false;
+    private Executor mExecutor;
+    private Consumer<Integer> mConsumer;
+    private int mResult = 0;
     private @UsbOperationStatus int mStatus;
     final ReentrantLock mLock = new ReentrantLock();
     final Condition mOperationWait  = mLock.newCondition();
@@ -78,6 +85,15 @@
     @Retention(RetentionPolicy.SOURCE)
     @interface UsbOperationStatus{}
 
+    UsbOperationInternal(int operationID, String id,
+        Executor executor, Consumer<Integer> consumer) {
+        this.mOperationID = operationID;
+        this.mId = id;
+        this.mExecutor = executor;
+        this.mConsumer = consumer;
+        this.mAsynchronous = true;
+    }
+
     UsbOperationInternal(int operationID, String id) {
         this.mOperationID = operationID;
         this.mId = id;
@@ -94,7 +110,27 @@
             mOperationComplete = true;
             mStatus = status;
             Log.i(TAG, "Port:" + mId + " opID:" + mOperationID + " status:" + mStatus);
-            mOperationWait.signal();
+            if (mAsynchronous) {
+                switch (mStatus) {
+                    case USB_OPERATION_SUCCESS:
+                        mResult = UsbPort.RESET_USB_PORT_SUCCESS;
+                        break;
+                    case USB_OPERATION_ERROR_INTERNAL:
+                        mResult = UsbPort.RESET_USB_PORT_ERROR_INTERNAL;
+                        break;
+                    case USB_OPERATION_ERROR_NOT_SUPPORTED:
+                        mResult = UsbPort.RESET_USB_PORT_ERROR_NOT_SUPPORTED;
+                        break;
+                    case USB_OPERATION_ERROR_PORT_MISMATCH:
+                        mResult = UsbPort.RESET_USB_PORT_ERROR_PORT_MISMATCH;
+                        break;
+                    default:
+                        mResult = UsbPort.RESET_USB_PORT_ERROR_OTHER;
+                }
+                mExecutor.execute(() -> mConsumer.accept(mResult));
+            } else {
+                mOperationWait.signal();
+            }
         } finally {
             mLock.unlock();
         }
diff --git a/core/java/android/hardware/usb/UsbPort.java b/core/java/android/hardware/usb/UsbPort.java
index 7b695e7..7c5a4c6 100644
--- a/core/java/android/hardware/usb/UsbPort.java
+++ b/core/java/android/hardware/usb/UsbPort.java
@@ -48,6 +48,7 @@
 import static android.hardware.usb.UsbPortStatus.DATA_STATUS_DISABLED_DEBUG;
 
 import android.Manifest;
+import android.annotation.CallbackExecutor;
 import android.annotation.CheckResult;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -65,6 +66,8 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 
 /**
  * Represents a physical USB port and describes its characteristics.
@@ -128,9 +131,6 @@
     @Retention(RetentionPolicy.SOURCE)
     @interface EnableUsbDataStatus{}
 
-    @Retention(RetentionPolicy.SOURCE)
-    @interface ResetUsbPortStatus{}
-
     /**
      * The {@link #enableLimitPowerTransfer} request was successfully completed.
      */
@@ -197,6 +197,42 @@
      */
     public static final int ENABLE_USB_DATA_WHILE_DOCKED_ERROR_OTHER = 5;
 
+    /**
+     * The {@link #resetUsbPort} request was successfully completed.
+     */
+    public static final int RESET_USB_PORT_SUCCESS = 0;
+
+    /**
+     * The {@link #resetUsbPort} request failed due to internal error.
+     */
+    public static final int RESET_USB_PORT_ERROR_INTERNAL = 1;
+
+    /**
+     * The {@link #resetUsbPort} request failed as it's not supported.
+     */
+    public static final int RESET_USB_PORT_ERROR_NOT_SUPPORTED = 2;
+
+    /**
+     * The {@link #resetUsbPort} request failed as port id mismatched.
+     */
+    public static final int RESET_USB_PORT_ERROR_PORT_MISMATCH = 3;
+
+    /**
+     * The {@link #resetUsbPort} request failed due to other reasons.
+     */
+    public static final int RESET_USB_PORT_ERROR_OTHER = 4;
+
+    /** @hide */
+    @IntDef(prefix = { "RESET_USB_PORT_" }, value = {
+            RESET_USB_PORT_SUCCESS,
+            RESET_USB_PORT_ERROR_INTERNAL,
+            RESET_USB_PORT_ERROR_NOT_SUPPORTED,
+            RESET_USB_PORT_ERROR_PORT_MISMATCH,
+            RESET_USB_PORT_ERROR_OTHER
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface ResetUsbPortStatus{}
+
     /** @hide */
     @IntDef(prefix = { "ENABLE_USB_DATA_WHILE_DOCKED_" }, value = {
             ENABLE_USB_DATA_WHILE_DOCKED_SUCCESS,
@@ -324,38 +360,29 @@
     /**
      * Reset Usb data on the port.
      *
-     * @return       {@link #ENABLE_USB_DATA_SUCCESS} when request completes successfully or
-     *               {@link #ENABLE_USB_DATA_ERROR_INTERNAL} when request fails due to internal
-     *               error or
-     *               {@link ENABLE_USB_DATA_ERROR_NOT_SUPPORTED} when not supported or
-     *               {@link ENABLE_USB_DATA_ERROR_PORT_MISMATCH} when request fails due to port id
-     *               mismatch or
-     *               {@link ENABLE_USB_DATA_ERROR_OTHER} when fails due to other reasons.
+     * @param executor Executor for the callback.
+     * @param consumer A consumer that consumes the reset result.
+     *                 {@link #RESET_USB_PORT_SUCCESS} when request completes
+     *                 successfully or
+     *                 {@link #RESET_USB_PORT_ERROR_INTERNAL} when request
+     *                 fails due to internal error or
+     *                 {@link RESET_USB_PORT_ERROR_NOT_SUPPORTED} when not
+     *                 supported or
+     *                 {@link RESET_USB_PORT_ERROR_PORT_MISMATCH} when request
+     *                 fails due to port id mismatch or
+     *                 {@link RESET_USB_PORT_ERROR_OTHER} when fails due to
+     *                  other reasons.
      */
     @CheckResult
     @RequiresPermission(Manifest.permission.MANAGE_USB)
-    public @ResetUsbPortStatus int resetUsbPort() {
+    public void resetUsbPort(@NonNull @CallbackExecutor Executor executor,
+            @NonNull @ResetUsbPortStatus Consumer<Integer> consumer) {
         // UID is added To minimize operationID overlap between two different packages.
         int operationId = sUsbOperationCount.incrementAndGet() + Binder.getCallingUid();
-        Log.i(TAG, "resetUsbData opId:" + operationId);
+        Log.i(TAG, "resetUsbPort opId:" + operationId);
         UsbOperationInternal opCallback =
-                new UsbOperationInternal(operationId, mId);
-        if (mUsbManager.resetUsbPort(this, operationId, opCallback) == true) {
-            opCallback.waitForOperationComplete();
-        }
-        int result = opCallback.getStatus();
-        switch (result) {
-            case USB_OPERATION_SUCCESS:
-                return ENABLE_USB_DATA_SUCCESS;
-            case USB_OPERATION_ERROR_INTERNAL:
-                return ENABLE_USB_DATA_ERROR_INTERNAL;
-            case USB_OPERATION_ERROR_NOT_SUPPORTED:
-                return ENABLE_USB_DATA_ERROR_NOT_SUPPORTED;
-            case USB_OPERATION_ERROR_PORT_MISMATCH:
-                return ENABLE_USB_DATA_ERROR_PORT_MISMATCH;
-            default:
-                return ENABLE_USB_DATA_ERROR_OTHER;
-        }
+                new UsbOperationInternal(operationId, mId, executor, consumer);
+        mUsbManager.resetUsbPort(this, operationId, opCallback);
     }
 
     /**
diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java
index 03d1151..bd6c4e1 100644
--- a/core/java/android/inputmethodservice/NavigationBarController.java
+++ b/core/java/android/inputmethodservice/NavigationBarController.java
@@ -151,6 +151,8 @@
 
         private boolean mDrawLegacyNavigationBarBackground;
 
+        private final Rect mTempRect = new Rect();
+
         Impl(@NonNull InputMethodService inputMethodService) {
             mService = inputMethodService;
         }
@@ -281,13 +283,12 @@
                         touchableRegion.set(originalInsets.touchableRegion);
                         break;
                 }
-                final Rect navBarRect = new Rect(decor.getLeft(),
-                        decor.getBottom() - systemInsets.bottom,
+                mTempRect.set(decor.getLeft(), decor.getBottom() - systemInsets.bottom,
                         decor.getRight(), decor.getBottom());
                 if (touchableRegion == null) {
-                    touchableRegion = new Region(navBarRect);
+                    touchableRegion = new Region(mTempRect);
                 } else {
-                    touchableRegion.union(navBarRect);
+                    touchableRegion.union(mTempRect);
                 }
 
                 dest.touchableRegion.set(touchableRegion);
diff --git a/core/java/android/net/TEST_MAPPING b/core/java/android/net/TEST_MAPPING
index a379c33..3df5616 100644
--- a/core/java/android/net/TEST_MAPPING
+++ b/core/java/android/net/TEST_MAPPING
@@ -17,7 +17,7 @@
       "path": "frameworks/opt/net/wifi"
     }
   ],
-  "postsubmit": [
+  "presubmit": [
     {
       "name": "FrameworksCoreTests",
       "options": [
diff --git a/core/java/android/os/SystemProperties.java b/core/java/android/os/SystemProperties.java
index 82d4443..aa283a2 100644
--- a/core/java/android/os/SystemProperties.java
+++ b/core/java/android/os/SystemProperties.java
@@ -226,9 +226,10 @@
      */
     @UnsupportedAppUsage
     public static void set(@NonNull String key, @Nullable String val) {
-        if (val != null && !key.startsWith("ro.") && val.length() > PROP_VALUE_MAX) {
+        if (val != null && !key.startsWith("ro.") && val.getBytes(StandardCharsets.UTF_8).length
+                > PROP_VALUE_MAX) {
             throw new IllegalArgumentException("value of system property '" + key
-                    + "' is longer than " + PROP_VALUE_MAX + " characters: " + val);
+                    + "' is longer than " + PROP_VALUE_MAX + " bytes: " + val);
         }
         if (TRACK_KEY_ACCESS) onKeyAccess(key);
         native_set(key, val);
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index c4cb319..a64e63e 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -300,6 +300,10 @@
      * When it is set by any of these owners, it prevents all users from using
      * Wi-Fi tethering. Other forms of tethering are not affected.
      *
+     * This user restriction disables only Wi-Fi tethering.
+     * Use {@link #DISALLOW_CONFIG_TETHERING} to limit all forms of tethering.
+     * When {@link #DISALLOW_CONFIG_TETHERING} is set, this user restriction becomes obsolete.
+     *
      * <p>The default value is <code>false</code>.
      *
      * <p>Key for user restrictions.
@@ -735,7 +739,7 @@
     public static final String DISALLOW_CONFIG_DATE_TIME = "no_config_date_time";
 
     /**
-     * Specifies if a user is disallowed from configuring Tethering and portable hotspots
+     * Specifies if a user is disallowed from using and configuring Tethering and portable hotspots
      * via Settings.
      *
      * <p>This restriction can only be set by a device owner, a profile owner on the primary
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 4731504..d25e456 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -915,7 +915,8 @@
      * @param name        The name of the property to create or update.
      * @param value       The value to store for the property.
      * @param makeDefault Whether to make the new value the default one.
-     * @return True if the value was set, false if the storage implementation throws errors.
+     * @return {@code true} if the value was set, {@code false} if the storage implementation throws
+     * errors.
      * @hide
      * @see #resetToDefaults(int, String).
      */
@@ -939,7 +940,7 @@
      *
      * @param properties the complete set of properties to set for a specific namespace.
      * @throws BadConfigException if the provided properties are banned by RescueParty.
-     * @return True if the values were set, false otherwise.
+     * @return {@code true} if the values were set, {@code false} otherwise.
      * @hide
      */
     @SystemApi
@@ -955,8 +956,8 @@
      *
      * @param namespace   The namespace containing the property to delete.
      * @param name        The name of the property to delete.
-     * @return True if the property was deleted or it did not exist in the first place.
-     * False if the storage implementation throws errors.
+     * @return {@code true} if the property was deleted or it did not exist in the first place.
+     * Return {@code false} if the storage implementation throws errors.
      * @hide
      */
     @SystemApi
diff --git a/core/java/android/service/carrier/OWNERS b/core/java/android/service/carrier/OWNERS
index d768ef4..447cd51 100644
--- a/core/java/android/service/carrier/OWNERS
+++ b/core/java/android/service/carrier/OWNERS
@@ -1,5 +1,3 @@
-# Bug component: 20868
+set noparent
 
-rgreenwalt@google.com
-tgunn@google.com
-fionaxu@google.com
+file:platform/frameworks/base:/telephony/OWNERS
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 001707d..d4f8a3b 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -1038,14 +1038,14 @@
         }
         mFinished = true;
 
+        mOverlayConnection.unbind(this);
+
         if (mDreamToken == null) {
             Slog.w(mTag, "Finish was called before the dream was attached.");
             stopSelf();
             return;
         }
 
-        mOverlayConnection.unbind(this);
-
         try {
             // finishSelf will unbind the dream controller from the dream service. This will
             // trigger DreamService.this.onDestroy and DreamService.this will die.
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index dd0ac2e..5131e4e 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -1632,6 +1632,7 @@
             float finalXOffsetStep = xOffsetStep;
             float finalXOffset = xOffset;
             mHandler.post(() -> {
+                Trace.beginSection("WallpaperService#processLocalColors");
                 resetWindowPages();
                 int xPage = xCurrentPage;
                 EngineWindowPage current;
@@ -1662,6 +1663,7 @@
                 }
                 current = mWindowPages[xPage];
                 updatePage(current, xPage, xPages, finalXOffsetStep);
+                Trace.endSection();
             });
         }
 
@@ -1734,6 +1736,7 @@
         private void updatePageColors(EngineWindowPage page, int pageIndx, int numPages,
                 float xOffsetStep) {
             if (page.getBitmap() == null) return;
+            Trace.beginSection("WallpaperService#updatePageColors");
             if (DEBUG) {
                 Log.d(TAG, "updatePageColorsLocked for page " + pageIndx + " with areas "
                         + page.getAreas().size() + " and bitmap size of "
@@ -1779,6 +1782,7 @@
                     }
                 }
             }
+            Trace.endSection();
         }
 
         private RectF generateSubRect(RectF in, int pageInx, int numPages) {
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index 28f5c21..7ac6ae1 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -350,6 +350,53 @@
         return ret;
     }
 
+
+    /**
+     * Returns the longest prefix of a string for which the UTF-8 encoding fits into the given
+     * number of bytes, with the additional guarantee that the string is not truncated in the middle
+     * of a valid surrogate pair.
+     *
+     * <p>Unpaired surrogates are counted as taking 3 bytes of storage. However, a subsequent
+     * attempt to actually encode a string containing unpaired surrogates is likely to be rejected
+     * by the UTF-8 implementation.
+     *
+     * (copied from google/thirdparty)
+     *
+     * @param str a string
+     * @param maxbytes the maximum number of UTF-8 encoded bytes
+     * @return the beginning of the string, so that it uses at most maxbytes bytes in UTF-8
+     * @throws IndexOutOfBoundsException if maxbytes is negative
+     *
+     * @hide
+     */
+    public static String truncateStringForUtf8Storage(String str, int maxbytes) {
+        if (maxbytes < 0) {
+            throw new IndexOutOfBoundsException();
+        }
+
+        int bytes = 0;
+        for (int i = 0, len = str.length(); i < len; i++) {
+            char c = str.charAt(i);
+            if (c < 0x80) {
+                bytes += 1;
+            } else if (c < 0x800) {
+                bytes += 2;
+            } else if (c < Character.MIN_SURROGATE
+                    || c > Character.MAX_SURROGATE
+                    || str.codePointAt(i) < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
+                bytes += 3;
+            } else {
+                bytes += 4;
+                i += (bytes > maxbytes) ? 0 : 1;
+            }
+            if (bytes > maxbytes) {
+                return str.substring(0, i);
+            }
+        }
+        return str;
+    }
+
+
     /**
      * Returns a string containing the tokens joined by delimiters.
      *
diff --git a/core/java/android/util/NtpTrustedTime.java b/core/java/android/util/NtpTrustedTime.java
index 01a037a..4e7b3a5 100644
--- a/core/java/android/util/NtpTrustedTime.java
+++ b/core/java/android/util/NtpTrustedTime.java
@@ -193,6 +193,16 @@
             }
             final Network network = connectivityManager.getActiveNetwork();
             final NetworkInfo ni = connectivityManager.getNetworkInfo(network);
+
+            // This connectivity check is to avoid performing a DNS lookup for the time server on a
+            // unconnected network. There are races to obtain time in Android when connectivity
+            // changes, which means that forceRefresh() can be called by various components before
+            // the network is actually available. This led in the past to DNS lookup failures being
+            // cached (~2 seconds) thereby preventing the device successfully making an NTP request
+            // when connectivity had actually been established.
+            // A side effect of check is that tests that run a fake NTP server on the device itself
+            // will only be able to use it if the active network is connected, even though loopback
+            // addresses are actually reachable.
             if (ni == null || !ni.isConnected()) {
                 if (LOGD) Log.d(TAG, "forceRefresh: no connectivity");
                 return false;
diff --git a/core/java/android/util/SparseSetArray.java b/core/java/android/util/SparseSetArray.java
index eb53b42..f5025f7 100644
--- a/core/java/android/util/SparseSetArray.java
+++ b/core/java/android/util/SparseSetArray.java
@@ -21,26 +21,9 @@
  * @hide
  */
 public class SparseSetArray<T> {
-    private final SparseArray<ArraySet<T>> mData;
+    private final SparseArray<ArraySet<T>> mData = new SparseArray<>();
 
     public SparseSetArray() {
-        mData = new SparseArray<>();
-    }
-
-    /**
-     * Copy constructor
-     */
-    public SparseSetArray(SparseSetArray<T> src) {
-        final int arraySize = src.size();
-        mData = new SparseArray<>(arraySize);
-        for (int i = 0; i < arraySize; i++) {
-            final int key = src.keyAt(i);
-            final ArraySet<T> set = src.get(key);
-            final int setSize = set.size();
-            for (int j = 0; j < setSize; j++) {
-                add(key, set.valueAt(j));
-            }
-        }
     }
 
     /**
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 5f0098c..0c4d9bf 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -1584,10 +1584,10 @@
             return false;
         }
         final Configuration config = mResources.getConfiguration();
-        // TODO(b/179308296) Temporarily - never report max bounds to only Launcher if the feature
-        // is disabled.
+        // TODO(b/179308296) Temporarily exclude Launcher from being given max bounds, by checking
+        // if the caller is the recents component.
         return config != null && !config.windowConfiguration.getMaxBounds().isEmpty()
-                && (mDisplayInfo.shouldConstrainMetricsForLauncher || !isRecentsComponent());
+                && !isRecentsComponent();
     }
 
     /**
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 6917d66..9264d2e 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -306,13 +306,6 @@
     public float brightnessDefault;
 
     /**
-     * @hide
-     * True if Display#getRealSize and getRealMetrics should be constrained for Launcher, false
-     * otherwise.
-     */
-    public boolean shouldConstrainMetricsForLauncher = false;
-
-    /**
      * The {@link RoundedCorners} if present, otherwise {@code null}.
      */
     @Nullable
@@ -395,7 +388,6 @@
                 && brightnessMaximum == other.brightnessMaximum
                 && brightnessDefault == other.brightnessDefault
                 && Objects.equals(roundedCorners, other.roundedCorners)
-                && shouldConstrainMetricsForLauncher == other.shouldConstrainMetricsForLauncher
                 && installOrientation == other.installOrientation;
     }
 
@@ -447,7 +439,6 @@
         brightnessMaximum = other.brightnessMaximum;
         brightnessDefault = other.brightnessDefault;
         roundedCorners = other.roundedCorners;
-        shouldConstrainMetricsForLauncher = other.shouldConstrainMetricsForLauncher;
         installOrientation = other.installOrientation;
     }
 
@@ -505,7 +496,6 @@
         for (int i = 0; i < numUserDisabledFormats; i++) {
             userDisabledHdrTypes[i] = source.readInt();
         }
-        shouldConstrainMetricsForLauncher = source.readBoolean();
         installOrientation = source.readInt();
     }
 
@@ -561,7 +551,6 @@
         for (int i = 0; i < userDisabledHdrTypes.length; i++) {
             dest.writeInt(userDisabledHdrTypes[i]);
         }
-        dest.writeBoolean(shouldConstrainMetricsForLauncher);
         dest.writeInt(installOrientation);
     }
 
@@ -817,8 +806,6 @@
         sb.append(brightnessMaximum);
         sb.append(", brightnessDefault ");
         sb.append(brightnessDefault);
-        sb.append(", shouldConstrainMetricsForLauncher ");
-        sb.append(shouldConstrainMetricsForLauncher);
         sb.append(", installOrientation ");
         sb.append(Surface.rotationToString(installOrientation));
         sb.append("}");
diff --git a/core/java/android/view/IDisplayWindowInsetsController.aidl b/core/java/android/view/IDisplayWindowInsetsController.aidl
index a0d4a65..f4a0dfa 100644
--- a/core/java/android/view/IDisplayWindowInsetsController.aidl
+++ b/core/java/android/view/IDisplayWindowInsetsController.aidl
@@ -18,6 +18,7 @@
 
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
+import android.view.InsetsVisibilities;
 
 /**
  * Singular controller of insets to use when there isn't another obvious controller available.
@@ -30,8 +31,9 @@
      * Called when top focused window changes to determine whether or not to take over insets
      * control. Won't be called if config_remoteInsetsControllerControlsSystemBars is false.
      * @param packageName: Passes the top package name
+     * @param requestedVisibilities The insets visibilities requested by the focussed window.
      */
-    void topFocusedWindowChanged(String packageName);
+    void topFocusedWindowChanged(String packageName, in InsetsVisibilities insetsVisibilities);
 
     /**
      * @see IWindow#insetsChanged
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index a35a195..5ce5477 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -677,17 +677,6 @@
     void setDisplayImePolicy(int displayId, int imePolicy);
 
     /**
-     * Waits for transactions to get applied before injecting input, optionally waiting for
-     * animations to complete. This includes waiting for the input windows to get sent to
-     * InputManager.
-     *
-     * This is needed for testing since the system add windows and injects input
-     * quick enough that the windows don't have time to get sent to InputManager.
-     */
-    boolean injectInputAfterTransactionsApplied(in InputEvent ev, int mode,
-            boolean waitForAnimations);
-
-    /**
      * Waits until input information has been sent from WindowManager to native InputManager,
      * optionally waiting for animations to complete.
      *
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index a4841f6..7b6a0d6 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -1310,8 +1310,8 @@
     }
 
     private void cancelAnimation(InsetsAnimationControlRunner control, boolean invokeCallback) {
-        if (DEBUG) Log.d(TAG, String.format("cancelAnimation of types: %d, animType: %d",
-                control.getTypes(), control.getAnimationType()));
+        if (DEBUG) Log.d(TAG, String.format("cancelAnimation of types: %d, animType: %d, host: %s",
+                control.getTypes(), control.getAnimationType(), mHost.getRootViewTitle()));
         if (invokeCallback) {
             control.cancel();
         }
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index 5500fb8..b1b630e 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -432,6 +432,17 @@
             processSourceAsPublicType(source, typeInsetsMap, typeSideMap, typeVisibilityMap,
                     insets, Type.SYSTEM_GESTURES);
         }
+        if (type == Type.CAPTION_BAR) {
+            // Caption should also be gesture and tappable elements. This should not be needed when
+            // the caption is added from the shell, as the shell can add other types at the same
+            // time.
+            processSourceAsPublicType(source, typeInsetsMap, typeSideMap, typeVisibilityMap,
+                    insets, Type.SYSTEM_GESTURES);
+            processSourceAsPublicType(source, typeInsetsMap, typeSideMap, typeVisibilityMap,
+                    insets, Type.MANDATORY_SYSTEM_GESTURES);
+            processSourceAsPublicType(source, typeInsetsMap, typeSideMap, typeVisibilityMap,
+                    insets, Type.TAPPABLE_ELEMENT);
+        }
     }
 
     private void processSourceAsPublicType(InsetsSource source, Insets[] typeInsetsMap,
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index c04b096..ed57136 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -966,13 +966,16 @@
 
                 final boolean redrawNeeded = sizeChanged || creating || hintChanged
                         || (mVisible && !mDrawFinished);
-                final TransactionCallback transactionCallback =
-                        redrawNeeded ? new TransactionCallback() : null;
-                if (redrawNeeded && viewRoot.wasRelayoutRequested() && viewRoot.isInSync()) {
+                boolean shouldSyncBuffer =
+                        redrawNeeded && viewRoot.wasRelayoutRequested() && viewRoot.isInSync();
+                SyncBufferTransactionCallback syncBufferTransactionCallback = null;
+                if (shouldSyncBuffer) {
+                    syncBufferTransactionCallback = new SyncBufferTransactionCallback();
                     mBlastBufferQueue.syncNextTransaction(
                             false /* acquireSingleBuffer */,
-                            transactionCallback::onTransactionReady);
+                            syncBufferTransactionCallback::onTransactionReady);
                 }
+
                 final boolean realSizeChanged = performSurfaceTransaction(viewRoot,
                         translator, creating, sizeChanged, hintChanged, surfaceUpdateTransaction);
 
@@ -1011,7 +1014,18 @@
                             }
                         }
                         if (redrawNeeded) {
-                            redrawNeeded(callbacks, transactionCallback);
+                            if (DEBUG) {
+                                Log.i(TAG, System.identityHashCode(this) + " surfaceRedrawNeeded");
+                            }
+                            if (callbacks == null) {
+                                callbacks = getSurfaceCallbacks();
+                            }
+
+                            if (shouldSyncBuffer) {
+                                handleSyncBufferCallback(callbacks, syncBufferTransactionCallback);
+                            } else {
+                                redrawNeededAsync(callbacks, this::onDrawFinished);
+                            }
                         }
                     }
                 } finally {
@@ -1030,38 +1044,30 @@
         }
     }
 
-    private void redrawNeeded(SurfaceHolder.Callback[] callbacks,
-            @Nullable TransactionCallback transactionCallback) {
-        if (DEBUG) {
-            Log.i(TAG, System.identityHashCode(this) + " surfaceRedrawNeeded");
-        }
-        final SurfaceHolder.Callback[] capturedCallbacks =
-                callbacks == null ? getSurfaceCallbacks() : callbacks;
+    /**
+     * If SV is trying to be part of the VRI sync, we need to add SV to the VRI sync before
+     * invoking the redrawNeeded call to the owner. This is to ensure we can set up the SV in
+     * the sync before the SV owner knows it needs to draw a new frame.
+     * Once the redrawNeeded callback is invoked, we can stop the continuous sync transaction
+     * call which will invoke the syncTransaction callback that contains the buffer. The
+     * code waits until we can retrieve the transaction that contains the buffer before
+     * notifying the syncer that the buffer is ready.
+     */
+    private void handleSyncBufferCallback(SurfaceHolder.Callback[] callbacks,
+            SyncBufferTransactionCallback syncBufferTransactionCallback) {
 
-        ViewRootImpl viewRoot = getViewRootImpl();
-        boolean isVriSync = viewRoot.addToSync(syncBufferCallback ->
-                redrawNeededAsync(capturedCallbacks, () -> {
+        getViewRootImpl().addToSync(syncBufferCallback ->
+                redrawNeededAsync(callbacks, () -> {
+                    Transaction t = null;
                     if (mBlastBufferQueue != null) {
                         mBlastBufferQueue.stopContinuousSyncTransaction();
+                        t = syncBufferTransactionCallback.waitForTransaction();
                     }
 
-                    Transaction t = null;
-                    if (transactionCallback != null && mBlastBufferQueue != null) {
-                        t = transactionCallback.waitForTransaction();
-                    }
-                    // If relayout was requested, then a callback from BBQ will
-                    // be invoked with the sync transaction. onDrawFinished will be
-                    // called in there
                     syncBufferCallback.onBufferReady(t);
                     onDrawFinished();
                 }));
 
-        // If isVriSync, then everything was setup in the addToSync.
-        if (isVriSync) {
-            return;
-        }
-
-        redrawNeededAsync(capturedCallbacks, this::onDrawFinished);
     }
 
     private void redrawNeededAsync(SurfaceHolder.Callback[] callbacks,
@@ -1070,7 +1076,7 @@
         sch.dispatchSurfaceRedrawNeededAsync(mSurfaceHolder, callbacks);
     }
 
-    private static class TransactionCallback {
+    private static class SyncBufferTransactionCallback {
         private final CountDownLatch mCountDownLatch = new CountDownLatch(1);
         private Transaction mTransaction;
 
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 7d823b1..8b9a86b9 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -162,6 +162,7 @@
 import android.widget.Checkable;
 import android.widget.FrameLayout;
 import android.widget.ScrollBarDrawable;
+import android.window.OnBackInvokedDispatcher;
 
 import com.android.internal.R;
 import com.android.internal.util.ArrayUtils;
@@ -12032,7 +12033,7 @@
     @Nullable
     public Rect getHandwritingArea() {
         final ListenerInfo info = mListenerInfo;
-        if (info != null) {
+        if (info != null && info.mHandwritingArea != null) {
             return new Rect(info.mHandwritingArea);
         }
         return null;
@@ -12048,13 +12049,16 @@
     }
 
     /**
-     * Compute the view's coordinate within the surface.
+     * Gets the coordinates of this view in the coordinate space of the
+     * {@link Surface} that contains the view.
      *
-     * <p>Computes the coordinates of this view in its surface. The argument
-     * must be an array of two integers. After the method returns, the array
-     * contains the x and y location in that order.</p>
+     * <p>After the method returns, the argument array contains the x- and
+     * y-coordinates of the view relative to the view's left and top edges,
+     * respectively.
      *
-     * @param location an array of two integers in which to hold the coordinates
+     * @param location A two-element integer array in which the view coordinates
+     *      are stored. The x-coordinate is at index 0; the y-coordinate, at
+     *      index 1.
      */
     public void getLocationInSurface(@NonNull @Size(2) int[] location) {
         getLocationInWindow(location);
@@ -12098,6 +12102,23 @@
         return null;
     }
 
+
+    /**
+     * Walk up the View hierarchy to find the nearest {@link OnBackInvokedDispatcher}.
+     *
+     * @return The {@link OnBackInvokedDispatcher} from this or the nearest
+     * ancestor, or null if this view is both not attached and have no ancestor providing an
+     * {@link OnBackInvokedDispatcher}.
+     */
+    @Nullable
+    public final OnBackInvokedDispatcher findOnBackInvokedDispatcher() {
+        ViewParent parent = getParent();
+        if (parent != null) {
+            return parent.findOnBackInvokedDispatcherForChild(this, this);
+        }
+        return null;
+    }
+
     /**
      * @hide Compute the insets that should be consumed by this view and the ones
      * that should propagate to those under it.
@@ -25553,11 +25574,27 @@
     }
 
     /**
-     * <p>Computes the coordinates of this view on the screen. The argument
-     * must be an array of two integers. After the method returns, the array
-     * contains the x and y location in that order.</p>
+     * Gets the global coordinates of this view. The coordinates are in the
+     * coordinate space of the device screen, irrespective of system decorations
+     * and whether the system is in multi-window mode.
      *
-     * @param outLocation an array of two integers in which to hold the coordinates
+     * <p>In multi-window mode, the global coordinate space encompasses the
+     * entire device screen, ignoring the bounds of the app window. For
+     * example, if the view is in the bottom portion of a horizontal split
+     * screen, the top edge of the screen&mdash;not the top edge of the
+     * window&mdash;is the origin from which the y-coordinate is calculated.
+     *
+     * <p><b>Note:</b> In multiple-screen scenarios, the global coordinate space
+     * is restricted to the screen on which the view is displayed. The
+     * coordinate space does not span multiple screens.
+     *
+     * <p>After the method returns, the argument array contains the x- and
+     * y-coordinates of the view relative to the view's left and top edges,
+     * respectively.
+     *
+     * @param outLocation A two-element integer array in which the view
+     *      coordinates are stored. The x-coordinate is at index 0; the
+     *      y-coordinate, at index 1.
      */
     public void getLocationOnScreen(@Size(2) int[] outLocation) {
         getLocationInWindow(outLocation);
@@ -25570,11 +25607,20 @@
     }
 
     /**
-     * <p>Computes the coordinates of this view in its window. The argument
-     * must be an array of two integers. After the method returns, the array
-     * contains the x and y location in that order.</p>
+     * Gets the coordinates of this view in the coordinate space of the window
+     * that contains the view, irrespective of system decorations.
      *
-     * @param outLocation an array of two integers in which to hold the coordinates
+     * <p>In multi-window mode, the origin of the coordinate space is the
+     * top left corner of the window that contains the view. In full screen
+     * mode, the origin is the top left corner of the device screen.
+     *
+     * <p>After the method returns, the argument array contains the x- and
+     * y-coordinates of the view relative to the view's left and top edges,
+     * respectively.
+     *
+     * @param outLocation A two-element integer array in which the view
+     *      coordinates are stored. The x-coordinate is at index 0; the
+     *      y-coordinate, at index 1.
      */
     public void getLocationInWindow(@Size(2) int[] outLocation) {
         if (outLocation == null || outLocation.length < 2) {
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index fe67232..9f0ad11 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -72,6 +72,7 @@
 import android.view.translation.TranslationCapability;
 import android.view.translation.TranslationSpec.DataFormat;
 import android.view.translation.ViewTranslationRequest;
+import android.window.OnBackInvokedDispatcher;
 
 import com.android.internal.R;
 
@@ -9296,4 +9297,25 @@
                     requests);
         }
     }
+
+    /**
+     * Walk up the View hierarchy to find the nearest {@link OnBackInvokedDispatcher}.
+     *
+     * @return The {@link OnBackInvokedDispatcher} from this or the nearest
+     * ancestor, or null if the view is both not attached and have no ancestor providing an
+     * {@link OnBackInvokedDispatcher}.
+     *
+     * @param child The direct child of this view for which to find a dispatcher.
+     * @param requester The requester that will use the dispatcher. Can be the same as child.
+     */
+    @Nullable
+    @Override
+    public OnBackInvokedDispatcher findOnBackInvokedDispatcherForChild(@NonNull View child,
+            @NonNull View requester) {
+        ViewParent parent = getParent();
+        if (parent != null) {
+            return parent.findOnBackInvokedDispatcherForChild(this, requester);
+        }
+        return null;
+    }
 }
diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java
index 128da31..1020d2e 100644
--- a/core/java/android/view/ViewParent.java
+++ b/core/java/android/view/ViewParent.java
@@ -22,6 +22,7 @@
 import android.graphics.Region;
 import android.os.Bundle;
 import android.view.accessibility.AccessibilityEvent;
+import android.window.OnBackInvokedDispatcher;
 
 /**
  * Defines the responsibilities for a class that will be a parent of a View.
@@ -697,4 +698,20 @@
             getParent().onDescendantUnbufferedRequested();
         }
     }
+
+    /**
+     * Walk up the View hierarchy to find the nearest {@link OnBackInvokedDispatcher}.
+     *
+     * @return The {@link OnBackInvokedDispatcher} from this or the nearest
+     * ancestor, or null if the view is both not attached and have no ancestor providing an
+     * {@link OnBackInvokedDispatcher}.
+     *
+     * @param child The direct child of this view for which to find a dispatcher.
+     * @param requester The requester that will use the dispatcher. Can be the same as child.
+     */
+    @Nullable
+    default OnBackInvokedDispatcher findOnBackInvokedDispatcherForChild(@NonNull View child,
+            @NonNull View requester) {
+        return null;
+    }
 }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 931ae27..c45a4c7 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -197,7 +197,6 @@
 import android.window.ClientWindowFrames;
 import android.window.OnBackInvokedCallback;
 import android.window.OnBackInvokedDispatcher;
-import android.window.OnBackInvokedDispatcherOwner;
 import android.window.SurfaceSyncer;
 import android.window.WindowOnBackInvokedDispatcher;
 
@@ -240,7 +239,7 @@
 @SuppressWarnings({"EmptyCatchBlock", "PointlessBooleanExpression"})
 public final class ViewRootImpl implements ViewParent,
         View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks,
-        AttachedSurfaceControl, OnBackInvokedDispatcherOwner {
+        AttachedSurfaceControl {
     private static final String TAG = "ViewRootImpl";
     private static final boolean DBG = false;
     private static final boolean LOCAL_LOGV = false;
@@ -1743,7 +1742,7 @@
 
         mForceNextWindowRelayout = forceNextWindowRelayout;
         mPendingAlwaysConsumeSystemBars = args.argi2 != 0;
-        mSyncSeqId = args.argi4;
+        mSyncSeqId = args.argi4 > mSyncSeqId ? args.argi4 : mSyncSeqId;
 
         if (msg == MSG_RESIZED_REPORT) {
             reportNextDraw();
@@ -2768,17 +2767,16 @@
             dispatchApplyInsets(host);
         }
 
+        if (mFirst) {
+            // make sure touch mode code executes by setting cached value
+            // to opposite of the added touch mode.
+            mAttachInfo.mInTouchMode = !mAddedTouchMode;
+            ensureTouchModeLocally(mAddedTouchMode);
+        }
+
         boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
         if (layoutRequested) {
-
-            final Resources res = mView.getContext().getResources();
-
-            if (mFirst) {
-                // make sure touch mode code executes by setting cached value
-                // to opposite of the added touch mode.
-                mAttachInfo.mInTouchMode = !mAddedTouchMode;
-                ensureTouchModeLocally(mAddedTouchMode);
-            } else {
+            if (!mFirst) {
                 if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT
                         || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
                     windowSizeMayChange = true;
@@ -2798,7 +2796,7 @@
             }
 
             // Ask host how big it wants to be
-            windowSizeMayChange |= measureHierarchy(host, lp, res,
+            windowSizeMayChange |= measureHierarchy(host, lp, mView.getContext().getResources(),
                     desiredWindowWidth, desiredWindowHeight);
         }
 
@@ -6408,6 +6406,24 @@
                 return FINISH_HANDLED;
             }
 
+            // If the new back dispatch is enabled, intercept KEYCODE_BACK before it reaches the
+            // view tree and invoke the appropriate {@link OnBackInvokedCallback}.
+            if (isBack(event)
+                    && mContext != null
+                    && WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(mContext)) {
+                OnBackInvokedCallback topCallback =
+                        getOnBackInvokedDispatcher().getTopCallback();
+                if (event.getAction() == KeyEvent.ACTION_UP) {
+                    if (topCallback != null) {
+                        topCallback.onBackInvoked();
+                        return FINISH_HANDLED;
+                    }
+                } else {
+                    // Drop other actions such as {@link KeyEvent.ACTION_DOWN}.
+                    return FINISH_NOT_HANDLED;
+                }
+            }
+
             // Deliver the key to the view hierarchy.
             if (mView.dispatchKeyEvent(event)) {
                 return FINISH_HANDLED;
@@ -6417,19 +6433,6 @@
                 return FINISH_NOT_HANDLED;
             }
 
-            if (isBack(event)
-                    && mContext != null
-                    && WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(mContext)) {
-                // Invoke the appropriate {@link OnBackInvokedCallback} if the new back
-                // navigation should be used, and the key event is not handled by anything else.
-                OnBackInvokedCallback topCallback =
-                        getOnBackInvokedDispatcher().getTopCallback();
-                if (topCallback != null) {
-                    topCallback.onBackInvoked();
-                    return FINISH_HANDLED;
-                }
-            }
-
             // This dispatch is for windows that don't have a Window.Callback. Otherwise,
             // the Window.Callback usually will have already called this (see
             // DecorView.superDispatchKeyEvent) leaving this call a no-op.
@@ -7987,7 +7990,10 @@
                     insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0,
                     mTmpFrames, mPendingMergedConfiguration, mSurfaceControl, mTempInsets,
                     mTempControls, mRelayoutBundle);
-            mSyncSeqId = mRelayoutBundle.getInt("seqid");
+            final int maybeSyncSeqId = mRelayoutBundle.getInt("seqid");
+            if (maybeSyncSeqId > 0) {
+                mSyncSeqId = maybeSyncSeqId;
+            }
 
             if (mTranslator != null) {
                 mTranslator.translateRectInScreenToAppWindow(mTmpFrames.frame);
@@ -10736,6 +10742,13 @@
         return mOnBackInvokedDispatcher;
     }
 
+    @NonNull
+    @Override
+    public OnBackInvokedDispatcher findOnBackInvokedDispatcherForChild(
+            @NonNull View child, @NonNull View requester) {
+        return getOnBackInvokedDispatcher();
+    }
+
     /**
      * When this ViewRootImpl is added to the window manager, transfers the first
      * {@link OnBackInvokedCallback} to be called to the server.
@@ -10743,7 +10756,7 @@
     private void registerBackCallbackOnWindow() {
         if (OnBackInvokedDispatcher.DEBUG) {
             Log.d(OnBackInvokedDispatcher.TAG, TextUtils.formatSimple(
-                    "ViewRootImpl.registerBackCallbackOnWindow. Callback:%s Package:%s "
+                    "ViewRootImpl.registerBackCallbackOnWindow. Dispatcher:%s Package:%s "
                             + "IWindow:%s Session:%s",
                     mOnBackInvokedDispatcher, mBasePackageName, mWindow, mWindowSession));
         }
@@ -10757,20 +10770,13 @@
                 KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */,
                 KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
                 InputDevice.SOURCE_KEYBOARD);
-
-        ev.setDisplayId(mContext.getDisplay().getDisplayId());
-        if (mView != null) {
-            mView.dispatchKeyEvent(ev);
-        }
+        enqueueInputEvent(ev);
     }
 
     private void registerCompatOnBackInvokedCallback() {
-        mCompatOnBackInvokedCallback = new OnBackInvokedCallback() {
-            @Override
-            public void onBackInvoked() {
+        mCompatOnBackInvokedCallback = () -> {
                 sendBackKeyEvent(KeyEvent.ACTION_DOWN);
                 sendBackKeyEvent(KeyEvent.ACTION_UP);
-            }
         };
         mOnBackInvokedDispatcher.registerOnBackInvokedCallback(
                 OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCompatOnBackInvokedCallback);
diff --git a/core/java/android/view/ViewRootInsetsControllerHost.java b/core/java/android/view/ViewRootInsetsControllerHost.java
index 4387701..d960ba1 100644
--- a/core/java/android/view/ViewRootInsetsControllerHost.java
+++ b/core/java/android/view/ViewRootInsetsControllerHost.java
@@ -125,10 +125,11 @@
         if (mApplier == null) {
             mApplier = new SyncRtSurfaceTransactionApplier(mViewRoot.mView);
         }
-        if (mViewRoot.mView.isHardwareAccelerated()) {
+        if (mViewRoot.mView.isHardwareAccelerated() && isVisibleToUser()) {
             mApplier.scheduleApply(params);
         } else {
-            // Window doesn't support hardware acceleration, no synchronization for now.
+            // Synchronization requires hardware acceleration for now.
+            // If the window isn't visible, drawing is paused and the applier won't run.
             // TODO(b/149342281): use mViewRoot.mSurface.getNextFrameNumber() to sync on every
             //  frame instead.
             final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
@@ -269,4 +270,8 @@
         }
         return null;
     }
+
+    private boolean isVisibleToUser() {
+        return mViewRoot.getHostVisibility() == View.VISIBLE;
+    }
 }
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 8401b7c..555fd43 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -56,6 +56,7 @@
 import android.util.Pair;
 import android.view.View.OnApplyWindowInsetsListener;
 import android.view.accessibility.AccessibilityEvent;
+import android.window.OnBackInvokedDispatcher;
 
 import java.util.Collections;
 import java.util.List;
@@ -2798,4 +2799,12 @@
     public @Nullable AttachedSurfaceControl getRootSurfaceControl() {
         return null;
     }
+
+    /**
+     * Returns the {@link OnBackInvokedDispatcher} instance associated with this window.
+     */
+    @NonNull
+    public OnBackInvokedDispatcher getOnBackInvokedDispatcher() {
+        throw new RuntimeException("Not implemented. Must override in a subclass.");
+    }
 }
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 45169ef..8e9f9d9 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -3107,9 +3107,7 @@
          * This value is ignored if {@link #preferredDisplayModeId} is set.
          *
          * @see Display#getSupportedRefreshRates()
-         * @deprecated use {@link #preferredDisplayModeId} instead
          */
-        @Deprecated
         public float preferredRefreshRate;
 
         /**
diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java
index 464414d8..09306c7 100644
--- a/core/java/android/view/animation/Animation.java
+++ b/core/java/android/view/animation/Animation.java
@@ -196,9 +196,9 @@
     private int mZAdjustment;
 
     /**
-     * Desired background color behind animation.
+     * The desired color of the backdrop to show behind the animation.
      */
-    private int mBackgroundColor;
+    private int mBackdropColor;
 
     /**
      * scalefactor to apply to pivot points, etc. during animation. Subclasses retrieve the
@@ -211,10 +211,10 @@
 
     /**
      * Whether to show a background behind the windows during the animation.
-     * @see #getShowBackground()
-     * @see #setShowBackground(boolean)
+     * @see #getShowBackdrop()
+     * @see #setShowBackdrop(boolean)
      */
-    private boolean mShowBackground;
+    private boolean mShowBackdrop;
 
     private boolean mMore = true;
     private boolean mOneMoreTime = true;
@@ -265,7 +265,7 @@
 
         setZAdjustment(a.getInt(com.android.internal.R.styleable.Animation_zAdjustment, ZORDER_NORMAL));
 
-        setBackgroundColor(a.getInt(com.android.internal.R.styleable.Animation_background, 0));
+        setBackdropColor(a.getInt(com.android.internal.R.styleable.Animation_backdropColor, 0));
 
         setDetachWallpaper(
                 a.getBoolean(com.android.internal.R.styleable.Animation_detachWallpaper, false));
@@ -273,8 +273,8 @@
                 a.getBoolean(com.android.internal.R.styleable.Animation_showWallpaper, false));
         setHasRoundedCorners(
                 a.getBoolean(com.android.internal.R.styleable.Animation_hasRoundedCorners, false));
-        setShowBackground(
-                a.getBoolean(com.android.internal.R.styleable.Animation_showBackground, false));
+        setShowBackdrop(
+                a.getBoolean(com.android.internal.R.styleable.Animation_showBackdrop, false));
 
         final int resID = a.getResourceId(com.android.internal.R.styleable.Animation_interpolator, 0);
 
@@ -646,9 +646,12 @@
      * @param bg The background color.  If 0, no background.  Currently must
      * be black, with any desired alpha level.
      *
+     * @deprecated None of window animations are running with background color.
+     * @see #setBackdropColor(int) for an alternative.
      */
+    @Deprecated
     public void setBackgroundColor(@ColorInt int bg) {
-        mBackgroundColor = bg;
+        // The background color is not needed any more, do nothing.
     }
 
     /**
@@ -705,21 +708,35 @@
     }
 
     /**
-     * If showBackground is {@code true} and this animation is applied on a window, then the windows
+     * If showBackdrop is {@code true} and this animation is applied on a window, then the windows
      * in the animation will animate with the background associated with this window behind them.
      *
-     * The background comes from the {@link android.R.styleable#Theme_colorBackground} that is
-     * applied to this window through its theme.
+     * If no backdrop color is explicitly set, the backdrop's color comes from the
+     * {@link android.R.styleable#Theme_colorBackground} that is applied to this window through its
+     * theme.
      *
-     * If multiple animating windows have showBackground set to {@code true} during an animation,
-     * the top most window with showBackground set to {@code true} and a valid background color
+     * If multiple animating windows have showBackdrop set to {@code true} during an animation,
+     * the top most window with showBackdrop set to {@code true} and a valid background color
      * takes precedence.
      *
-     * @param showBackground Whether to show a background behind the windows during the animation.
-     * @attr ref android.R.styleable#Animation_showBackground
+     * @param showBackdrop Whether to show a background behind the windows during the animation.
+     * @attr ref android.R.styleable#Animation_showBackdrop
      */
-    public void setShowBackground(boolean showBackground) {
-        mShowBackground = showBackground;
+    public void setShowBackdrop(boolean showBackdrop) {
+        mShowBackdrop = showBackdrop;
+    }
+
+    /**
+     * Set the color to use for the backdrop shown behind the animating windows.
+     *
+     * Will only show the backdrop if showBackdrop was set to true.
+     * See {@link #setShowBackdrop(boolean)}.
+     *
+     * @param backdropColor The backdrop color. If 0, the backdrop color will not apply.
+     * @attr ref android.R.styleable#Animation_backdropColor
+     */
+    public void setBackdropColor(@ColorInt int backdropColor) {
+        mBackdropColor = backdropColor;
     }
 
     /**
@@ -822,10 +839,14 @@
 
     /**
      * Returns the background color behind the animation.
+     *
+     * @deprecated None of window animations are running with background color.
+     * @see #getBackdropColor() for an alternative.
      */
+    @Deprecated
     @ColorInt
     public int getBackgroundColor() {
-        return mBackgroundColor;
+        return 0;
     }
 
     /**
@@ -869,21 +890,36 @@
     }
 
     /**
-     * If showBackground is {@code true} and this animation is applied on a window, then the windows
+     * If showBackdrop is {@code true} and this animation is applied on a window, then the windows
      * in the animation will animate with the background associated with this window behind them.
      *
-     * The background comes from the {@link android.R.styleable#Theme_colorBackground} that is
-     * applied to this window through its theme.
+     * If no backdrop color is explicitly set, the backdrop's color comes from the
+     * {@link android.R.styleable#Theme_colorBackground} that is applied to this window
+     * through its theme.
      *
-     * If multiple animating windows have showBackground set to {@code true} during an animation,
-     * the top most window with showBackground set to {@code true} and a valid background color
+     * If multiple animating windows have showBackdrop set to {@code true} during an animation,
+     * the top most window with showBackdrop set to {@code true} and a valid background color
      * takes precedence.
      *
-     * @return if the background of this window should be shown behind the animating windows.
-     * @attr ref android.R.styleable#Animation_showBackground
+     * @return if a backdrop should be shown behind the animating windows.
+     * @attr ref android.R.styleable#Animation_showBackdrop
      */
-    public boolean getShowBackground() {
-        return mShowBackground;
+    public boolean getShowBackdrop() {
+        return mShowBackdrop;
+    }
+
+    /**
+     * Returns the background color to show behind the animating windows.
+     *
+     * Will only show the background if showBackdrop was set to true.
+     * See {@link #setShowBackdrop(boolean)}.
+     *
+     * @return The backdrop color. If 0, the backdrop color will not apply.
+     * @attr ref android.R.styleable#Animation_backdropColor
+     */
+    @ColorInt
+    public int getBackdropColor() {
+        return mBackdropColor;
     }
 
     /**
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index dbfcca9..285a407 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -240,12 +240,7 @@
 
     private final boolean mHapticTextHandleEnabled;
     /** Handles OnBackInvokedCallback back dispatch */
-    private final OnBackInvokedCallback mBackCallback = new OnBackInvokedCallback() {
-        @Override
-        public void onBackInvoked() {
-            stopTextActionMode();
-        }
-    };
+    private final OnBackInvokedCallback mBackCallback = this::stopTextActionMode;
     private boolean mBackCallbackRegistered;
 
     @Nullable
diff --git a/core/java/android/widget/MediaController.java b/core/java/android/widget/MediaController.java
index f1dc5e7..ab2d005 100644
--- a/core/java/android/widget/MediaController.java
+++ b/core/java/android/widget/MediaController.java
@@ -122,12 +122,7 @@
     private final AccessibilityManager mAccessibilityManager;
     private boolean mBackCallbackRegistered;
     /** Handles back invocation */
-    private final OnBackInvokedCallback mBackCallback = new OnBackInvokedCallback() {
-        @Override
-        public void onBackInvoked() {
-            hide();
-        }
-    };
+    private final OnBackInvokedCallback mBackCallback = this::hide;
     /** Handles decor view attach state change */
     private final OnAttachStateChangeListener mAttachStateListener =
             new OnAttachStateChangeListener() {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index f9c9641..54c0d7c 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -12617,7 +12617,10 @@
                     if (start >= 0 && start <= end && end <= text.length()) {
                         requestFocusOnNonEditableSelectableText();
                         Selection.setSelection((Spannable) text, start, end);
-                        hideAccessibilitySelectionControllers();
+                        // Make sure selection mode is engaged.
+                        if (mEditor != null) {
+                            mEditor.startSelectionActionModeAsync(false);
+                        }
                         return true;
                     }
                 }
@@ -13760,12 +13763,8 @@
             Selection.removeSelection((Spannable) text);
         }
         // Hide all selection controllers used for adjusting selection
-        // since we are doing so explicitly by other means and these
+        // since we are doing so explicitlty by other means and these
         // controllers interact with how selection behaves.
-        hideAccessibilitySelectionControllers();
-    }
-
-    private void hideAccessibilitySelectionControllers() {
         if (mEditor != null) {
             mEditor.hideCursorAndSpanControllers();
             mEditor.stopTextActionMode();
diff --git a/core/java/android/window/ITaskFragmentOrganizerController.aidl b/core/java/android/window/ITaskFragmentOrganizerController.aidl
index 4399207..8407d10 100644
--- a/core/java/android/window/ITaskFragmentOrganizerController.aidl
+++ b/core/java/android/window/ITaskFragmentOrganizerController.aidl
@@ -35,15 +35,15 @@
     /**
      * Registers remote animations per transition type for the organizer. It will override the
      * animations if the transition only contains windows that belong to the organized
-     * TaskFragments.
+     * TaskFragments in the given Task.
      */
-    void registerRemoteAnimations(in ITaskFragmentOrganizer organizer,
+    void registerRemoteAnimations(in ITaskFragmentOrganizer organizer, int taskId,
         in RemoteAnimationDefinition definition);
 
     /**
      * Unregisters remote animations per transition type for the organizer.
      */
-    void unregisterRemoteAnimations(in ITaskFragmentOrganizer organizer);
+    void unregisterRemoteAnimations(in ITaskFragmentOrganizer organizer, int taskId);
 
     /**
       * Checks if an activity organized by a {@link android.window.TaskFragmentOrganizer} and
diff --git a/core/java/android/window/OnBackAnimationCallback.java b/core/java/android/window/OnBackAnimationCallback.java
new file mode 100644
index 0000000..1a37e57
--- /dev/null
+++ b/core/java/android/window/OnBackAnimationCallback.java
@@ -0,0 +1,61 @@
+/*
+ * 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.window;
+
+import android.annotation.NonNull;
+import android.app.Activity;
+import android.app.Dialog;
+import android.view.View;
+
+/**
+ * Interface for applications to register back animation callbacks along their custom back
+ * handling.
+ * <p>
+ * This allows the client to customize various back behaviors by overriding the corresponding
+ * callback methods.
+ * <p>
+ * Callback instances can be added to and removed from {@link OnBackInvokedDispatcher}, held
+ * by classes that implement {@link OnBackInvokedDispatcherOwner} (such as {@link Activity},
+ * {@link Dialog} and {@link View}).
+ * <p>
+ * When back is triggered, callbacks on the in-focus window are invoked in reverse order in which
+ * they are added within the same priority. Between different priorities, callbacks with higher
+ * priority are invoked first.
+ * <p>
+ * @see OnBackInvokedCallback
+ * @hide
+ */
+public interface OnBackAnimationCallback extends OnBackInvokedCallback {
+   /**
+    * Called when a back gesture has been started, or back button has been pressed down.
+    */
+    default void onBackStarted() { }
+
+    /**
+     * Called on back gesture progress.
+     *
+     * @param backEvent An {@link BackEvent} object describing the progress event.
+     *
+     * @see BackEvent
+     */
+    default void onBackProgressed(@NonNull BackEvent backEvent) { }
+
+    /**
+     * Called when a back gesture or back button press has been cancelled.
+     */
+    default void onBackCancelled() { }
+}
diff --git a/core/java/android/window/OnBackInvokedCallback.java b/core/java/android/window/OnBackInvokedCallback.java
index 400a56f..6e2d4f9 100644
--- a/core/java/android/window/OnBackInvokedCallback.java
+++ b/core/java/android/window/OnBackInvokedCallback.java
@@ -18,52 +18,31 @@
 
 import android.app.Activity;
 import android.app.Dialog;
-import android.view.View;
+import android.view.Window;
 
 /**
- * Interface for applications to register back invocation callbacks. This allows the client
- * to customize various back behaviors by overriding the corresponding callback methods.
- *
- * Callback instances can be added to and removed from {@link OnBackInvokedDispatcher}, held
- * by classes that implement {@link OnBackInvokedDispatcherOwner} (such as {@link Activity},
- * {@link Dialog} and {@link View}).
- *
- * Under the hood callbacks are registered at window level. When back is triggered,
- * callbacks on the in-focus window are invoked in reverse order in which they are added
- * within the same priority. Between different pirorities, callbacks with higher priority
- * are invoked first.
- *
- * See {@link OnBackInvokedDispatcher#registerOnBackInvokedCallback(int, OnBackInvokedCallback)}
- * for specifying callback priority.
+ * Callback allowing applications to handle back events in place of the system.
+ * <p>
+ * Callback instances can be added to and removed from {@link OnBackInvokedDispatcher}, which
+ * is held at window level and accessible through {@link Activity#getOnBackInvokedDispatcher()},
+ * {@link Dialog#getOnBackInvokedDispatcher()} and {@link Window#getOnBackInvokedDispatcher()}.
+ * <p>
+ * When back is triggered, callbacks on the in-focus window are invoked in reverse order in which
+ * they are added within the same priority. Between different priorities, callbacks with higher
+ * priority are invoked first.
+ * <p>
+ * This replaces {@link Activity#onBackPressed()}, {@link Dialog#onBackPressed()} and
+ * {@link android.view.KeyEvent#KEYCODE_BACK}
+ * <p>
+ * @see OnBackInvokedDispatcher#registerOnBackInvokedCallback(int, OnBackInvokedCallback)
+ * registerOnBackInvokedCallback(priority, OnBackInvokedCallback)
+ * to specify callback priority.
  */
+@SuppressWarnings("deprecation")
 public interface OnBackInvokedCallback {
-   /**
-    * Called when a back gesture has been started, or back button has been pressed down.
-    *
-    * @hide
-    */
-    default void onBackStarted() { };
-
-    /**
-     * Called on back gesture progress.
-     *
-     * @param backEvent An {@link android.window.BackEvent} object describing the progress event.
-     *
-     * @see android.window.BackEvent
-     * @hide
-     */
-    default void onBackProgressed(BackEvent backEvent) { };
-
-    /**
-     * Called when a back gesture or back button press has been cancelled.
-     *
-     * @hide
-     */
-    default void onBackCancelled() { };
-
     /**
      * Called when a back gesture has been completed and committed, or back button pressed
      * has been released and committed.
      */
-    default void onBackInvoked() { };
+    void onBackInvoked();
 }
diff --git a/core/java/android/window/OnBackInvokedDispatcher.java b/core/java/android/window/OnBackInvokedDispatcher.java
index c254a9d..6bc2b50 100644
--- a/core/java/android/window/OnBackInvokedDispatcher.java
+++ b/core/java/android/window/OnBackInvokedDispatcher.java
@@ -19,7 +19,6 @@
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.os.Build;
 
@@ -93,15 +92,6 @@
     void unregisterOnBackInvokedCallback(@NonNull OnBackInvokedCallback callback);
 
     /**
-     * Returns the most prioritized callback to receive back dispatch next.
-     * @hide
-     */
-    @Nullable
-    default OnBackInvokedCallback getTopCallback() {
-        return null;
-    }
-
-    /**
      * Registers a {@link OnBackInvokedCallback} with system priority.
      * @hide
      */
diff --git a/core/java/android/window/OnBackInvokedDispatcherOwner.java b/core/java/android/window/OnBackInvokedDispatcherOwner.java
deleted file mode 100644
index dfe06fd..0000000
--- a/core/java/android/window/OnBackInvokedDispatcherOwner.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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.window;
-
-import android.annotation.NonNull;
-
-/**
- * A class that provides an {@link OnBackInvokedDispatcher} that allows you to register
- * an {@link OnBackInvokedCallback} for handling the system back invocation behavior.
- */
-public interface OnBackInvokedDispatcherOwner {
-    /**
-     * Returns the {@link OnBackInvokedDispatcher} that should dispatch the back invocation
-     * to its registered {@link OnBackInvokedCallback}s.
-     * Returns null when the root view is not attached to a window or a view tree with a decor.
-     */
-    @NonNull
-    OnBackInvokedDispatcher getOnBackInvokedDispatcher();
-}
diff --git a/core/java/android/window/ProxyOnBackInvokedDispatcher.java b/core/java/android/window/ProxyOnBackInvokedDispatcher.java
index eb77631..4409397 100644
--- a/core/java/android/window/ProxyOnBackInvokedDispatcher.java
+++ b/core/java/android/window/ProxyOnBackInvokedDispatcher.java
@@ -28,8 +28,8 @@
  * {@link OnBackInvokedDispatcher} only used to hold callbacks while an actual
  * dispatcher becomes available. <b>It does not dispatch the back events</b>.
  * <p>
- * Once the actual {@link OnBackInvokedDispatcherOwner} becomes available,
- * {@link #setActualDispatcherOwner(OnBackInvokedDispatcherOwner)} needs to
+ * Once the actual {@link OnBackInvokedDispatcher} becomes available,
+ * {@link #setActualDispatcher(OnBackInvokedDispatcher)} needs to
  * be called and this {@link ProxyOnBackInvokedDispatcher} will pass the callback registrations
  * onto it.
  * <p>
@@ -48,14 +48,14 @@
      */
     private final List<Pair<OnBackInvokedCallback, Integer>> mCallbacks = new ArrayList<>();
     private final Object mLock = new Object();
-    private OnBackInvokedDispatcherOwner mActualDispatcherOwner = null;
+    private OnBackInvokedDispatcher mActualDispatcher = null;
 
     @Override
     public void registerOnBackInvokedCallback(
             int priority, @NonNull OnBackInvokedCallback callback) {
         if (DEBUG) {
-            Log.v(TAG, String.format("Pending register %s. Actual=%s", callback,
-                    mActualDispatcherOwner));
+            Log.v(TAG, String.format("Proxy register %s. mActualDispatcher=%s", callback,
+                    mActualDispatcher));
         }
         if (priority < 0) {
             throw new IllegalArgumentException("Application registered OnBackInvokedCallback "
@@ -74,13 +74,12 @@
             @NonNull OnBackInvokedCallback callback) {
         if (DEBUG) {
             Log.v(TAG, String.format("Proxy unregister %s. Actual=%s", callback,
-                    mActualDispatcherOwner));
+                    mActualDispatcher));
         }
         synchronized (mLock) {
             mCallbacks.removeIf((p) -> p.first.equals(callback));
-            if (mActualDispatcherOwner != null) {
-                mActualDispatcherOwner.getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(
-                        callback);
+            if (mActualDispatcher != null) {
+                mActualDispatcher.unregisterOnBackInvokedCallback(callback);
             }
         }
     }
@@ -89,9 +88,8 @@
             @NonNull OnBackInvokedCallback callback, int priority) {
         synchronized (mLock) {
             mCallbacks.add(Pair.create(callback, priority));
-            if (mActualDispatcherOwner != null) {
-                mActualDispatcherOwner.getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
-                        priority, callback);
+            if (mActualDispatcher != null) {
+                mActualDispatcher.registerOnBackInvokedCallback(priority, callback);
             }
         }
     }
@@ -103,34 +101,30 @@
      * proxy dispatcher.
      */
     private void transferCallbacksToDispatcher() {
-        if (mActualDispatcherOwner == null) {
+        if (mActualDispatcher == null) {
             return;
         }
-        OnBackInvokedDispatcher dispatcher =
-                mActualDispatcherOwner.getOnBackInvokedDispatcher();
         if (DEBUG) {
-            Log.v(TAG, String.format("Proxy: transferring %d pending callbacks to %s",
-                    mCallbacks.size(), dispatcher));
+            Log.v(TAG, String.format("Proxy transferring %d callbacks to %s", mCallbacks.size(),
+                    mActualDispatcher));
         }
         for (Pair<OnBackInvokedCallback, Integer> callbackPair : mCallbacks) {
             int priority = callbackPair.second;
             if (priority >= 0) {
-                dispatcher.registerOnBackInvokedCallback(priority, callbackPair.first);
+                mActualDispatcher.registerOnBackInvokedCallback(priority, callbackPair.first);
             } else {
-                dispatcher.registerSystemOnBackInvokedCallback(callbackPair.first);
+                mActualDispatcher.registerSystemOnBackInvokedCallback(callbackPair.first);
             }
         }
         mCallbacks.clear();
     }
 
     private void clearCallbacksOnDispatcher() {
-        if (mActualDispatcherOwner == null) {
+        if (mActualDispatcher == null) {
             return;
         }
-        OnBackInvokedDispatcher onBackInvokedDispatcher =
-                mActualDispatcherOwner.getOnBackInvokedDispatcher();
         for (Pair<OnBackInvokedCallback, Integer> callback : mCallbacks) {
-            onBackInvokedDispatcher.unregisterOnBackInvokedCallback(callback.first);
+            mActualDispatcher.unregisterOnBackInvokedCallback(callback.first);
         }
     }
 
@@ -138,7 +132,7 @@
      * Resets this {@link ProxyOnBackInvokedDispatcher} so it loses track of the currently
      * registered callbacks.
      * <p>
-     * Using this method means that when setting a new {@link OnBackInvokedDispatcherOwner}, the
+     * Using this method means that when setting a new {@link OnBackInvokedDispatcher}, the
      * callbacks registered on the old one won't be removed from it and won't be registered on
      * the new one.
      */
@@ -152,28 +146,26 @@
     }
 
     /**
-     * Sets the actual {@link OnBackInvokedDispatcherOwner} that will provides the
-     * {@link OnBackInvokedDispatcher} onto which the callbacks will be registered.
+     * Sets the actual {@link OnBackInvokedDispatcher} onto which the callbacks will be registered.
      * <p>
-     * If any dispatcher owner was already present, all the callbacks that were added via this
+     * If any dispatcher was already present, all the callbacks that were added via this
      * {@link ProxyOnBackInvokedDispatcher} will be unregistered from the old one and registered
      * on the new one if it is not null.
      * <p>
      * If you do not wish for the previously registered callbacks to be reassigned to the new
      * dispatcher, {@link #reset} must be called beforehand.
      */
-    public void setActualDispatcherOwner(
-            @Nullable OnBackInvokedDispatcherOwner actualDispatcherOwner) {
+    public void setActualDispatcher(@Nullable OnBackInvokedDispatcher actualDispatcher) {
         if (DEBUG) {
             Log.v(TAG, String.format("Proxy setActual %s. Current %s",
-                            actualDispatcherOwner, mActualDispatcherOwner));
+                            actualDispatcher, mActualDispatcher));
         }
         synchronized (mLock) {
-            if (actualDispatcherOwner == mActualDispatcherOwner) {
+            if (actualDispatcher == mActualDispatcher) {
                 return;
             }
             clearCallbacksOnDispatcher();
-            mActualDispatcherOwner = actualDispatcherOwner;
+            mActualDispatcher = actualDispatcher;
             transferCallbacksToDispatcher();
         }
     }
diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java
index 9c2fde0..1d1deac 100644
--- a/core/java/android/window/TaskFragmentOrganizer.java
+++ b/core/java/android/window/TaskFragmentOrganizer.java
@@ -94,13 +94,16 @@
     /**
      * Registers remote animations per transition type for the organizer. It will override the
      * animations if the transition only contains windows that belong to the organized
-     * TaskFragments.
+     * TaskFragments in the given Task.
+     *
+     * @param taskId overrides if the transition only contains windows belonging to this Task.
      * @hide
      */
     @CallSuper
-    public void registerRemoteAnimations(@NonNull RemoteAnimationDefinition definition) {
+    public void registerRemoteAnimations(int taskId,
+            @NonNull RemoteAnimationDefinition definition) {
         try {
-            getController().registerRemoteAnimations(mInterface, definition);
+            getController().registerRemoteAnimations(mInterface, taskId, definition);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -111,9 +114,9 @@
      * @hide
      */
     @CallSuper
-    public void unregisterRemoteAnimations() {
+    public void unregisterRemoteAnimations(int taskId) {
         try {
-            getController().unregisterRemoteAnimations(mInterface);
+            getController().unregisterRemoteAnimations(mInterface, taskId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 8811116..cad8b9b 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -51,9 +51,10 @@
     private IWindowSession mWindowSession;
     private IWindow mWindow;
     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;
+    private static final boolean ENABLE_PREDICTIVE_BACK = SystemProperties
+            .getInt("persist.wm.debug.predictive_back", 1) != 0;
+    private static final boolean ALWAYS_ENFORCE_PREDICTIVE_BACK = SystemProperties
+            .getInt("persist.wm.debug.predictive_back_always_enforce", 0) != 0;
 
     /** Convenience hashmap to quickly decide if a callback has been added. */
     private final HashMap<OnBackInvokedCallback, Integer> mAllCallbacks = new HashMap<>();
@@ -175,7 +176,6 @@
         }
     }
 
-    @Override
     public OnBackInvokedCallback getTopCallback() {
         if (mAllCallbacks.isEmpty()) {
             return null;
@@ -199,36 +199,30 @@
         @Override
         public void onBackStarted() {
             Handler.getMain().post(() -> {
-                final OnBackInvokedCallback callback = mCallback.get();
-                if (callback == null) {
-                    return;
+                final OnBackAnimationCallback callback = getBackAnimationCallback();
+                if (callback != null) {
+                    callback.onBackStarted();
                 }
-
-                callback.onBackStarted();
             });
         }
 
         @Override
         public void onBackProgressed(BackEvent backEvent) {
             Handler.getMain().post(() -> {
-                final OnBackInvokedCallback callback = mCallback.get();
-                if (callback == null) {
-                    return;
+                final OnBackAnimationCallback callback = getBackAnimationCallback();
+                if (callback != null) {
+                    callback.onBackProgressed(backEvent);
                 }
-
-                callback.onBackProgressed(backEvent);
             });
         }
 
         @Override
         public void onBackCancelled() {
             Handler.getMain().post(() -> {
-                final OnBackInvokedCallback callback = mCallback.get();
-                if (callback == null) {
-                    return;
+                final OnBackAnimationCallback callback = getBackAnimationCallback();
+                if (callback != null) {
+                    callback.onBackCancelled();
                 }
-
-                callback.onBackCancelled();
             });
         }
 
@@ -243,6 +237,13 @@
                 callback.onBackInvoked();
             });
         }
+
+        @Nullable
+        private OnBackAnimationCallback getBackAnimationCallback() {
+            OnBackInvokedCallback callback = mCallback.get();
+            return callback instanceof OnBackAnimationCallback ? (OnBackAnimationCallback) callback
+                    : null;
+        }
     }
 
     /**
@@ -254,18 +255,18 @@
     public static boolean isOnBackInvokedCallbackEnabled(@Nullable Context context) {
         // new back is enabled if the feature flag is enabled AND the app does not explicitly
         // request legacy back.
-        boolean featureFlagEnabled = IS_BACK_PREDICTABILITY_ENABLED;
+        boolean featureFlagEnabled = ENABLE_PREDICTIVE_BACK;
         // If the context is null, we assume true and fallback on the two other conditions.
         boolean appRequestsPredictiveBack =
                 context != null && context.getApplicationInfo().isOnBackInvokedCallbackEnabled();
 
         if (DEBUG) {
             Log.d(TAG, TextUtils.formatSimple("App: %s featureFlagEnabled=%s "
-                            + "appRequestsPredictiveBack=%s",
+                            + "appRequestsPredictiveBack=%s alwaysEnforce=%s",
                     context != null ? context.getApplicationInfo().packageName : "null context",
-                    featureFlagEnabled, appRequestsPredictiveBack));
+                    featureFlagEnabled, appRequestsPredictiveBack, ALWAYS_ENFORCE_PREDICTIVE_BACK));
         }
 
-        return featureFlagEnabled && appRequestsPredictiveBack;
+        return featureFlagEnabled && (appRequestsPredictiveBack || ALWAYS_ENFORCE_PREDICTIVE_BACK);
     }
 }
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
index 4a20ad0..fc2c8cc 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
@@ -33,7 +33,6 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.os.Build;
-import android.os.storage.StorageManager;
 import android.provider.Settings;
 import android.text.BidiFormatter;
 import android.view.LayoutInflater;
@@ -282,19 +281,7 @@
                 Context.LAYOUT_INFLATER_SERVICE);
 
         final View content = inflater.inflate(
-                R.layout.accessibility_enable_service_encryption_warning, /* root= */ null);
-
-        final TextView encryptionWarningView = (TextView) content.findViewById(
-                R.id.accessibility_encryption_warning);
-        if (StorageManager.isNonDefaultBlockEncrypted()) {
-            final String text = context.getString(
-                    R.string.accessibility_enable_service_encryption_warning,
-                    getServiceName(context, target.getLabel()));
-            encryptionWarningView.setText(text);
-            encryptionWarningView.setVisibility(View.VISIBLE);
-        } else {
-            encryptionWarningView.setVisibility(View.GONE);
-        }
+                R.layout.accessibility_enable_service_warning, /* root= */ null);
 
         final ImageView dialogIcon = content.findViewById(
                 R.id.accessibility_permissionDialog_icon);
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 88089b5..d4a8a16 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -167,16 +167,6 @@
     public static final String EXTRA_PRIVATE_RETAIN_IN_ON_STOP
             = "com.android.internal.app.ChooserActivity.EXTRA_PRIVATE_RETAIN_IN_ON_STOP";
 
-    /**
-     * Boolean extra added to "unbundled Sharesheet" delegation intents to signal whether the app
-     * prediction service is available. Our query of the service <em>availability</em> depends on
-     * privileges that are only available in the system, even though the service itself would then
-     * be available to the unbundled component. For now, we just include the query result as part of
-     * the handover intent.
-     * TODO: investigate whether the privileged query is necessary to determine the availability.
-     */
-    public static final String EXTRA_IS_APP_PREDICTION_SERVICE_AVAILABLE =
-            "com.android.internal.app.ChooserActivity.EXTRA_IS_APP_PREDICTION_SERVICE_AVAILABLE";
 
     /**
      * Transition name for the first image preview.
@@ -985,6 +975,12 @@
     }
 
     @Override
+    protected void onResume() {
+        super.onResume();
+        Log.d(TAG, "onResume: " + getComponentName().flattenToShortString());
+    }
+
+    @Override
     public void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
         ViewPager viewPager = findViewById(R.id.profile_pager);
diff --git a/core/java/com/android/internal/app/LocalePickerWithRegion.java b/core/java/com/android/internal/app/LocalePickerWithRegion.java
index e7cb43e..52c74cf 100644
--- a/core/java/com/android/internal/app/LocalePickerWithRegion.java
+++ b/core/java/com/android/internal/app/LocalePickerWithRegion.java
@@ -264,10 +264,9 @@
     }
 
     @Override
-    public void onListItemClick(ListView l, View v, int position, long id) {
-        SuggestedLocaleAdapter adapter = (SuggestedLocaleAdapter) getListAdapter();
+    public void onListItemClick(ListView parent, View v, int position, long id) {
         final LocaleStore.LocaleInfo locale =
-                (LocaleStore.LocaleInfo) adapter.getItem(position);
+                (LocaleStore.LocaleInfo) parent.getAdapter().getItem(position);
         // Special case for resetting the app locale to equal the system locale.
         boolean isSystemLocale = locale.isSystemLocale();
         boolean isRegionLocale = locale.getParent() != null;
@@ -280,7 +279,7 @@
         } else {
             LocalePickerWithRegion selector = LocalePickerWithRegion.createCountryPicker(
                     getContext(), mListener, locale, mTranslatedOnly /* translate only */,
-                    adapter.getAppPackageName());
+                    mAppPackageName);
             if (selector != null) {
                 getFragmentManager().beginTransaction()
                         .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index ea8589b..bfd8ff9 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -157,8 +157,6 @@
     /** See {@link #setRetainInOnStop}. */
     private boolean mRetainInOnStop;
 
-    protected static final int REQUEST_CODE_RETURN_FROM_DELEGATE_CHOOSER = 20;
-
     private static final String EXTRA_SHOW_FRAGMENT_ARGS = ":settings:show_fragment_args";
     private static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
     private static final String OPEN_LINKS_COMPONENT_KEY = "app_link_state";
@@ -1374,18 +1372,6 @@
                 .write();
     }
 
-    @Override
-    public void onActivityResult(int requestCode, int resultCode, Intent data) {
-        switch (requestCode) {
-            case REQUEST_CODE_RETURN_FROM_DELEGATE_CHOOSER:
-                // Repeat the delegate's result as our own.
-                setResult(resultCode, data);
-                finish();
-                break;
-            default:
-                super.onActivityResult(requestCode, resultCode, data);
-        }
-    }
 
     public void onActivityStarted(TargetInfo cti) {
         // Do nothing
diff --git a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
index c3e7920..2eb104e 100644
--- a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
+++ b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
@@ -348,8 +348,4 @@
     public Filter getFilter() {
         return new FilterByNativeAndUiNames();
     }
-
-    public String getAppPackageName() {
-        return mAppPackageName;
-    }
 }
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 4b20347..e4dec56 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -719,7 +719,7 @@
     int mNumHistoryItems;
 
     private static final int HISTORY_TAG_INDEX_LIMIT = 0x7ffe;
-    private static final int MAX_HISTORY_TAG_STRING_LENGTH = 256;
+    private static final int MAX_HISTORY_TAG_STRING_LENGTH = 1024;
 
     final HashMap<HistoryTag, Integer> mHistoryTagPool = new HashMap<>();
     private SparseArray<HistoryTag> mHistoryTags;
@@ -3970,6 +3970,12 @@
             Slog.wtfStack(TAG, "writeHistoryTag called with null name");
         }
 
+        final int stringLength = tag.string.length();
+        if (stringLength > MAX_HISTORY_TAG_STRING_LENGTH) {
+            Slog.e(TAG, "Long battery history tag: " + tag.string);
+            tag.string = tag.string.substring(0, MAX_HISTORY_TAG_STRING_LENGTH);
+        }
+
         Integer idxObj = mHistoryTagPool.get(tag);
         int idx;
         if (idxObj != null) {
@@ -3986,11 +3992,6 @@
             tag.poolIdx = idx;
             mHistoryTagPool.put(key, idx);
             mNextHistoryTagIdx++;
-            final int stringLength = key.string.length();
-
-            if (stringLength > MAX_HISTORY_TAG_STRING_LENGTH) {
-                Slog.wtf(TAG, "Long battery history tag: " + key.string);
-            }
 
             mNumHistoryTagChars += stringLength + 1;
             if (mHistoryTags != null) {
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index b4ba16f..488fb180 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -111,7 +111,6 @@
 import android.widget.ProgressBar;
 import android.widget.TextView;
 import android.window.OnBackInvokedDispatcher;
-import android.window.OnBackInvokedDispatcherOwner;
 import android.window.ProxyOnBackInvokedDispatcher;
 
 import com.android.internal.R;
@@ -137,8 +136,7 @@
  *
  * @hide
  */
-public class PhoneWindow extends Window implements MenuBuilder.Callback,
-        OnBackInvokedDispatcherOwner {
+public class PhoneWindow extends Window implements MenuBuilder.Callback {
 
     private final static String TAG = "PhoneWindow";
 
@@ -2153,7 +2151,7 @@
     /** Notify when decor view is attached to window and {@link ViewRootImpl} is available. */
     void onViewRootImplSet(ViewRootImpl viewRoot) {
         viewRoot.setActivityConfigCallback(mActivityConfigCallback);
-        mProxyOnBackInvokedDispatcher.setActualDispatcherOwner(viewRoot);
+        mProxyOnBackInvokedDispatcher.setActualDispatcher(viewRoot.getOnBackInvokedDispatcher());
         applyDecorFitsSystemWindows();
     }
 
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 089179d..634063a 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -169,7 +169,7 @@
     /**
     * Used to hide the authentication dialog, e.g. when the application cancels authentication.
     */
-    void hideAuthenticationDialog();
+    void hideAuthenticationDialog(long requestId);
     /* Used to notify the biometric service of events that occur outside of an operation. */
     void setBiometicContextListener(in IBiometricContextListener listener);
 
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 2ee5e79..46b4630 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -131,7 +131,7 @@
     // Used to show an error - the dialog will dismiss after a certain amount of time
     void onBiometricError(int modality, int error, int vendorCode);
     // Used to hide the authentication dialog, e.g. when the application cancels authentication
-    void hideAuthenticationDialog();
+    void hideAuthenticationDialog(long requestId);
     // Used to notify the biometric service of events that occur outside of an operation.
     void setBiometicContextListener(in IBiometricContextListener listener);
 
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index db41d33..06d12b5 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -46,6 +46,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.build.UnboundedSdkLevel;
 
 import libcore.io.IoUtils;
 import libcore.util.EmptyArray;
@@ -58,7 +59,6 @@
 import java.io.FileNotFoundException;
 import java.io.FileReader;
 import java.io.IOException;
-import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -88,8 +88,8 @@
     private static final int ALLOW_HIDDENAPI_WHITELISTING = 0x040;
     private static final int ALLOW_ASSOCIATIONS = 0x080;
     // ALLOW_OVERRIDE_APP_RESTRICTIONS allows to use "allow-in-power-save-except-idle",
-    // "allow-in-power-save", "allow-in-data-usage-save", "allow-unthrottled-location",
-    // and "allow-ignore-location-settings".
+    // "allow-in-power-save", "allow-in-data-usage-save","allow-unthrottled-location",
+    // "allow-ignore-location-settings" and "allow-adas-location-settings".
     private static final int ALLOW_OVERRIDE_APP_RESTRICTIONS = 0x100;
     private static final int ALLOW_IMPLICIT_BROADCASTS = 0x200;
     private static final int ALLOW_VENDOR_APEX = 0x400;
@@ -126,7 +126,7 @@
          *
          * <p>0 means not specified.
          */
-        public final int onBootclasspathSince;
+        public final String onBootclasspathSince;
 
         /**
          * SDK version this library was removed from the BOOTCLASSPATH.
@@ -138,7 +138,7 @@
          *
          * <p>0 means not specified.
          */
-        public final int onBootclasspathBefore;
+        public final String onBootclasspathBefore;
 
         /**
          * Declares whether this library can be safely ignored from <uses-library> tags.
@@ -155,19 +155,19 @@
         @VisibleForTesting
         public SharedLibraryEntry(String name, String filename, String[] dependencies,
                 boolean isNative) {
-            this(name, filename, dependencies, 0 /* onBootclasspathSince */,
-                    0 /* onBootclasspathBefore */, isNative);
+            this(name, filename, dependencies, null /* onBootclasspathSince */,
+                    null /* onBootclasspathBefore */, isNative);
         }
 
         @VisibleForTesting
         public SharedLibraryEntry(String name, String filename, String[] dependencies,
-                int onBootclasspathSince, int onBootclassPathBefore) {
-            this(name, filename, dependencies, onBootclasspathSince, onBootclassPathBefore,
+                String onBootclasspathSince, String onBootclasspathBefore) {
+            this(name, filename, dependencies, onBootclasspathSince, onBootclasspathBefore,
                     false /* isNative */);
         }
 
         SharedLibraryEntry(String name, String filename, String[] dependencies,
-                int onBootclasspathSince, int onBootclasspathBefore, boolean isNative) {
+                String onBootclasspathSince, String onBootclasspathBefore, boolean isNative) {
             this.name = name;
             this.filename = filename;
             this.dependencies = dependencies;
@@ -175,16 +175,14 @@
             this.onBootclasspathBefore = onBootclasspathBefore;
             this.isNative = isNative;
 
-            canBeSafelyIgnored = this.onBootclasspathSince != 0
-                    && isSdkAtLeast(this.onBootclasspathSince);
-        }
-
-        private static boolean isSdkAtLeast(int level) {
-            if ("REL".equals(Build.VERSION.CODENAME)) {
-                return Build.VERSION.SDK_INT >= level;
-            }
-            return level == Build.VERSION_CODES.CUR_DEVELOPMENT
-                    || Build.VERSION.SDK_INT >= level;
+            // this entry can be ignored if either:
+            // - onBootclasspathSince is set and we are at or past that SDK
+            // - onBootclasspathBefore is set and we are before that SDK
+            canBeSafelyIgnored =
+                    (this.onBootclasspathSince != null
+                            && UnboundedSdkLevel.isAtLeast(this.onBootclasspathSince))
+                            || (this.onBootclasspathBefore != null
+                            && !UnboundedSdkLevel.isAtLeast(this.onBootclasspathBefore));
         }
     }
 
@@ -234,6 +232,10 @@
     // without throttling, as read from the configuration files.
     final ArraySet<String> mAllowUnthrottledLocation = new ArraySet<>();
 
+    // These are the packages that are allow-listed to be able to retrieve location when
+    // the location state is driver assistance only.
+    final ArrayMap<String, ArraySet<String>> mAllowAdasSettings = new ArrayMap<>();
+
     // These are the packages that are white-listed to be able to retrieve location even when user
     // location settings are off, for emergency purposes, as read from the configuration files.
     final ArrayMap<String, ArraySet<String>> mAllowIgnoreLocationSettings = new ArrayMap<>();
@@ -394,6 +396,10 @@
         return mAllowUnthrottledLocation;
     }
 
+    public ArrayMap<String, ArraySet<String>> getAllowAdasLocationSettings() {
+        return mAllowAdasSettings;
+    }
+
     public ArrayMap<String, ArraySet<String>> getAllowIgnoreLocationSettings() {
         return mAllowIgnoreLocationSettings;
     }
@@ -870,10 +876,8 @@
                             String lname = parser.getAttributeValue(null, "name");
                             String lfile = parser.getAttributeValue(null, "file");
                             String ldependency = parser.getAttributeValue(null, "dependency");
-                            int minDeviceSdk = XmlUtils.readIntAttribute(parser, "min-device-sdk",
-                                    0);
-                            int maxDeviceSdk = XmlUtils.readIntAttribute(parser, "max-device-sdk",
-                                    0);
+                            String minDeviceSdk = parser.getAttributeValue(null, "min-device-sdk");
+                            String maxDeviceSdk = parser.getAttributeValue(null, "max-device-sdk");
                             if (lname == null) {
                                 Slog.w(TAG, "<" + name + "> without name in " + permFile + " at "
                                         + parser.getPositionDescription());
@@ -881,15 +885,18 @@
                                 Slog.w(TAG, "<" + name + "> without file in " + permFile + " at "
                                         + parser.getPositionDescription());
                             } else {
-                                boolean allowedMinSdk = minDeviceSdk <= Build.VERSION.SDK_INT;
+                                boolean allowedMinSdk =
+                                        minDeviceSdk == null || UnboundedSdkLevel.isAtLeast(
+                                                minDeviceSdk);
                                 boolean allowedMaxSdk =
-                                        maxDeviceSdk == 0 || maxDeviceSdk >= Build.VERSION.SDK_INT;
+                                        maxDeviceSdk == null || UnboundedSdkLevel.isAtMost(
+                                                maxDeviceSdk);
                                 final boolean exists = new File(lfile).exists();
                                 if (allowedMinSdk && allowedMaxSdk && exists) {
-                                    int bcpSince = XmlUtils.readIntAttribute(parser,
-                                            "on-bootclasspath-since", 0);
-                                    int bcpBefore = XmlUtils.readIntAttribute(parser,
-                                            "on-bootclasspath-before", 0);
+                                    String bcpSince = parser.getAttributeValue(null,
+                                            "on-bootclasspath-since");
+                                    String bcpBefore = parser.getAttributeValue(null,
+                                            "on-bootclasspath-before");
                                     SharedLibraryEntry entry = new SharedLibraryEntry(lname, lfile,
                                             ldependency == null
                                                     ? new String[0] : ldependency.split(":"),
@@ -1007,6 +1014,34 @@
                         }
                         XmlUtils.skipCurrentTag(parser);
                     } break;
+                    case "allow-adas-location-settings" : {
+                        if (allowOverrideAppRestrictions) {
+                            String pkgname = parser.getAttributeValue(null, "package");
+                            String attributionTag = parser.getAttributeValue(null,
+                                    "attributionTag");
+                            if (pkgname == null) {
+                                Slog.w(TAG, "<" + name + "> without package in "
+                                        + permFile + " at " + parser.getPositionDescription());
+                            } else {
+                                ArraySet<String> tags = mAllowAdasSettings.get(pkgname);
+                                if (tags == null || !tags.isEmpty()) {
+                                    if (tags == null) {
+                                        tags = new ArraySet<>(1);
+                                        mAllowAdasSettings.put(pkgname, tags);
+                                    }
+                                    if (!"*".equals(attributionTag)) {
+                                        if ("null".equals(attributionTag)) {
+                                            attributionTag = null;
+                                        }
+                                        tags.add(attributionTag);
+                                    }
+                                }
+                            }
+                        } else {
+                            logNotAllowedInPartition(name, permFile, parser);
+                        }
+                        XmlUtils.skipCurrentTag(parser);
+                    } break;
                     case "allow-ignore-location-settings": {
                         if (allowOverrideAppRestrictions) {
                             String pkgname = parser.getAttributeValue(null, "package");
@@ -1451,7 +1486,7 @@
             addFeature(PackageManager.FEATURE_IPSEC_TUNNELS, 0);
         }
 
-        if (isFilesystemSupported("erofs")) {
+        if (isErofsSupported()) {
             if (isKernelVersionAtLeast(5, 10)) {
                 addFeature(PackageManager.FEATURE_EROFS, 0);
             } else if (isKernelVersionAtLeast(4, 19)) {
@@ -1829,11 +1864,10 @@
         return Process.myUid() == Process.SYSTEM_UID;
     }
 
-    private static boolean isFilesystemSupported(String fs) {
+    private static boolean isErofsSupported() {
         try {
-            final byte[] fsTableData = Files.readAllBytes(Paths.get("/proc/filesystems"));
-            final String fsTable = new String(fsTableData, StandardCharsets.UTF_8);
-            return fsTable.contains("\t" + fs + "\n");
+            final Path path = Paths.get("/sys/fs/erofs");
+            return Files.exists(path);
         } catch (Exception e) {
             return false;
         }
diff --git a/core/jni/android_hardware_input_InputApplicationHandle.cpp b/core/jni/android_hardware_input_InputApplicationHandle.cpp
index eab4e1d..24d3531 100644
--- a/core/jni/android_hardware_input_InputApplicationHandle.cpp
+++ b/core/jni/android_hardware_input_InputApplicationHandle.cpp
@@ -105,11 +105,10 @@
 
 jobject android_view_InputApplicationHandle_fromInputApplicationInfo(
         JNIEnv* env, gui::InputApplicationInfo inputApplicationInfo) {
-    ScopedLocalRef<jobject> binderObject(env,
-                                         javaObjectForIBinder(env, inputApplicationInfo.token));
+    jobject binderObject = javaObjectForIBinder(env, inputApplicationInfo.token);
     ScopedLocalRef<jstring> name(env, env->NewStringUTF(inputApplicationInfo.name.data()));
     return env->NewObject(gInputApplicationHandleClassInfo.clazz,
-                          gInputApplicationHandleClassInfo.ctor, binderObject.get(), name.get(),
+                          gInputApplicationHandleClassInfo.ctor, binderObject, name.get(),
                           inputApplicationInfo.dispatchingTimeoutMillis);
 }
 
diff --git a/core/jni/android_hardware_input_InputWindowHandle.cpp b/core/jni/android_hardware_input_InputWindowHandle.cpp
index 484d928..973ed29 100644
--- a/core/jni/android_hardware_input_InputWindowHandle.cpp
+++ b/core/jni/android_hardware_input_InputWindowHandle.cpp
@@ -23,7 +23,7 @@
 #include <android_runtime/AndroidRuntime.h>
 #include <android_runtime/Log.h>
 #include <binder/IPCThreadState.h>
-#include <ftl/cast.h>
+#include <ftl/flags.h>
 #include <gui/SurfaceControl.h>
 #include <gui/WindowInfo.h>
 #include <nativehelper/JNIHelp.h>
@@ -151,7 +151,7 @@
         env->DeleteLocalRef(regionObj);
     }
 
-    const auto flags = Flags<WindowInfo::Flag>(
+    const auto flags = ftl::Flags<WindowInfo::Flag>(
             env->GetIntField(obj, gInputWindowHandleClassInfo.layoutParamsFlags));
     const auto type = static_cast<WindowInfo::Type>(
             env->GetIntField(obj, gInputWindowHandleClassInfo.layoutParamsType));
@@ -261,8 +261,8 @@
     }
     LOG_ALWAYS_FATAL_IF(inputWindowHandle == nullptr,
                         "Failed to create new InputWindowHandle object.");
-    ScopedLocalRef<jobject> token(env, javaObjectForIBinder(env, windowInfo.token));
-    env->SetObjectField(inputWindowHandle, gInputWindowHandleClassInfo.token, token.get());
+    env->SetObjectField(inputWindowHandle, gInputWindowHandleClassInfo.token,
+                        javaObjectForIBinder(env, windowInfo.token));
     ScopedLocalRef<jstring> name(env, env->NewStringUTF(windowInfo.name.data()));
     env->SetObjectField(inputWindowHandle, gInputWindowHandleClassInfo.name, name.get());
     env->SetIntField(inputWindowHandle, gInputWindowHandleClassInfo.layoutParamsFlags,
@@ -317,9 +317,8 @@
     ScopedLocalRef<jobject> matrixObj(env, AMatrix_newInstance(env, transformVals));
     env->SetObjectField(inputWindowHandle, gInputWindowHandleClassInfo.transform, matrixObj.get());
 
-    ScopedLocalRef<jobject> windowToken(env, javaObjectForIBinder(env, windowInfo.windowToken));
     env->SetObjectField(inputWindowHandle, gInputWindowHandleClassInfo.windowToken,
-                        windowToken.get());
+                        javaObjectForIBinder(env, windowInfo.windowToken));
 
     return inputWindowHandle;
 }
diff --git a/core/jni/android_window_WindowInfosListener.cpp b/core/jni/android_window_WindowInfosListener.cpp
index aae2549..f2cbe8a 100644
--- a/core/jni/android_window_WindowInfosListener.cpp
+++ b/core/jni/android_window_WindowInfosListener.cpp
@@ -22,6 +22,7 @@
 #include <gui/DisplayInfo.h>
 #include <gui/SurfaceComposerClient.h>
 #include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedLocalFrame.h>
 #include <utils/Log.h>
 
 #include "android_hardware_input_InputWindowHandle.h"
@@ -95,6 +96,7 @@
         JNIEnv* env = AndroidRuntime::getJNIEnv();
         LOG_ALWAYS_FATAL_IF(env == nullptr, "Unable to retrieve JNIEnv in onWindowInfoChanged.");
 
+        ScopedLocalFrame localFrame(env);
         jobject listener = env->NewGlobalRef(mListener);
         if (listener == nullptr) {
             // Weak reference went out of scope
diff --git a/core/proto/android/os/appbackgroundrestrictioninfo.proto b/core/proto/android/os/appbackgroundrestrictioninfo.proto
new file mode 100644
index 0000000..8445641
--- /dev/null
+++ b/core/proto/android/os/appbackgroundrestrictioninfo.proto
@@ -0,0 +1,199 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+package android.os;
+
+option java_multiple_files = true;
+
+// This message is used for statsd logging and should be kept in sync with
+// frameworks/proto_logging/stats/atoms.proto
+/**
+ * Logs information about app background restrictions.
+ *
+ * Logged from:
+ *      frameworks/base/services/core/java/com/android/server/am/AppRestrictionController.java
+ */
+message AppBackgroundRestrictionsInfo {
+    // the uid of the app.
+    optional int32 uid = 1;
+
+    enum RestrictionLevel {
+        LEVEL_UNKNOWN = 0;
+        LEVEL_UNRESTRICTED = 1;
+        LEVEL_EXEMPTED = 2;
+        LEVEL_ADAPTIVE_BUCKET = 3;
+        LEVEL_RESTRICTED_BUCKET = 4;
+        LEVEL_BACKGROUND_RESTRICTED = 5;
+        LEVEL_HIBERNATION = 6;
+    }
+    // indicates the app background restriction level.
+    optional RestrictionLevel restriction_level = 2;
+
+    enum Threshold {
+        THRESHOLD_UNKNOWN = 0;
+        THRESHOLD_RESTRICTED = 1; // app was background restricted by the system.
+        THRESHOLD_USER = 2; // app was background restricted by user action.
+    }
+    // indicates which threshold caused the app to be put into bg restriction.
+    optional Threshold threshold = 3;
+
+    enum StateTracker {
+        UNKNOWN_TRACKER = 0;
+        BATTERY_TRACKER = 1;
+        BATTERY_EXEMPTION_TRACKER = 2;
+        FGS_TRACKER = 3;
+        MEDIA_SESSION_TRACKER = 4;
+        PERMISSION_TRACKER = 5;
+        BROADCAST_EVENTS_TRACKER = 6;
+        BIND_SERVICE_EVENTS_TRACKER = 7;
+    }
+    // indicates the reason/tracker which caused the app to hit the threshold.
+    optional StateTracker tracker = 4;
+
+    message FgsTrackerInfo {
+        // indicates whether an fgs notification was visible for this app or not.
+        optional bool fgs_notification_visible = 1;
+        // total FGS duration for this app.
+        optional int64 fgs_duration = 2;
+    }
+    optional FgsTrackerInfo fgs_tracker_info = 5;
+
+    message BatteryTrackerInfo {
+        // total battery usage within last 24h (percentage)
+        optional int32 battery_24h = 1;
+        // background battery usage (percentage)
+        optional int32 battery_usage_background = 2;
+        // FGS battery usage (percentage)
+        optional int32 battery_usage_fgs = 3;
+    }
+    optional BatteryTrackerInfo battery_tracker_info = 6;
+
+    message BroadcastEventsTrackerInfo {
+        // the number of broadcasts sent by this app.
+        optional int32 broadcasts_sent = 1;
+    }
+    optional BroadcastEventsTrackerInfo broadcast_events_tracker_info = 7;
+
+    message BindServiceEventsTrackerInfo {
+        // the number of bind service requests by this app.
+        optional int32 bind_service_requests = 1;
+    }
+    optional BindServiceEventsTrackerInfo bind_service_events_tracker_info =
+        8;
+
+    // The reasons listed below are defined in PowerExemptionManager.java
+    enum ExemptionReason {
+        // range 0-9 is reserved for default reasons
+        REASON_UNKNOWN = 0;
+        REASON_DENIED = 1;
+        REASON_OTHER = 2;
+        // range 10-49 is reserved for BG-FGS-launch allowed proc states
+        REASON_PROC_STATE_PERSISTENT = 10;
+        REASON_PROC_STATE_PERSISTENT_UI = 11;
+        REASON_PROC_STATE_TOP = 12;
+        REASON_PROC_STATE_BTOP = 13;
+        REASON_PROC_STATE_FGS = 14;
+        REASON_PROC_STATE_BFGS = 15;
+        // range 50-99 is reserved for BG-FGS-launch allowed reasons
+        REASON_UID_VISIBLE = 50;
+        REASON_SYSTEM_UID = 51;
+        REASON_ACTIVITY_STARTER = 52;
+        REASON_START_ACTIVITY_FLAG = 53;
+        REASON_FGS_BINDING = 54;
+        REASON_DEVICE_OWNER = 55;
+        REASON_PROFILE_OWNER = 56;
+        REASON_COMPANION_DEVICE_MANAGER = 57;
+        REASON_BACKGROUND_ACTIVITY_PERMISSION = 58;
+        REASON_BACKGROUND_FGS_PERMISSION = 59;
+        REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION = 60;
+        REASON_INSTR_BACKGROUND_FGS_PERMISSION = 61;
+        REASON_SYSTEM_ALERT_WINDOW_PERMISSION = 62;
+        REASON_DEVICE_DEMO_MODE = 63;
+        REASON_ALLOWLISTED_PACKAGE = 65;
+        REASON_APPOP = 66;
+        REASON_ACTIVITY_VISIBILITY_GRACE_PERIOD = 67;
+        REASON_OP_ACTIVATE_VPN = 68;
+        REASON_OP_ACTIVATE_PLATFORM_VPN = 69;
+        REASON_TEMP_ALLOWED_WHILE_IN_USE = 70;
+        REASON_CURRENT_INPUT_METHOD = 71;
+        // range 100-199 is reserved for public reasons
+        REASON_GEOFENCING = 100;
+        REASON_PUSH_MESSAGING = 101;
+        REASON_PUSH_MESSAGING_OVER_QUOTA = 102;
+        REASON_ACTIVITY_RECOGNITION = 103;
+        REASON_ACCOUNT_TRANSFER = 104;
+        // range 200-299 is reserved for broadcast actions
+        REASON_BOOT_COMPLETED = 200;
+        REASON_PRE_BOOT_COMPLETED = 201;
+        REASON_LOCKED_BOOT_COMPLETED = 202;
+        REASON_BLUETOOTH_BROADCAST = 203;
+        REASON_TIMEZONE_CHANGED = 204;
+        REASON_TIME_CHANGED = 205;
+        REASON_LOCALE_CHANGED = 206;
+        REASON_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED = 207;
+        REASON_REFRESH_SAFETY_SOURCES = 208;
+        // range 300-399 is reserved for other internal reasons
+        REASON_SYSTEM_ALLOW_LISTED = 300;
+        REASON_ALARM_MANAGER_ALARM_CLOCK = 301;
+        REASON_ALARM_MANAGER_WHILE_IDLE = 302;
+        REASON_SERVICE_LAUNCH = 303;
+        REASON_KEY_CHAIN = 304;
+        REASON_PACKAGE_VERIFIER = 305;
+        REASON_SYNC_MANAGER = 306;
+        REASON_DOMAIN_VERIFICATION_V1 = 307;
+        REASON_DOMAIN_VERIFICATION_V2 = 308;
+        REASON_VPN = 309;
+        REASON_NOTIFICATION_SERVICE = 310;
+        REASON_PACKAGE_REPLACED = 311;
+        REASON_LOCATION_PROVIDER = 312;
+        REASON_MEDIA_BUTTON = 313;
+        REASON_EVENT_SMS = 314;
+        REASON_EVENT_MMS = 315;
+        REASON_SHELL = 316;
+        REASON_MEDIA_SESSION_CALLBACK = 317;
+        REASON_ROLE_DIALER = 318;
+        REASON_ROLE_EMERGENCY = 319;
+        REASON_SYSTEM_MODULE = 320;
+        REASON_CARRIER_PRIVILEGED_APP = 321;
+        // app requested to be exempt
+        REASON_OPT_OUT_REQUESTED = 1000;
+    }
+    // indicates if the app is exempt from background restrictions and the reason if applicable.
+    optional ExemptionReason exemption_reason = 9;
+
+    enum OptimizationLevel {
+        UNKNOWN = 0;
+        OPTIMIZED = 1;
+        BACKGROUND_RESTRICTED = 2;
+        NOT_OPTIMIZED = 3;
+    }
+    // the user choice for the optimization level of the app.
+    optional OptimizationLevel opt_level = 10;
+
+    enum TargetSdk {
+        SDK_UNKNOWN = 0;
+        SDK_PRE_S = 1;
+        SDK_S = 2;
+        SDK_T = 3;
+    }
+    // indicates the target sdk level for this app.
+    optional TargetSdk target_sdk = 11;
+
+    // indicates if the current device is a low ram device.
+    optional bool low_mem_device = 12;
+}
+
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 976c02e..4075c5f 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2086,7 +2086,7 @@
 
     <!-- @SystemApi @hide Allows applications to register network factory or agent -->
     <permission android:name="android.permission.NETWORK_FACTORY"
-                android:protectionLevel="signature" />
+                android:protectionLevel="signature|role" />
 
     <!-- @SystemApi @hide Allows applications to access network stats provider -->
     <permission android:name="android.permission.NETWORK_STATS_PROVIDER"
@@ -2275,13 +2275,13 @@
          @hide
     -->
     <permission android:name="android.permission.BLUETOOTH_MAP"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|role" />
 
     <!-- Allows bluetooth stack to access files
          @hide This should only be used by Bluetooth apk.
     -->
     <permission android:name="android.permission.BLUETOOTH_STACK"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|role" />
 
     <!-- Allows uhid write access for creating virtual input devices
          @hide
@@ -2552,7 +2552,7 @@
     <!-- Allows access to configure network interfaces, configure/use IPSec, etc.
          @hide -->
     <permission android:name="android.permission.NET_ADMIN"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|role" />
 
     <!-- Allows registration for remote audio playback. @hide -->
     <permission android:name="android.permission.REMOTE_AUDIO_PLAYBACK"
@@ -2676,7 +2676,7 @@
     <!-- Allows listen permission to always reported system signal strength.
          @hide Used internally. -->
     <permission android:name="android.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|role" />
 
     <!-- @SystemApi Protects the ability to register any PhoneAccount with
          PhoneAccount#CAPABILITY_SIM_SUBSCRIPTION. This capability indicates that the PhoneAccount
@@ -3911,7 +3911,7 @@
          Not for use by third party apps.
          @hide -->
     <permission android:name="android.permission.MANAGE_APP_OPS_MODES"
-        android:protectionLevel="signature|installer|verifier" />
+        android:protectionLevel="signature|installer|verifier|role" />
 
     <!-- @SystemApi Allows an application to open windows that are for use by parts
          of the system user interface.
@@ -4792,7 +4792,7 @@
     <!-- Allows an application to manage the companion devices.
          @hide -->
     <permission android:name="android.permission.MANAGE_COMPANION_DEVICES"
-                android:protectionLevel="signature" />
+                android:protectionLevel="signature|role" />
 
     <!-- Allows an application to subscribe to notifications about the presence status change
          of their associated companion device
@@ -5041,7 +5041,7 @@
     <!-- @TestApi Allows an application to query audio related state.
          @hide -->
     <permission android:name="android.permission.QUERY_AUDIO_STATE"
-                android:protectionLevel="signature" />
+                android:protectionLevel="signature|role" />
 
     <!-- Allows an application to modify what effects are applied to all audio
          (matching certain criteria) from any application.
@@ -5114,7 +5114,7 @@
         @hide
     -->
    <permission android:name="android.permission.DEVICE_POWER"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|role" />
 
     <!-- Allows toggling battery saver on the system.
          Superseded by DEVICE_POWER permission. @hide @SystemApi
@@ -5140,7 +5140,7 @@
 
    <!-- @hide Allows low-level access to tun tap driver -->
     <permission android:name="android.permission.NET_TUNNELING"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|role" />
 
     <!-- Run as a manufacturer test application, running as the root user.
          Only available when the device is running in manufacturer test mode.
@@ -6007,7 +6007,7 @@
     <!-- @SystemApi Allows an application to manage the cloudsearch service.
           @hide  <p>Not for use by third-party applications.</p> -->
     <permission android:name="android.permission.MANAGE_CLOUDSEARCH"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|privileged|role" />
 
     <!-- @SystemApi Allows an application to manage the music recognition service.
          @hide  <p>Not for use by third-party applications.</p> -->
diff --git a/core/res/OWNERS b/core/res/OWNERS
index ca8b3f8..95d2712 100644
--- a/core/res/OWNERS
+++ b/core/res/OWNERS
@@ -42,3 +42,6 @@
 # PowerProfile
 per-file res/xml/power_profile.xml = file:/BATTERY_STATS_OWNERS
 per-file res/xml/power_profile_test.xml = file:/BATTERY_STATS_OWNERS
+
+# Telephony
+per-file res/values/config_telephony.xml = file:/platform/frameworks/opt/telephony:/OWNERS
diff --git a/core/res/res/layout/accessibility_enable_service_encryption_warning.xml b/core/res/res/layout/accessibility_enable_service_warning.xml
similarity index 94%
rename from core/res/res/layout/accessibility_enable_service_encryption_warning.xml
rename to core/res/res/layout/accessibility_enable_service_warning.xml
index 4000516..01ef101 100644
--- a/core/res/res/layout/accessibility_enable_service_encryption_warning.xml
+++ b/core/res/res/layout/accessibility_enable_service_warning.xml
@@ -54,14 +54,6 @@
                 android:fontFamily="google-sans-medium"/>
 
             <TextView
-                android:id="@+id/accessibility_encryption_warning"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:padding="10dip"
-                android:textAlignment="viewStart"
-                android:textAppearance="?android:attr/textAppearanceMedium"/>
-
-            <TextView
                 android:id="@+id/accessibility_permissionDialog_description"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 2f77eb0..d2f31b0 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -6960,9 +6960,13 @@
         <!-- Special option for window animations: whether window should have rounded corners.
              @see ScreenDecorationsUtils#getWindowCornerRadius(Resources) -->
         <attr name="hasRoundedCorners" format="boolean" />
+        <!-- Special option for window animations: whether to show a background behind the animating
+             windows. By default the window's background is used unless overridden by the
+             animation. -->
+        <attr name="showBackdrop" format="boolean" />
         <!-- Special option for window animations: whether the window's background should be used as
              a background to the animation. -->
-        <attr name="showBackground" format="boolean" />
+        <attr name="backdropColor" format="color" />
     </declare-styleable>
 
     <declare-styleable name="AnimationSet">
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 29ca3bf..42f789b 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -690,6 +690,11 @@
            will apply, regardless of device state. -->
     <string-array name="config_perDeviceStateRotationLockDefaults" />
 
+    <!-- Dock behavior -->
+
+    <!-- Control whether to start dream immediately upon docking even if the lockscreen is unlocked.
+         This defaults to true to be consistent with historical behavior. -->
+    <bool name="config_startDreamImmediatelyOnDock">true</bool>
 
     <!-- Desk dock behavior -->
 
@@ -1372,10 +1377,6 @@
     <integer name="config_screenBrightnessDoze">1</integer>
     <item name="config_screenBrightnessDozeFloat" format="float" type="dimen">0.0</item>
 
-    <!-- Delay that allows some content to arrive at the display before switching
-         from DOZE to ON. -->
-    <integer name="config_wakeUpDelayDoze">0</integer>
-
     <!-- Whether or not to skip the initial brightness ramps when the display transitions to
          STATE_ON. Setting this to true will skip the brightness ramp to the last stored active
          brightness value and will repeat for the following ramp if autobrightness is enabled. -->
@@ -2702,41 +2703,10 @@
     <string-array name="config_mobile_tcp_buffers">
     </string-array>
 
-    <!-- Configure tcp buffer sizes per network type in the form:
-         network-type:rmem_min,rmem_def,rmem_max,wmem_min,wmem_def,wmem_max
-
-         The network-type must be a valid DataConfigNetworkType value. If no value is found for the
-         network-type in use, config_tcp_buffers will be used instead.
-    -->
-    <string-array name="config_network_type_tcp_buffers">
-    </string-array>
-
-    <!-- Configure tcp buffer sizes in the form:
-         rmem_min,rmem_def,rmem_max,wmem_min,wmem_def,wmem_max
-         If this is configured as an empty string, the system default will be applied.
-
-         For now this config is used by mobile data only. In the future it should be
-         used by Wi-Fi as well.
-    -->
-    <string name="config_tcp_buffers" translatable="false"></string>
-
     <!-- Configure ethernet tcp buffersizes in the form:
          rmem_min,rmem_def,rmem_max,wmem_min,wmem_def,wmem_max -->
     <string name="config_ethernet_tcp_buffers" translatable="false">524288,1048576,3145728,524288,1048576,2097152</string>
 
-    <!-- What source to use to estimate link upstream and downstream bandwidth capacities.
-         Default is bandwidth_estimator.
-         Values are bandwidth_estimator, carrier_config and modem. -->
-    <string name="config_bandwidthEstimateSource">bandwidth_estimator</string>
-
-    <!-- Whether force to enable telephony new data stack or not -->
-    <bool name="config_force_enable_telephony_new_data_stack">true</bool>
-
-    <!-- Whether to adopt the predefined handover policies for IWLAN.
-         {@see CarrierConfigManager#KEY_IWLAN_HANDOVER_POLICY_STRING_ARRAY}
-    -->
-    <bool name="config_enable_iwlan_handover_policy">true</bool>
-
     <!-- Whether WiFi display is supported by this device.
          There are many prerequisites for this feature to work correctly.
          Here are a few of them:
@@ -2948,6 +2918,11 @@
         com.android.settings.intelligence
     </string>
 
+    <!-- System bluetooth stack package name -->
+    <string name="config_systemBluetoothStack" translatable="false">
+        com.android.bluetooth.services
+    </string>
+
     <!-- Flag indicating that the media framework should not allow changes or mute on any
          stream or global volumes. -->
     <bool name="config_useFixedVolume">false</bool>
@@ -3316,27 +3291,6 @@
     <!-- String array containing numbers that shouldn't be logged. Country-specific. -->
     <string-array name="unloggable_phone_numbers" />
 
-    <!-- Cellular data service package name to bind to by default. If none is specified in an overlay, an
-         empty string is passed in -->
-    <string name="config_wwan_data_service_package" translatable="false">com.android.phone</string>
-
-    <!-- IWLAN data service package name to bind to by default. If none is specified in an overlay, an
-         empty string is passed in -->
-    <string name="config_wlan_data_service_package" translatable="false"></string>
-
-    <!-- Boolean indicating whether the Iwlan data service supports persistence of iwlan ipsec
-         tunnels across service restart. If iwlan tunnels are not persisted across restart,
-         Framework will clean up dangling data connections when service restarts -->
-    <bool name="config_wlan_data_service_conn_persistence_on_restart">true</bool>
-
-    <!-- Cellular data service class name to bind to by default. If none is specified in an overlay, an
-         empty string is passed in -->
-    <string name="config_wwan_data_service_class" translatable="false"></string>
-
-    <!-- IWLAN data service class name to bind to by default. If none is specified in an overlay, an
-         empty string is passed in -->
-    <string name="config_wlan_data_service_class" translatable="false"></string>
-
     <bool name="config_networkSamplingWakesDevice">true</bool>
 
     <!--From SmsMessage-->
@@ -3436,11 +3390,6 @@
         <item>2</item>    <!-- USAGE_SETTING_DATA_CENTRIC -->
     </integer-array>
 
-    <!-- When a radio power off request is received, we will delay completing the request until
-         either IMS moves to the deregistered state or the timeout defined by this configuration
-         elapses. If 0, this feature is disabled and we do not delay radio power off requests.-->
-    <integer name="config_delay_for_ims_dereg_millis">0</integer>
-
     <!--Thresholds for LTE dbm in status bar-->
     <integer-array translatable="false" name="config_lteDbmThresholds">
         <item>-140</item>    <!-- SIGNAL_STRENGTH_NONE_OR_UNKNOWN -->
@@ -4404,24 +4353,6 @@
 
     <bool name="config_keepRestrictedProfilesInBackground">true</bool>
 
-    <!-- Cellular network service package name to bind to by default. -->
-    <string name="config_wwan_network_service_package" translatable="false">com.android.phone</string>
-
-    <!-- Cellular network service class name to bind to by default.-->
-    <string name="config_wwan_network_service_class" translatable="false"></string>
-
-    <!-- IWLAN network service package name to bind to by default. If none is specified in an overlay, an
-         empty string is passed in -->
-    <string name="config_wlan_network_service_package" translatable="false"></string>
-
-    <!-- IWLAN network service class name to bind to by default. If none is specified in an overlay, an
-         empty string is passed in -->
-    <string name="config_wlan_network_service_class" translatable="false"></string>
-    <!-- Telephony qualified networks service package name to bind to by default. -->
-    <string name="config_qualified_networks_service_package" translatable="false"></string>
-
-    <!-- Telephony qualified networks service class name to bind to by default. -->
-    <string name="config_qualified_networks_service_class" translatable="false"></string>
     <!-- Wear devices: Controls the radios affected by Activity Mode. -->
     <string-array name="config_wearActivityModeRadios">
         <item>"wifi"</item>
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
new file mode 100644
index 0000000..cd3578c
--- /dev/null
+++ b/core/res/res/values/config_telephony.xml
@@ -0,0 +1,112 @@
+<?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>
+    <!-- This file defines Android telephony related resources -->
+
+    <!-- Whether force to enable telephony new data stack or not -->
+    <bool name="config_force_enable_telephony_new_data_stack">true</bool>
+    <java-symbol type="bool" name="config_force_enable_telephony_new_data_stack" />
+
+    <!-- Configure tcp buffer sizes per network type in the form:
+         network-type:rmem_min,rmem_def,rmem_max,wmem_min,wmem_def,wmem_max
+
+         The network-type must be a valid DataConfigNetworkType value. If no value is found for the
+         network-type in use, config_tcp_buffers will be used instead.
+    -->
+    <string-array name="config_network_type_tcp_buffers">
+    </string-array>
+    <java-symbol type="array" name="config_network_type_tcp_buffers" />
+
+    <!-- Configure tcp buffer sizes in the form:
+         rmem_min,rmem_def,rmem_max,wmem_min,wmem_def,wmem_max
+         If this is configured as an empty string, the system default will be applied.
+    -->
+    <string name="config_tcp_buffers" translatable="false"></string>
+    <java-symbol type="string"  name="config_tcp_buffers" />
+
+    <!-- What source to use to estimate link upstream and downstream bandwidth capacities.
+         Default is bandwidth_estimator.
+         Values are bandwidth_estimator, carrier_config and modem. -->
+    <string name="config_bandwidthEstimateSource">bandwidth_estimator</string>
+    <java-symbol type="string" name="config_bandwidthEstimateSource" />
+
+    <!-- Whether to adopt the predefined handover policies for IWLAN.
+         {@see CarrierConfigManager#KEY_IWLAN_HANDOVER_POLICY_STRING_ARRAY}
+    -->
+    <bool name="config_enable_iwlan_handover_policy">true</bool>
+    <java-symbol type="bool" name="config_enable_iwlan_handover_policy" />
+
+    <!-- When a radio power off request is received, we will delay completing the request until
+         either IMS moves to the deregistered state or the timeout defined by this configuration
+         elapses. If 0, this feature is disabled and we do not delay radio power off requests.-->
+    <integer name="config_delay_for_ims_dereg_millis">0</integer>
+    <java-symbol type="integer" name="config_delay_for_ims_dereg_millis" />
+
+    <!-- Boolean indicating whether the Iwlan data service supports persistence of iwlan ipsec
+         tunnels across service restart. If iwlan tunnels are not persisted across restart,
+         Framework will clean up dangling data connections when service restarts -->
+    <bool name="config_wlan_data_service_conn_persistence_on_restart">true</bool>
+    <java-symbol type="bool" name="config_wlan_data_service_conn_persistence_on_restart" />
+
+    <!-- Cellular data service package name to bind to by default. If none is specified in an
+         overlay, an empty string is passed in -->
+    <string name="config_wwan_data_service_package" translatable="false">com.android.phone</string>
+    <java-symbol type="string" name="config_wwan_data_service_package" />
+
+    <!-- IWLAN data service package name to bind to by default. If none is specified in an overlay,
+         an empty string is passed in -->
+    <string name="config_wlan_data_service_package" translatable="false"></string>
+    <java-symbol type="string" name="config_wlan_data_service_package" />
+
+    <!-- Cellular data service class name to bind to by default. If none is specified in an overlay,
+         an empty string is passed in -->
+    <string name="config_wwan_data_service_class" translatable="false"></string>
+    <java-symbol type="string" name="config_wwan_data_service_class" />
+
+    <!-- IWLAN data service class name to bind to by default. If none is specified in an overlay, an
+         empty string is passed in -->
+    <string name="config_wlan_data_service_class" translatable="false"></string>
+    <java-symbol type="string" name="config_wlan_data_service_class" />
+
+    <!-- Cellular network service package name to bind to by default. -->
+    <string name="config_wwan_network_service_package" translatable="false">
+        com.android.phone
+    </string>
+    <java-symbol type="string" name="config_wwan_network_service_package" />
+
+    <!-- Cellular network service class name to bind to by default.-->
+    <string name="config_wwan_network_service_class" translatable="false"></string>
+    <java-symbol type="string" name="config_wwan_network_service_class" />
+
+    <!-- IWLAN network service package name to bind to by default. If none is specified in an
+         overlay, an empty string is passed in -->
+    <string name="config_wlan_network_service_package" translatable="false"></string>
+    <java-symbol type="string" name="config_wlan_network_service_package" />
+
+    <!-- IWLAN network service class name to bind to by default. If none is specified in an overlay,
+         an empty string is passed in -->
+    <string name="config_wlan_network_service_class" translatable="false"></string>
+    <java-symbol type="string" name="config_wlan_network_service_class" />
+
+    <!-- Telephony qualified networks service package name to bind to by default. -->
+    <string name="config_qualified_networks_service_package" translatable="false"></string>
+    <java-symbol type="string" name="config_qualified_networks_service_package" />
+
+    <!-- Telephony qualified networks service class name to bind to by default. -->
+    <string name="config_qualified_networks_service_class" translatable="false"></string>
+    <java-symbol type="string" name="config_qualified_networks_service_class" />
+</resources>
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index b37c901..86bad7f 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -126,7 +126,7 @@
     <public name="allowGameDownscaling" />
     <public name="allowGameFpsOverride" />
     <public name="localeConfig" />
-    <public name="showBackground" />
+    <public name="showBackdrop" />
     <public name="removed_useTargetActivityForQuickAccess"/>
     <public name="removed_inheritKeyStoreKeys" />
     <public name="preferKeepClear" />
@@ -152,6 +152,7 @@
     <public name="maxDrawableWidth" />
     <!-- @hide -->
     <public name="maxDrawableHeight" />
+    <public name="backdropColor" />
   </staging-public-group>
 
   <staging-public-group type="id" first-id="0x01de0000">
@@ -183,6 +184,8 @@
     <public name="safety_protection_display_text" />
     <!-- @hide @SystemApi -->
     <public name="config_systemSettingsIntelligence" />
+    <!-- @hide -->
+    <public name="config_systemBluetoothStack" />
   </staging-public-group>
 
   <staging-public-group type="dimen" first-id="0x01db0000">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 86591e8..3d73a02 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4768,13 +4768,6 @@
         <xliff:g id="service" example="TalkBack">%1$s</xliff:g> to have full control of your
         device?</string>
 
-    <!-- Warning that the device data will not be encrypted with password or PIN if
-    enabling an accessibility service and there is a secure lock setup. [CHAR LIMIT=NONE] -->
-    <string name="accessibility_enable_service_encryption_warning">If you turn on
-        <xliff:g id="service" example="TalkBack">%1$s</xliff:g>, your device won’t use your screen
-        lock to enhance data encryption.
-    </string>
-
     <!-- Warning description that explains that it's appropriate for accessibility
      services to have full control to help users with accessibility needs. [CHAR LIMIT=NONE] -->
     <string name="accessibility_service_warning_description">Full control is appropriate for apps
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 8fa5dd2..dd69fa0 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -294,17 +294,6 @@
   <java-symbol type="bool" name="config_enableBurnInProtection" />
   <java-symbol type="bool" name="config_hotswapCapable" />
   <java-symbol type="bool" name="config_mms_content_disposition_support" />
-  <java-symbol type="string" name="config_wwan_network_service_package" />
-  <java-symbol type="string" name="config_wlan_network_service_package" />
-  <java-symbol type="string" name="config_wwan_network_service_class" />
-  <java-symbol type="string" name="config_wlan_network_service_class" />
-  <java-symbol type="bool" name="config_wlan_data_service_conn_persistence_on_restart" />
-  <java-symbol type="string" name="config_wwan_data_service_package" />
-  <java-symbol type="string" name="config_wlan_data_service_package" />
-  <java-symbol type="string" name="config_wwan_data_service_class" />
-  <java-symbol type="string" name="config_wlan_data_service_class" />
-  <java-symbol type="string" name="config_qualified_networks_service_package" />
-  <java-symbol type="string" name="config_qualified_networks_service_class" />
   <java-symbol type="bool" name="config_networkSamplingWakesDevice" />
   <java-symbol type="bool" name="config_showMenuShortcutsWhenKeyboardPresent" />
   <java-symbol type="bool" name="config_sip_wifi_only" />
@@ -472,10 +461,6 @@
   <java-symbol type="integer" name="config_safe_media_volume_usb_mB" />
   <java-symbol type="integer" name="config_mobile_mtu" />
   <java-symbol type="array"   name="config_mobile_tcp_buffers" />
-  <java-symbol type="array"   name="config_network_type_tcp_buffers" />
-  <java-symbol type="string"  name="config_tcp_buffers" />
-  <java-symbol type="bool" name="config_force_enable_telephony_new_data_stack" />
-  <java-symbol type="bool" name="config_enable_iwlan_handover_policy" />
   <java-symbol type="integer" name="config_volte_replacement_rat"/>
   <java-symbol type="integer" name="config_valid_wappush_index" />
   <java-symbol type="integer" name="config_overrideHasPermanentMenuKey" />
@@ -489,10 +474,8 @@
   <java-symbol type="integer" name="config_num_physical_slots" />
   <java-symbol type="integer" name="config_default_cellular_usage_setting" />
   <java-symbol type="array" name="config_supported_cellular_usage_settings" />
-  <java-symbol type="integer" name="config_delay_for_ims_dereg_millis" />
   <java-symbol type="array" name="config_integrityRuleProviderPackages" />
   <java-symbol type="bool" name="config_useAssistantVolume" />
-  <java-symbol type="string" name="config_bandwidthEstimateSource" />
   <java-symbol type="integer" name="config_smartSelectionInitializedTimeoutMillis" />
   <java-symbol type="integer" name="config_smartSelectionInitializingTimeoutMillis" />
   <java-symbol type="integer" name="config_preferKeepClearForFocusDelayMillis" />
@@ -1732,6 +1715,7 @@
   <java-symbol type="attr" name="dialogTitleIconsDecorLayout" />
   <java-symbol type="bool" name="config_allowAllRotations" />
   <java-symbol type="bool" name="config_annoy_dianne" />
+  <java-symbol type="bool" name="config_startDreamImmediatelyOnDock" />
   <java-symbol type="bool" name="config_carDockEnablesAccelerometer" />
   <java-symbol type="bool" name="config_customUserSwitchUi" />
   <java-symbol type="bool" name="config_deskDockEnablesAccelerometer" />
@@ -2319,6 +2303,7 @@
   <java-symbol type="drawable" name="scrubber_control_disabled_holo" />
   <java-symbol type="drawable" name="scrubber_control_selector_holo" />
   <java-symbol type="drawable" name="scrubber_progress_horizontal_holo_dark" />
+  <java-symbol type="drawable" name="progress_small_material" />
   <java-symbol type="string" name="chooseUsbActivity" />
   <java-symbol type="string" name="ext_media_badremoval_notification_message" />
   <java-symbol type="string" name="ext_media_badremoval_notification_title" />
@@ -3487,14 +3472,12 @@
   <java-symbol type="string" name="accessibility_edit_shortcut_menu_volume_title" />
   <java-symbol type="string" name="accessibility_uncheck_legacy_item_warning" />
 
-  <java-symbol type="layout" name="accessibility_enable_service_encryption_warning" />
+  <java-symbol type="layout" name="accessibility_enable_service_warning" />
   <java-symbol type="id" name="accessibility_permissionDialog_icon" />
   <java-symbol type="id" name="accessibility_permissionDialog_title" />
-  <java-symbol type="id" name="accessibility_encryption_warning" />
   <java-symbol type="id" name="accessibility_permission_enable_allow_button" />
   <java-symbol type="id" name="accessibility_permission_enable_deny_button" />
   <java-symbol type="string" name="accessibility_enable_service_title" />
-  <java-symbol type="string" name="accessibility_enable_service_encryption_warning" />
 
   <java-symbol type="layout" name="accessibility_shortcut_chooser_item" />
   <java-symbol type="id" name="accessibility_shortcut_target_checkbox" />
@@ -3969,7 +3952,6 @@
   <java-symbol type="string" name="config_misprovisionedBrandValue" />
 
   <java-symbol type="integer" name="db_wal_truncate_size" />
-  <java-symbol type="integer" name="config_wakeUpDelayDoze" />
 
   <!-- For Bluetooth AbsoluteVolume -->
   <java-symbol type="fraction" name="config_prescaleAbsoluteVolume_index1" />
diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
index 3e261a7..50639be 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
@@ -157,7 +157,7 @@
                 .setPendingResults(resultInfoList()).setPendingNewIntents(referrerIntentList())
                 .setIsForward(true).setAssistToken(assistToken)
                 .setShareableActivityToken(shareableActivityToken)
-                .build();
+                .setTaskFragmentToken(new Binder()).build();
 
         LaunchActivityItem emptyItem = new LaunchActivityItemBuilder().build();
         LaunchActivityItem item = itemSupplier.get();
diff --git a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
index 1467fed..26d9628 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
@@ -110,6 +110,7 @@
         private IBinder mAssistToken;
         private IBinder mShareableActivityToken;
         private boolean mLaunchedFromBubble;
+        private IBinder mTaskFragmentToken;
 
         LaunchActivityItemBuilder setIntent(Intent intent) {
             mIntent = intent;
@@ -206,13 +207,18 @@
             return this;
         }
 
+        LaunchActivityItemBuilder setTaskFragmentToken(IBinder taskFragmentToken) {
+            mTaskFragmentToken = taskFragmentToken;
+            return this;
+        }
+
         LaunchActivityItem build() {
             return LaunchActivityItem.obtain(mIntent, mIdent, mInfo,
                     mCurConfig, mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor,
                     mProcState, mState, mPersistentState, mPendingResults, mPendingNewIntents,
                     mActivityOptions, mIsForward, mProfilerInfo, mAssistToken,
                     null /* activityClientController */, mShareableActivityToken,
-                    mLaunchedFromBubble);
+                    mLaunchedFromBubble, mTaskFragmentToken);
         }
     }
 }
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index beadc446..8276d10 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -203,6 +203,7 @@
                 .setPendingResults(resultInfoList()).setActivityOptions(ActivityOptions.makeBasic())
                 .setPendingNewIntents(referrerIntentList()).setIsForward(true)
                 .setAssistToken(new Binder()).setShareableActivityToken(new Binder())
+                .setTaskFragmentToken(new Binder())
                 .build();
 
         writeAndPrepareForReading(item);
diff --git a/core/tests/coretests/src/android/text/TextUtilsTest.java b/core/tests/coretests/src/android/text/TextUtilsTest.java
index a0fc349..c4bcfd4 100644
--- a/core/tests/coretests/src/android/text/TextUtilsTest.java
+++ b/core/tests/coretests/src/android/text/TextUtilsTest.java
@@ -43,6 +43,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
@@ -782,6 +783,81 @@
     }
 
     @Test
+    public void truncateStringForUtf8Storage() {
+        assertEquals("", TextUtils.truncateStringForUtf8Storage("abc", 0));
+
+        //================ long normal case ================
+        StringBuilder builder = new StringBuilder();
+
+        int n = 50;
+        for (int i = 0; i < 2 * n; i++) {
+            builder.append("哈");
+        }
+        String initial = builder.toString();
+        String result = TextUtils.truncateStringForUtf8Storage(initial, n);
+
+        // Result should be the beginning of initial
+        assertTrue(initial.startsWith(result));
+
+        // Result should take less than n bytes in UTF-8
+        assertTrue(result.getBytes(StandardCharsets.UTF_8).length <= n);
+
+        // result + the next codePoint should take strictly more than
+        // n bytes in UTF-8
+        assertTrue(initial.substring(0, initial.offsetByCodePoints(result.length(), 1))
+                .getBytes(StandardCharsets.UTF_8).length > n);
+
+        // =================== short normal case =====================
+        String s = "sf\u20ACgk\u00E9ls\u00E9fg";
+        result = TextUtils.truncateStringForUtf8Storage(s, 100);
+        assertEquals(s, result);
+    }
+
+    @Test
+    public void testTruncateInMiddleOfSurrogate() {
+        StringBuilder builder = new StringBuilder();
+        String beginning = "a";
+        builder.append(beginning);
+        builder.append(Character.toChars(0x1D11E));
+
+        String result = TextUtils.truncateStringForUtf8Storage(builder.toString(), 3);
+
+        // \u1D11E is a surrogate and needs 4 bytes in UTF-8. beginning == "a" uses
+        // only 1 bytes in UTF8
+        // As we allow only 3 bytes for the whole string, so just 2 for this
+        // codePoint, there is not enough place and the string will be truncated
+        // just before it
+        assertEquals(beginning, result);
+    }
+
+    @Test
+    public void testTruncateInMiddleOfChar() {
+        StringBuilder builder = new StringBuilder();
+        String beginning = "a";
+        builder.append(beginning);
+        builder.append(Character.toChars(0x20AC));
+
+        String result = TextUtils.truncateStringForUtf8Storage(builder.toString(), 3);
+
+        // Like above, \u20AC uses 3 bytes in UTF-8, with "beginning", that makes
+        // 4 bytes so it is too big and should be truncated
+        assertEquals(beginning, result);
+    }
+
+    @Test
+    public void testTruncateSubString() {
+        String test = "sdgkl;hjsl;gjhdgkljdfhglkdj";
+        String sub = test.substring(10, 20);
+        String res = TextUtils.truncateStringForUtf8Storage(sub, 255);
+        assertEquals(sub, res);
+    }
+
+    @Test(expected = IndexOutOfBoundsException.class)
+    public void truncateStringForUtf8StorageThrowsExceptionForNegativeSize() {
+        TextUtils.truncateStringForUtf8Storage("abc", -1);
+    }
+
+    @Test
     public void length() {
         assertEquals(0, TextUtils.length(null));
         assertEquals(0, TextUtils.length(""));
diff --git a/core/tests/coretests/src/android/window/BackNavigationTest.java b/core/tests/coretests/src/android/window/BackNavigationTest.java
index 94a149b..678eef55 100644
--- a/core/tests/coretests/src/android/window/BackNavigationTest.java
+++ b/core/tests/coretests/src/android/window/BackNavigationTest.java
@@ -111,12 +111,7 @@
         CountDownLatch backRegisteredLatch = new CountDownLatch(1);
         mScenario.onActivity(activity -> {
             activity.getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
-                    0, new OnBackInvokedCallback() {
-                        @Override
-                        public void onBackInvoked() {
-                            backInvokedLatch.countDown();
-                        }
-                    }
+                    0, backInvokedLatch::countDown
             );
             backRegisteredLatch.countDown();
         });
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index 212f4ed..6aa5be5 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -56,9 +56,9 @@
     private IWindow mWindow;
     private WindowOnBackInvokedDispatcher mDispatcher;
     @Mock
-    private OnBackInvokedCallback mCallback1;
+    private OnBackAnimationCallback mCallback1;
     @Mock
-    private OnBackInvokedCallback mCallback2;
+    private OnBackAnimationCallback mCallback2;
 
     @Before
     public void setUp() throws Exception {
diff --git a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
index b006a16..8d3751e 100644
--- a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
+++ b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
@@ -345,7 +345,7 @@
                     null /* pendingResults */, null /* pendingNewIntents */,
                     null /* activityOptions */, true /* isForward */, null /* profilerInfo */,
                     mThread /* client */, null /* asssitToken */, null /* shareableActivityToken */,
-                    false /* launchedFromBubble */);
+                    false /* launchedFromBubble */, null /* taskfragmentToken */);
         }
 
         @Override
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 4727416..04ead1b 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -33,6 +33,30 @@
         <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
     </privapp-permissions>
 
+    <privapp-permissions package="com.android.bluetooth.services">
+        <permission name="android.permission.DUMP"/>
+        <permission name="android.permission.MODIFY_AUDIO_ROUTING"/>
+        <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
+        <permission name="android.permission.TETHER_PRIVILEGED"/>
+        <permission name="android.permission.CALL_PRIVILEGED"/>
+        <permission name="android.permission.MODIFY_PHONE_STATE"/>
+        <permission name="android.permission.INTERACT_ACROSS_USERS"/>
+        <permission name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
+        <permission name="android.permission.CONTROL_INCALL_EXPERIENCE"/>
+        <permission name="android.permission.UPDATE_DEVICE_STATS"/>
+        <permission name="android.permission.PACKAGE_USAGE_STATS"/>
+        <permission name="android.permission.NFC_HANDOVER_STATUS"/>
+        <permission name="android.permission.CONNECTIVITY_INTERNAL"/>
+        <permission name="android.permission.BLUETOOTH_PRIVILEGED"/>
+        <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
+        <permission name="android.permission.MEDIA_CONTENT_CONTROL"/>
+        <permission name="android.permission.REAL_GET_TASKS"/>
+        <permission name="android.permission.MANAGE_USERS"/>
+        <permission name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND"/>
+        <permission name="android.permission.WRITE_APN_SETTINGS"/>
+        <permission name="android.permission.UPDATE_APP_OPS_STATS"/>
+    </privapp-permissions>
+
     <privapp-permissions package="com.android.backupconfirm">
         <permission name="android.permission.BACKUP"/>
         <permission name="android.permission.CRYPT_KEEPER"/>
@@ -116,24 +140,6 @@
         <permission name="android.permission.PACKAGE_USAGE_STATS"/>
     </privapp-permissions>
 
-    <privapp-permissions package="com.android.permissioncontroller">
-        <permission name="android.permission.CLEAR_APP_CACHE"/>
-        <permission name="android.permission.MANAGE_USERS"/>
-        <permission name="android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS"/>
-        <permission name="android.permission.GET_APP_OPS_STATS"/>
-        <permission name="android.permission.UPDATE_APP_OPS_STATS"/>
-        <permission name="android.permission.REQUEST_INCIDENT_REPORT_APPROVAL"/>
-        <permission name="android.permission.APPROVE_INCIDENT_REPORTS"/>
-        <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
-        <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME" />
-        <permission name="android.permission.PACKAGE_USAGE_STATS" />
-        <permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" />
-        <permission name="android.permission.MODIFY_AUDIO_ROUTING" />
-        <permission name="android.permission.WRITE_SECURE_SETTINGS" />
-        <permission name="android.permission.READ_SAFETY_CENTER_STATUS" />
-        <permission name="android.permission.SEND_SAFETY_CENTER_UPDATE" />
-    </privapp-permissions>
-
     <privapp-permissions package="com.android.phone">
         <permission name="android.permission.ACCESS_IMS_CALL_SERVICE"/>
         <permission name="android.permission.BIND_CARRIER_MESSAGING_SERVICE"/>
@@ -291,12 +297,14 @@
         <permission name="android.permission.LOCAL_MAC_ADDRESS"/>
         <permission name="android.permission.MANAGE_ACCESSIBILITY"/>
         <permission name="android.permission.MANAGE_DEVICE_ADMINS"/>
+        <permission name="android.permission.ACCESS_FPS_COUNTER"/>
         <permission name="android.permission.MANAGE_GAME_MODE"/>
         <permission name="android.permission.MANAGE_GAME_ACTIVITY" />
         <permission name="android.permission.MANAGE_LOW_POWER_STANDBY" />
         <permission name="android.permission.MANAGE_ROLLBACKS"/>
         <permission name="android.permission.MANAGE_USB"/>
         <!-- Needed for tests only -->
+        <permission name="android.permission.MANAGE_CLOUDSEARCH" />
         <permission name="android.permission.MANAGE_WALLPAPER_EFFECTS_GENERATION" />
         <permission name="android.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS"/>
         <permission name="android.permission.MODIFY_DAY_NIGHT_MODE"/>
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 3a900e6..80b144a 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -703,6 +703,12 @@
       "group": "WM_DEBUG_STATES",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "-1427392850": {
+      "message": "WindowState: Setting back callback %s (priority: %d) (Client IWindow: %s). (WindowState: %s)",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_BACK_PREVIEW",
+      "at": "com\/android\/server\/wm\/WindowState.java"
+    },
     "-1427184084": {
       "message": "addWindow: New client %s: window=%s Callers=%s",
       "level": "VERBOSE",
diff --git a/framework-jarjar-rules.txt b/framework-jarjar-rules.txt
index be21f4e..03b268d 100644
--- a/framework-jarjar-rules.txt
+++ b/framework-jarjar-rules.txt
@@ -5,3 +5,6 @@
 # Framework-specific renames.
 rule android.net.wifi.WifiAnnotations* android.internal.wifi.WifiAnnotations@1
 rule com.android.server.vcn.util.** com.android.server.vcn.repackaged.util.@1
+
+# for modules-utils-build dependency
+rule com.android.modules.utils.build.** android.internal.modules.utils.build.@1
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
index 6987401..fdcb7be 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
@@ -31,11 +31,13 @@
 import android.util.SparseIntArray;
 
 import androidx.window.util.BaseDataProducer;
+import androidx.window.util.DataProducer;
 
 import com.android.internal.R;
 
 import java.util.List;
 import java.util.Optional;
+import java.util.Set;
 
 /**
  * An implementation of {@link androidx.window.util.DataProducer} that returns the device's posture
@@ -48,7 +50,6 @@
             DeviceStateManagerFoldingFeatureProducer.class.getSimpleName();
     private static final boolean DEBUG = false;
 
-    private final Context mContext;
     private final SparseIntArray mDeviceStateToPostureMap = new SparseIntArray();
 
     private int mCurrentDeviceState = INVALID_DEVICE_STATE;
@@ -57,9 +58,12 @@
         mCurrentDeviceState = state;
         notifyDataChanged();
     };
+    @NonNull
+    private final DataProducer<String> mRawFoldSupplier;
 
-    public DeviceStateManagerFoldingFeatureProducer(@NonNull Context context) {
-        mContext = context;
+    public DeviceStateManagerFoldingFeatureProducer(@NonNull Context context,
+            @NonNull DataProducer<String> rawFoldSupplier) {
+        mRawFoldSupplier = rawFoldSupplier;
         String[] deviceStatePosturePairs = context.getResources()
                 .getStringArray(R.array.config_device_state_postures);
         for (String deviceStatePosturePair : deviceStatePosturePairs) {
@@ -97,12 +101,21 @@
     @Nullable
     public Optional<List<CommonFoldingFeature>> getData() {
         final int globalHingeState = globalHingeState();
-        String displayFeaturesString = mContext.getResources().getString(
-                R.string.config_display_features);
-        if (TextUtils.isEmpty(displayFeaturesString)) {
+        Optional<String> displayFeaturesString = mRawFoldSupplier.getData();
+        if (displayFeaturesString.isEmpty() || TextUtils.isEmpty(displayFeaturesString.get())) {
             return Optional.empty();
         }
-        return Optional.of(parseListFromString(displayFeaturesString, globalHingeState));
+        return Optional.of(parseListFromString(displayFeaturesString.get(), globalHingeState));
+    }
+
+    @Override
+    protected void onListenersChanged(Set<Runnable> callbacks) {
+        super.onListenersChanged(callbacks);
+        if (callbacks.isEmpty()) {
+            mRawFoldSupplier.removeDataChangedCallback(this::notifyDataChanged);
+        } else {
+            mRawFoldSupplier.addDataChangedCallback(this::notifyDataChanged);
+        }
     }
 
     private int globalHingeState() {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java
new file mode 100644
index 0000000..69ad1ba
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java
@@ -0,0 +1,132 @@
+/*
+ * 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 androidx.window.common;
+
+import android.annotation.NonNull;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
+import android.text.TextUtils;
+
+import androidx.window.util.BaseDataProducer;
+
+import com.android.internal.R;
+
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * Implementation of {@link androidx.window.util.DataProducer} that produces a
+ * {@link String} that can be parsed to a {@link CommonFoldingFeature}.
+ * {@link RawFoldingFeatureProducer} searches for the value in two places. The first check is in
+ * settings where the {@link String} property is saved with the key
+ * {@link RawFoldingFeatureProducer#DISPLAY_FEATURES}. If this value is null or empty then the
+ * value in {@link android.content.res.Resources} is used. If both are empty then
+ * {@link RawFoldingFeatureProducer#getData()} returns an empty object.
+ * {@link RawFoldingFeatureProducer} listens to changes in the setting so that it can override
+ * the system {@link CommonFoldingFeature} data.
+ */
+public final class RawFoldingFeatureProducer extends BaseDataProducer<String> {
+    private static final String DISPLAY_FEATURES = "display_features";
+
+    private final Uri mDisplayFeaturesUri =
+            Settings.Global.getUriFor(DISPLAY_FEATURES);
+
+    private final ContentResolver mResolver;
+    private final ContentObserver mObserver;
+    private final String mResourceFeature;
+    private boolean mRegisteredObservers;
+
+    public RawFoldingFeatureProducer(@NonNull Context context) {
+        mResolver = context.getContentResolver();
+        mObserver = new SettingsObserver();
+        mResourceFeature = context.getResources().getString(R.string.config_display_features);
+    }
+
+    @Override
+    @NonNull
+    public Optional<String> getData() {
+        String displayFeaturesString = getFeatureString();
+        if (displayFeaturesString == null) {
+            return Optional.empty();
+        }
+        return Optional.of(displayFeaturesString);
+    }
+
+    /**
+     * Returns the {@link String} representation for a {@link CommonFoldingFeature} from settings if
+     * present and falls back to the resource value if empty or {@code null}.
+     */
+    private String getFeatureString() {
+        String settingsFeature = Settings.Global.getString(mResolver, DISPLAY_FEATURES);
+        if (TextUtils.isEmpty(settingsFeature)) {
+            return mResourceFeature;
+        }
+        return settingsFeature;
+    }
+
+    @Override
+    protected void onListenersChanged(Set<Runnable> callbacks) {
+        if (callbacks.isEmpty()) {
+            unregisterObserversIfNeeded();
+        } else {
+            registerObserversIfNeeded();
+        }
+    }
+
+    /**
+     * Registers settings observers, if needed. When settings observers are registered for this
+     * producer callbacks for changes in data will be triggered.
+     */
+    private void registerObserversIfNeeded() {
+        if (mRegisteredObservers) {
+            return;
+        }
+        mRegisteredObservers = true;
+        mResolver.registerContentObserver(mDisplayFeaturesUri, false /* notifyForDescendants */,
+                mObserver /* ContentObserver */);
+    }
+
+    /**
+     * Unregisters settings observers, if needed. When settings observers are unregistered for this
+     * producer callbacks for changes in data will not be triggered.
+     */
+    private void unregisterObserversIfNeeded() {
+        if (!mRegisteredObservers) {
+            return;
+        }
+        mRegisteredObservers = false;
+        mResolver.unregisterContentObserver(mObserver);
+    }
+
+    private final class SettingsObserver extends ContentObserver {
+        SettingsObserver() {
+            super(new Handler(Looper.getMainLooper()));
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            if (mDisplayFeaturesUri.equals(uri)) {
+                notifyDataChanged();
+            }
+        }
+    }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDisplayFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDisplayFeatureProducer.java
deleted file mode 100644
index 0e696eb..0000000
--- a/libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDisplayFeatureProducer.java
+++ /dev/null
@@ -1,124 +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 androidx.window.common;
-
-import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_FLAT;
-import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_HALF_OPENED;
-import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_UNKNOWN;
-import static androidx.window.common.CommonFoldingFeature.parseListFromString;
-
-import android.annotation.NonNull;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Looper;
-import android.provider.Settings;
-import android.text.TextUtils;
-
-import androidx.window.util.BaseDataProducer;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.Optional;
-
-/**
- * Implementation of {@link androidx.window.util.DataProducer} that produces
- * {@link CommonFoldingFeature} parsed from a string stored in {@link Settings}.
- */
-public final class SettingsDisplayFeatureProducer
-        extends BaseDataProducer<List<CommonFoldingFeature>> {
-    private static final String DISPLAY_FEATURES = "display_features";
-    private static final String DEVICE_POSTURE = "device_posture";
-
-    private final Uri mDevicePostureUri =
-            Settings.Global.getUriFor(DEVICE_POSTURE);
-    private final Uri mDisplayFeaturesUri =
-            Settings.Global.getUriFor(DISPLAY_FEATURES);
-
-    private final ContentResolver mResolver;
-    private final ContentObserver mObserver;
-    private boolean mRegisteredObservers;
-
-    public SettingsDisplayFeatureProducer(@NonNull Context context) {
-        mResolver = context.getContentResolver();
-        mObserver = new SettingsObserver();
-    }
-
-    private int getPosture() {
-        int posture = Settings.Global.getInt(mResolver, DEVICE_POSTURE, COMMON_STATE_UNKNOWN);
-        if (posture == COMMON_STATE_HALF_OPENED || posture == COMMON_STATE_FLAT) {
-            return posture;
-        } else {
-            return COMMON_STATE_UNKNOWN;
-        }
-    }
-
-    @Override
-    @NonNull
-    public Optional<List<CommonFoldingFeature>> getData() {
-        String displayFeaturesString = Settings.Global.getString(mResolver, DISPLAY_FEATURES);
-        if (displayFeaturesString == null) {
-            return Optional.empty();
-        }
-
-        if (TextUtils.isEmpty(displayFeaturesString)) {
-            return Optional.of(Collections.emptyList());
-        }
-        return Optional.of(parseListFromString(displayFeaturesString, getPosture()));
-    }
-
-    /**
-     * Registers settings observers, if needed. When settings observers are registered for this
-     * producer callbacks for changes in data will be triggered.
-     */
-    public void registerObserversIfNeeded() {
-        if (mRegisteredObservers) {
-            return;
-        }
-        mRegisteredObservers = true;
-        mResolver.registerContentObserver(mDisplayFeaturesUri, false /* notifyForDescendants */,
-                mObserver /* ContentObserver */);
-        mResolver.registerContentObserver(mDevicePostureUri, false, mObserver);
-    }
-
-    /**
-     * Unregisters settings observers, if needed. When settings observers are unregistered for this
-     * producer callbacks for changes in data will not be triggered.
-     */
-    public void unregisterObserversIfNeeded() {
-        if (!mRegisteredObservers) {
-            return;
-        }
-        mRegisteredObservers = false;
-        mResolver.unregisterContentObserver(mObserver);
-    }
-
-    private final class SettingsObserver extends ContentObserver {
-        SettingsObserver() {
-            super(new Handler(Looper.getMainLooper()));
-        }
-
-        @Override
-        public void onChange(boolean selfChange, Uri uri) {
-            if (mDisplayFeaturesUri.equals(uri) || mDevicePostureUri.equals(uri)) {
-                notifyDataChanged();
-            }
-        }
-    }
-}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index 180c772..f4e91ba 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -35,6 +35,8 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.util.Map;
 import java.util.concurrent.Executor;
 
@@ -56,7 +58,8 @@
     final Map<IBinder, Configuration> mFragmentParentConfigs = new ArrayMap<>();
 
     private final TaskFragmentCallback mCallback;
-    private TaskFragmentAnimationController mAnimationController;
+    @VisibleForTesting
+    TaskFragmentAnimationController mAnimationController;
 
     /**
      * Callback that notifies the controller about changes to task fragments.
@@ -80,21 +83,25 @@
 
     @Override
     public void unregisterOrganizer() {
-        stopOverrideSplitAnimation();
-        mAnimationController = null;
+        if (mAnimationController != null) {
+            mAnimationController.unregisterAllRemoteAnimations();
+            mAnimationController = null;
+        }
         super.unregisterOrganizer();
     }
 
-    void startOverrideSplitAnimation() {
+    /** Overrides the animation if the transition is on the given Task. */
+    void startOverrideSplitAnimation(int taskId) {
         if (mAnimationController == null) {
             mAnimationController = new TaskFragmentAnimationController(this);
         }
-        mAnimationController.registerRemoteAnimations();
+        mAnimationController.registerRemoteAnimations(taskId);
     }
 
-    void stopOverrideSplitAnimation() {
+    /** No longer overrides the animation if the transition is on the given Task. */
+    void stopOverrideSplitAnimation(int taskId) {
         if (mAnimationController != null) {
-            mAnimationController.unregisterRemoteAnimations();
+            mAnimationController.unregisterRemoteAnimations(taskId);
         }
     }
 
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index bb3b534..01f5feb 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -37,6 +37,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
+import android.util.ArraySet;
 import android.util.SparseArray;
 import android.window.TaskFragmentInfo;
 import android.window.WindowContainerTransaction;
@@ -75,10 +76,6 @@
     private Consumer<List<SplitInfo>> mEmbeddingCallback;
     private final List<SplitInfo> mLastReportedSplitStates = new ArrayList<>();
 
-    // We currently only support split activity embedding within the one root Task.
-    // TODO(b/207720388): move to TaskContainer
-    private final Rect mParentBounds = new Rect();
-
     public SplitController() {
         mPresenter = new SplitPresenter(new MainThreadExecutor(), this);
         ActivityThread activityThread = ActivityThread.currentActivityThread();
@@ -95,7 +92,9 @@
     public void setEmbeddingRules(@NonNull Set<EmbeddingRule> rules) {
         mSplitRules.clear();
         mSplitRules.addAll(rules);
-        updateAnimationOverride();
+        for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
+            updateAnimationOverride(mTaskContainers.keyAt(i));
+        }
     }
 
     @NonNull
@@ -163,38 +162,49 @@
 
     @Override
     public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) {
-        TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
-        if (container == null) {
-            return;
+        final TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
+        if (container != null) {
+            // Cleanup if the TaskFragment vanished is not requested by the organizer.
+            mPresenter.cleanupContainer(container, true /* shouldFinishDependent */);
+            updateCallbackIfNecessary();
         }
-
-        mPresenter.cleanupContainer(container, true /* shouldFinishDependent */);
-        updateCallbackIfNecessary();
+        cleanupTaskFragment(taskFragmentInfo.getFragmentToken());
     }
 
     @Override
     public void onTaskFragmentParentInfoChanged(@NonNull IBinder fragmentToken,
             @NonNull Configuration parentConfig) {
-        onParentBoundsMayChange(parentConfig.windowConfiguration.getBounds());
         TaskFragmentContainer container = getContainer(fragmentToken);
         if (container != null) {
+            onTaskBoundsMayChange(container.getTaskId(),
+                    parentConfig.windowConfiguration.getBounds());
             mPresenter.updateContainer(container);
             updateCallbackIfNecessary();
         }
     }
 
-    private void onParentBoundsMayChange(Activity activity) {
-        if (activity.isFinishing()) {
+    /** Called on receiving {@link #onTaskFragmentVanished(TaskFragmentInfo)} for cleanup. */
+    private void cleanupTaskFragment(@NonNull IBinder taskFragmentToken) {
+        for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
+            final TaskContainer taskContainer = mTaskContainers.valueAt(i);
+            if (!taskContainer.mFinishedContainer.remove(taskFragmentToken)) {
+                continue;
+            }
+            if (taskContainer.isEmpty()) {
+                // Cleanup the TaskContainer if it becomes empty.
+                mPresenter.stopOverrideSplitAnimation(taskContainer.mTaskId);
+                mTaskContainers.remove(taskContainer.mTaskId);
+            }
             return;
         }
-
-        onParentBoundsMayChange(mPresenter.getParentContainerBounds(activity));
     }
 
-    private void onParentBoundsMayChange(Rect parentBounds) {
-        if (!parentBounds.isEmpty() && !mParentBounds.equals(parentBounds)) {
-            mParentBounds.set(parentBounds);
-            updateAnimationOverride();
+    private void onTaskBoundsMayChange(int taskId, @NonNull Rect taskBounds) {
+        final TaskContainer taskContainer = mTaskContainers.get(taskId);
+        if (taskContainer != null && !taskBounds.isEmpty()
+                && !taskContainer.mTaskBounds.equals(taskBounds)) {
+            taskContainer.mTaskBounds.set(taskBounds);
+            updateAnimationOverride(taskId);
         }
     }
 
@@ -202,9 +212,10 @@
      * Updates if we should override transition animation. We only want to override if the Task
      * bounds is large enough for at least one split rule.
      */
-    private void updateAnimationOverride() {
-        if (mParentBounds.isEmpty()) {
-            // We don't know about the parent bounds yet.
+    private void updateAnimationOverride(int taskId) {
+        final TaskContainer taskContainer = mTaskContainers.get(taskId);
+        if (taskContainer == null || !taskContainer.isTaskBoundsInitialized()) {
+            // We don't know about the Task bounds yet.
             return;
         }
 
@@ -214,7 +225,7 @@
             if (!(rule instanceof SplitRule)) {
                 continue;
             }
-            if (mPresenter.shouldShowSideBySide(mParentBounds, (SplitRule) rule)) {
+            if (mPresenter.shouldShowSideBySide(taskContainer.mTaskBounds, (SplitRule) rule)) {
                 supportSplit = true;
                 break;
             }
@@ -222,9 +233,9 @@
 
         // We only want to override if it supports split.
         if (supportSplit) {
-            mPresenter.startOverrideSplitAnimation();
+            mPresenter.startOverrideSplitAnimation(taskId);
         } else {
-            mPresenter.stopOverrideSplitAnimation();
+            mPresenter.stopOverrideSplitAnimation(taskId);
         }
     }
 
@@ -243,11 +254,6 @@
         final TaskFragmentContainer currentContainer = getContainerWithActivity(
                 launchedActivity.getActivityToken());
 
-        if (currentContainer == null) {
-            // Initial check before any TaskFragment is created.
-            onParentBoundsMayChange(launchedActivity);
-        }
-
         // Check if the activity is configured to always be expanded.
         if (shouldExpand(launchedActivity, null, splitRules)) {
             if (shouldContainerBeExpanded(currentContainer)) {
@@ -326,8 +332,6 @@
             // onTaskFragmentParentInfoChanged
             return;
         }
-        // The bounds of the container may have been changed.
-        onParentBoundsMayChange(activity);
 
         // Check if activity requires a placeholder
         launchPlaceholderIfNecessary(activity);
@@ -357,9 +361,14 @@
     TaskFragmentContainer newContainer(@Nullable Activity activity, int taskId) {
         final TaskFragmentContainer container = new TaskFragmentContainer(activity, taskId);
         if (!mTaskContainers.contains(taskId)) {
-            mTaskContainers.put(taskId, new TaskContainer());
+            mTaskContainers.put(taskId, new TaskContainer(taskId));
         }
-        mTaskContainers.get(taskId).mContainers.add(container);
+        final TaskContainer taskContainer = mTaskContainers.get(taskId);
+        taskContainer.mContainers.add(container);
+        if (activity != null && !taskContainer.isTaskBoundsInitialized()) {
+            // Initial check before any TaskFragment has appeared.
+            onTaskBoundsMayChange(taskId, SplitPresenter.getTaskBoundsFromActivity(activity));
+        }
         return container;
     }
 
@@ -391,11 +400,11 @@
             return;
         }
         taskContainer.mContainers.remove(container);
-        if (taskContainer.mContainers.isEmpty()) {
-            mTaskContainers.remove(taskId);
-            // No more TaskFragment in this Task, so no need to check split container.
-            return;
-        }
+        // Marked as a pending removal which will be removed after it is actually removed on the
+        // server side (#onTaskFragmentVanished).
+        // In this way, we can keep track of the Task bounds until we no longer have any
+        // TaskFragment there.
+        taskContainer.mFinishedContainer.add(container.getTaskFragmentToken());
 
         final List<SplitContainer> containersToRemove = new ArrayList<>();
         for (SplitContainer splitContainer : taskContainer.mSplitContainers) {
@@ -543,7 +552,7 @@
     }
 
     boolean launchPlaceholderIfNecessary(@NonNull Activity activity) {
-        final  TaskFragmentContainer container = getContainerWithActivity(
+        final TaskFragmentContainer container = getContainerWithActivity(
                 activity.getActivityToken());
         // Don't launch placeholder if the container is occluded.
         if (container != null && container != getTopActiveContainer(container.getTaskId())) {
@@ -606,14 +615,22 @@
         return null;
     }
 
+    private void updateCallbackIfNecessary() {
+        updateCallbackIfNecessary(true /* deferCallbackUntilAllActivitiesCreated */);
+    }
+
     /**
      * Notifies listeners about changes to split states if necessary.
+     *
+     * @param deferCallbackUntilAllActivitiesCreated boolean to indicate whether the split info
+     *                                               callback should be deferred until all the
+     *                                               organized activities have been created.
      */
-    private void updateCallbackIfNecessary() {
+    private void updateCallbackIfNecessary(boolean deferCallbackUntilAllActivitiesCreated) {
         if (mEmbeddingCallback == null) {
             return;
         }
-        if (!allActivitiesCreated()) {
+        if (deferCallbackUntilAllActivitiesCreated && !allActivitiesCreated()) {
             return;
         }
         List<SplitInfo> currentSplitStates = getActiveSplitStates();
@@ -829,6 +846,36 @@
     private final class LifecycleCallbacks extends EmptyLifecycleCallbacksAdapter {
 
         @Override
+        public void onActivityPreCreated(Activity activity, Bundle savedInstanceState) {
+            final IBinder activityToken = activity.getActivityToken();
+            final IBinder initialTaskFragmentToken = ActivityThread.currentActivityThread()
+                    .getActivityClient(activityToken).mInitialTaskFragmentToken;
+            // If the activity is not embedded, then it will not have an initial task fragment token
+            // so no further action is needed.
+            if (initialTaskFragmentToken == null) {
+                return;
+            }
+            for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
+                final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i)
+                        .mContainers;
+                for (int j = containers.size() - 1; j >= 0; j--) {
+                    final TaskFragmentContainer container = containers.get(j);
+                    if (!container.hasActivity(activityToken)
+                            && container.getTaskFragmentToken().equals(initialTaskFragmentToken)) {
+                        // The onTaskFragmentInfoChanged callback containing this activity has not
+                        // reached the client yet, so add the activity to the pending appeared
+                        // activities and send a split info callback to the client before
+                        // {@link Activity#onCreate} is called.
+                        container.addPendingAppearedActivity(activity);
+                        updateCallbackIfNecessary(
+                                false /* deferCallbackUntilAllActivitiesCreated */);
+                        return;
+                    }
+                }
+            }
+        }
+
+        @Override
         public void onActivityPostCreated(Activity activity, Bundle savedInstanceState) {
             // Calling after Activity#onCreate is complete to allow the app launch something
             // first. In case of a configured placeholder activity we want to make sure
@@ -1035,7 +1082,30 @@
     /** Represents TaskFragments and split pairs below a Task. */
     @VisibleForTesting
     static class TaskContainer {
+        /** The unique task id. */
+        final int mTaskId;
+        /** Active TaskFragments in this Task. */
         final List<TaskFragmentContainer> mContainers = new ArrayList<>();
+        /** Active split pairs in this Task. */
         final List<SplitContainer> mSplitContainers = new ArrayList<>();
+        /**
+         * TaskFragments that the organizer has requested to be closed. They should be removed when
+         * the organizer receives {@link #onTaskFragmentVanished(TaskFragmentInfo)} event for them.
+         */
+        final Set<IBinder> mFinishedContainer = new ArraySet<>();
+        /** Available window bounds of this Task. */
+        final Rect mTaskBounds = new Rect();
+
+        TaskContainer(int taskId) {
+            mTaskId = taskId;
+        }
+
+        boolean isEmpty() {
+            return mContainers.isEmpty() && mFinishedContainer.isEmpty();
+        }
+
+        boolean isTaskBoundsInitialized() {
+            return !mTaskBounds.isEmpty();
+        }
     }
 }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index e7552ff..e4d9ede 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -419,7 +419,11 @@
             }
         }
 
-        // TODO(b/190433398): Check if the client-side available info about parent bounds is enough.
+        return getTaskBoundsFromActivity(activity);
+    }
+
+    @NonNull
+    static Rect getTaskBoundsFromActivity(@NonNull Activity activity) {
         if (!activity.isInMultiWindowMode()) {
             // In fullscreen mode the max bounds should correspond to the task bounds.
             return activity.getResources().getConfiguration().windowConfiguration.getMaxBounds();
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
index a801dc8..f721341 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
@@ -24,11 +24,14 @@
 import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;
 import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
 
+import android.util.ArraySet;
 import android.util.Log;
 import android.view.RemoteAnimationAdapter;
 import android.view.RemoteAnimationDefinition;
 import android.window.TaskFragmentOrganizer;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 /** Controls the TaskFragment remote animations. */
 class TaskFragmentAnimationController {
 
@@ -37,8 +40,10 @@
 
     private final TaskFragmentOrganizer mOrganizer;
     private final TaskFragmentAnimationRunner mRemoteRunner = new TaskFragmentAnimationRunner();
-    private final RemoteAnimationDefinition mDefinition;
-    private boolean mIsRegister;
+    @VisibleForTesting
+    final RemoteAnimationDefinition mDefinition;
+    /** Task Ids that we have registered for remote animation. */
+    private final ArraySet<Integer> mRegisterTasks = new ArraySet<>();
 
     TaskFragmentAnimationController(TaskFragmentOrganizer organizer) {
         mOrganizer = organizer;
@@ -54,25 +59,32 @@
         mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CHANGE, animationAdapter);
     }
 
-    void registerRemoteAnimations() {
+    void registerRemoteAnimations(int taskId) {
         if (DEBUG) {
             Log.v(TAG, "registerRemoteAnimations");
         }
-        if (mIsRegister) {
+        if (mRegisterTasks.contains(taskId)) {
             return;
         }
-        mOrganizer.registerRemoteAnimations(mDefinition);
-        mIsRegister = true;
+        mOrganizer.registerRemoteAnimations(taskId, mDefinition);
+        mRegisterTasks.add(taskId);
     }
 
-    void unregisterRemoteAnimations() {
+    void unregisterRemoteAnimations(int taskId) {
         if (DEBUG) {
             Log.v(TAG, "unregisterRemoteAnimations");
         }
-        if (!mIsRegister) {
+        if (!mRegisterTasks.contains(taskId)) {
             return;
         }
-        mOrganizer.unregisterRemoteAnimations();
-        mIsRegister = false;
+        mOrganizer.unregisterRemoteAnimations(taskId);
+        mRegisterTasks.remove(taskId);
+    }
+
+    void unregisterAllRemoteAnimations() {
+        final ArraySet<Integer> tasks = new ArraySet<>(mRegisterTasks);
+        for (int taskId : tasks) {
+            unregisterRemoteAnimations(taskId);
+        }
     }
 }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index e49af41..9a12669 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -120,7 +120,7 @@
     }
 
     ActivityStack toActivityStack() {
-        return new ActivityStack(collectActivities(), mInfo.getRunningActivityCount() == 0);
+        return new ActivityStack(collectActivities(), isEmpty());
     }
 
     void addPendingAppearedActivity(@NonNull Activity pendingAppearedActivity) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index a4fbdbc..2f7d958 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -37,9 +37,8 @@
 import androidx.window.common.CommonFoldingFeature;
 import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
 import androidx.window.common.EmptyLifecycleCallbacksAdapter;
-import androidx.window.common.SettingsDisplayFeatureProducer;
+import androidx.window.common.RawFoldingFeatureProducer;
 import androidx.window.util.DataProducer;
-import androidx.window.util.PriorityDataProducer;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -62,17 +61,14 @@
     private final Map<Activity, Consumer<WindowLayoutInfo>> mWindowLayoutChangeListeners =
             new ArrayMap<>();
 
-    private final SettingsDisplayFeatureProducer mSettingsDisplayFeatureProducer;
     private final DataProducer<List<CommonFoldingFeature>> mFoldingFeatureProducer;
 
     public WindowLayoutComponentImpl(Context context) {
         ((Application) context.getApplicationContext())
                 .registerActivityLifecycleCallbacks(new NotifyOnConfigurationChanged());
-        mSettingsDisplayFeatureProducer = new SettingsDisplayFeatureProducer(context);
-        mFoldingFeatureProducer = new PriorityDataProducer<>(List.of(
-                mSettingsDisplayFeatureProducer,
-                new DeviceStateManagerFoldingFeatureProducer(context)
-        ));
+        RawFoldingFeatureProducer foldingFeatureProducer = new RawFoldingFeatureProducer(context);
+        mFoldingFeatureProducer = new DeviceStateManagerFoldingFeatureProducer(context,
+                foldingFeatureProducer);
         mFoldingFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged);
     }
 
@@ -85,7 +81,7 @@
     public void addWindowLayoutInfoListener(@NonNull Activity activity,
             @NonNull Consumer<WindowLayoutInfo> consumer) {
         mWindowLayoutChangeListeners.put(activity, consumer);
-        updateRegistrations();
+        onDisplayFeaturesChanged();
     }
 
     /**
@@ -96,7 +92,7 @@
     public void removeWindowLayoutInfoListener(
             @NonNull Consumer<WindowLayoutInfo> consumer) {
         mWindowLayoutChangeListeners.values().remove(consumer);
-        updateRegistrations();
+        onDisplayFeaturesChanged();
     }
 
     void updateWindowLayout(@NonNull Activity activity,
@@ -210,15 +206,6 @@
         return features;
     }
 
-    private void updateRegistrations() {
-        if (hasListeners()) {
-            mSettingsDisplayFeatureProducer.registerObserversIfNeeded();
-        } else {
-            mSettingsDisplayFeatureProducer.unregisterObserversIfNeeded();
-        }
-        onDisplayFeaturesChanged();
-    }
-
     private final class NotifyOnConfigurationChanged extends EmptyLifecycleCallbacksAdapter {
         @Override
         public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
index c7b7093..970f0a2 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
@@ -34,9 +34,8 @@
 import androidx.window.common.CommonFoldingFeature;
 import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
 import androidx.window.common.EmptyLifecycleCallbacksAdapter;
-import androidx.window.common.SettingsDisplayFeatureProducer;
+import androidx.window.common.RawFoldingFeatureProducer;
 import androidx.window.util.DataProducer;
-import androidx.window.util.PriorityDataProducer;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -52,16 +51,13 @@
 
     private final DataProducer<List<CommonFoldingFeature>> mFoldingFeatureProducer;
 
-    private final SettingsDisplayFeatureProducer mSettingsFoldingFeatureProducer;
 
     SampleSidecarImpl(Context context) {
         ((Application) context.getApplicationContext())
                 .registerActivityLifecycleCallbacks(new NotifyOnConfigurationChanged());
-        mSettingsFoldingFeatureProducer = new SettingsDisplayFeatureProducer(context);
-        mFoldingFeatureProducer = new PriorityDataProducer<>(List.of(
-                mSettingsFoldingFeatureProducer,
-                new DeviceStateManagerFoldingFeatureProducer(context)
-        ));
+        DataProducer<String> settingsFeatureProducer = new RawFoldingFeatureProducer(context);
+        mFoldingFeatureProducer = new DeviceStateManagerFoldingFeatureProducer(context,
+                settingsFeatureProducer);
 
         mFoldingFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged);
     }
@@ -142,10 +138,7 @@
     @Override
     protected void onListenersChanged() {
         if (hasListeners()) {
-            mSettingsFoldingFeatureProducer.registerObserversIfNeeded();
             onDisplayFeaturesChanged();
-        } else {
-            mSettingsFoldingFeatureProducer.unregisterObserversIfNeeded();
         }
     }
 
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java
index 0a46703451..930db3b 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java
@@ -33,13 +33,17 @@
     @Override
     public final void addDataChangedCallback(@NonNull Runnable callback) {
         mCallbacks.add(callback);
+        onListenersChanged(mCallbacks);
     }
 
     @Override
     public final void removeDataChangedCallback(@NonNull Runnable callback) {
         mCallbacks.remove(callback);
+        onListenersChanged(mCallbacks);
     }
 
+    protected void onListenersChanged(Set<Runnable> callbacks) {}
+
     /**
      * Called to notify all registered callbacks that the data provided by {@link #getData()} has
      * changed.
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/PriorityDataProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/util/PriorityDataProducer.java
deleted file mode 100644
index 990ae20..0000000
--- a/libs/WindowManager/Jetpack/src/androidx/window/util/PriorityDataProducer.java
+++ /dev/null
@@ -1,56 +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 androidx.window.util;
-
-import android.annotation.Nullable;
-
-import java.util.List;
-import java.util.Optional;
-
-/**
- * Implementation of {@link DataProducer} that delegates calls to {@link #getData()} to the list of
- * provided child producers.
- * <p>
- * The value returned is based on the precedence of the supplied children where the producer with
- * index 0 has a higher precedence than producers that come later in the list. When a producer with
- * a higher precedence has a non-empty value returned from {@link #getData()}, its value will be
- * returned from an instance of this class, ignoring all other producers with lower precedence.
- *
- * @param <T> The type of data this producer returns through {@link #getData()}.
- */
-public final class PriorityDataProducer<T> extends BaseDataProducer<T> {
-    private final List<DataProducer<T>> mChildProducers;
-
-    public PriorityDataProducer(List<DataProducer<T>> childProducers) {
-        mChildProducers = childProducers;
-        for (DataProducer<T> childProducer : mChildProducers) {
-            childProducer.addDataChangedCallback(this::notifyDataChanged);
-        }
-    }
-
-    @Nullable
-    @Override
-    public Optional<T> getData() {
-        for (DataProducer<T> childProducer : mChildProducers) {
-            final Optional<T> data = childProducer.getData();
-            if (data.isPresent()) {
-                return data;
-            }
-        }
-        return Optional.empty();
-    }
-}
diff --git a/libs/WindowManager/Jetpack/tests/OWNERS b/libs/WindowManager/Jetpack/tests/OWNERS
index f2c3388..ac522b2 100644
--- a/libs/WindowManager/Jetpack/tests/OWNERS
+++ b/libs/WindowManager/Jetpack/tests/OWNERS
@@ -1,4 +1,4 @@
-# Bug component: 909476
+# Bug component: 1157642
 # includes OWNERS from parent directories
 charlesccchen@google.com
 diegovela@google.com
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
new file mode 100644
index 0000000..26463c1
--- /dev/null
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
@@ -0,0 +1,91 @@
+/*
+ * 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 androidx.window.extensions.embedding;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.never;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Test class for {@link JetpackTaskFragmentOrganizer}.
+ *
+ * Build/Install/Run:
+ *  atest WMJetpackUnitTests:JetpackTaskFragmentOrganizerTest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class JetpackTaskFragmentOrganizerTest {
+    private static final int TASK_ID = 10;
+
+    @Mock
+    private JetpackTaskFragmentOrganizer.TaskFragmentCallback mCallback;
+    private JetpackTaskFragmentOrganizer mOrganizer;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mOrganizer = new JetpackTaskFragmentOrganizer(Runnable::run, mCallback);
+        mOrganizer.registerOrganizer();
+        spyOn(mOrganizer);
+    }
+
+    @Test
+    public void testUnregisterOrganizer() {
+        mOrganizer.startOverrideSplitAnimation(TASK_ID);
+        mOrganizer.startOverrideSplitAnimation(TASK_ID + 1);
+        mOrganizer.unregisterOrganizer();
+
+        verify(mOrganizer).unregisterRemoteAnimations(TASK_ID);
+        verify(mOrganizer).unregisterRemoteAnimations(TASK_ID + 1);
+    }
+
+    @Test
+    public void testStartOverrideSplitAnimation() {
+        assertNull(mOrganizer.mAnimationController);
+
+        mOrganizer.startOverrideSplitAnimation(TASK_ID);
+
+        assertNotNull(mOrganizer.mAnimationController);
+        verify(mOrganizer).registerRemoteAnimations(TASK_ID,
+                mOrganizer.mAnimationController.mDefinition);
+    }
+
+    @Test
+    public void testStopOverrideSplitAnimation() {
+        mOrganizer.stopOverrideSplitAnimation(TASK_ID);
+
+        verify(mOrganizer, never()).unregisterRemoteAnimations(anyInt());
+
+        mOrganizer.startOverrideSplitAnimation(TASK_ID);
+        mOrganizer.stopOverrideSplitAnimation(TASK_ID);
+
+        verify(mOrganizer).unregisterRemoteAnimations(TASK_ID);
+    }
+}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index 30e89a6..120c7eb 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -31,6 +31,12 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+/**
+ * Test class for {@link SplitController}.
+ *
+ * Build/Install/Run:
+ *  atest WMJetpackUnitTests:SplitController
+ */
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class SplitControllerTest {
@@ -46,7 +52,7 @@
 
     @Test
     public void testGetTopActiveContainer() {
-        TaskContainer taskContainer = new TaskContainer();
+        TaskContainer taskContainer = new TaskContainer(TASK_ID);
         // tf3 is finished so is not active.
         TaskFragmentContainer tf3 = mock(TaskFragmentContainer.class);
         doReturn(true).when(tf3).isFinished();
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java
new file mode 100644
index 0000000..7f88f4e
--- /dev/null
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java
@@ -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 androidx.window.extensions.embedding;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.never;
+
+import android.window.TaskFragmentOrganizer;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Test class for {@link TaskFragmentAnimationController}.
+ *
+ * Build/Install/Run:
+ *  atest WMJetpackUnitTests:TaskFragmentAnimationControllerTest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TaskFragmentAnimationControllerTest {
+    private static final int TASK_ID = 10;
+
+    @Mock
+    private TaskFragmentOrganizer mOrganizer;
+    private TaskFragmentAnimationController mAnimationController;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mAnimationController = new TaskFragmentAnimationController(mOrganizer);
+    }
+
+    @Test
+    public void testRegisterRemoteAnimations() {
+        mAnimationController.registerRemoteAnimations(TASK_ID);
+
+        verify(mOrganizer).registerRemoteAnimations(TASK_ID, mAnimationController.mDefinition);
+
+        mAnimationController.registerRemoteAnimations(TASK_ID);
+
+        // No extra call if it has been registered.
+        verify(mOrganizer).registerRemoteAnimations(TASK_ID, mAnimationController.mDefinition);
+    }
+
+    @Test
+    public void testUnregisterRemoteAnimations() {
+        mAnimationController.unregisterRemoteAnimations(TASK_ID);
+
+        // No call if it is not registered.
+        verify(mOrganizer, never()).unregisterRemoteAnimations(anyInt());
+
+        mAnimationController.registerRemoteAnimations(TASK_ID);
+        mAnimationController.unregisterRemoteAnimations(TASK_ID);
+
+        verify(mOrganizer).unregisterRemoteAnimations(TASK_ID);
+
+        mAnimationController.unregisterRemoteAnimations(TASK_ID);
+
+        // No extra call if it has been unregistered.
+        verify(mOrganizer).unregisterRemoteAnimations(TASK_ID);
+    }
+
+    @Test
+    public void testUnregisterAllRemoteAnimations() {
+        mAnimationController.registerRemoteAnimations(TASK_ID);
+        mAnimationController.registerRemoteAnimations(TASK_ID + 1);
+        mAnimationController.unregisterAllRemoteAnimations();
+
+        verify(mOrganizer).unregisterRemoteAnimations(TASK_ID);
+        verify(mOrganizer).unregisterRemoteAnimations(TASK_ID + 1);
+    }
+}
diff --git a/libs/WindowManager/Shell/res/drawable/home_icon.xml b/libs/WindowManager/Shell/res/drawable/home_icon.xml
new file mode 100644
index 0000000..1669d01
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/home_icon.xml
@@ -0,0 +1,45 @@
+<?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.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
+    android:gravity="center">
+    <item android:gravity="center">
+        <shape
+            android:shape="oval">
+            <stroke
+                android:color="@color/tv_pip_edu_text_home_icon"
+                android:width="1sp" />
+            <solid android:color="@android:color/transparent" />
+            <size
+                android:width="@dimen/pip_menu_edu_text_home_icon_outline"
+                android:height="@dimen/pip_menu_edu_text_home_icon_outline"/>
+        </shape>
+    </item>
+    <item
+        android:width="@dimen/pip_menu_edu_text_home_icon"
+        android:height="@dimen/pip_menu_edu_text_home_icon"
+        android:gravity="center">
+        <vector
+            android:width="24sp"
+            android:height="24sp"
+            android:viewportWidth="24"
+            android:viewportHeight="24">
+            <path
+                android:fillColor="@color/tv_pip_edu_text_home_icon"
+                android:pathData="M12,3L4,9v12h5v-7h6v7h5V9z"/>
+        </vector>
+    </item>
+</layer-list>
diff --git a/libs/WindowManager/Shell/res/drawable/tv_pip_menu_background.xml b/libs/WindowManager/Shell/res/drawable/tv_pip_menu_background.xml
new file mode 100644
index 0000000..0c62792
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/tv_pip_menu_background.xml
@@ -0,0 +1,23 @@
+<?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.
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <corners android:radius="@dimen/pip_menu_background_corner_radius" />
+    <solid android:color="@color/tv_pip_menu_background"/>
+    <stroke android:width="@dimen/pip_menu_border_width"
+            android:color="@color/tv_pip_menu_background"/>
+</shape>
diff --git a/libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml b/libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml
index 9bc0311..846fdb3 100644
--- a/libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml
+++ b/libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml
@@ -14,9 +14,20 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="rectangle">
-    <corners android:radius="@dimen/pip_menu_border_radius" />
-    <stroke android:width="@dimen/pip_menu_border_width"
-            android:color="@color/tv_pip_menu_focus_border" />
-</shape>
\ No newline at end of file
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:exitFadeDuration="@integer/pip_menu_fade_animation_duration">
+    <item android:state_activated="true">
+        <shape android:shape="rectangle">
+            <corners android:radius="@dimen/pip_menu_border_corner_radius" />
+            <stroke android:width="@dimen/pip_menu_border_width"
+                    android:color="@color/tv_pip_menu_focus_border" />
+        </shape>
+    </item>
+    <item>
+        <shape android:shape="rectangle">
+            <corners android:radius="@dimen/pip_menu_border_corner_radius" />
+            <stroke android:width="@dimen/pip_menu_border_width"
+                    android:color="@color/tv_pip_menu_background"/>
+        </shape>
+    </item>
+</selector>
diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
index b826d03..dbd5a9b 100644
--- a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
@@ -16,26 +16,41 @@
 -->
 <!-- Layout for TvPipMenuView -->
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-             android:id="@+id/tv_pip_menu"
-             android:layout_width="match_parent"
-             android:layout_height="match_parent">
+            android:id="@+id/tv_pip_menu"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:gravity="center|top">
+
+    <View
+        android:id="@+id/tv_pip"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_marginTop="@dimen/pip_menu_outer_space"
+        android:layout_marginStart="@dimen/pip_menu_outer_space"
+        android:layout_marginEnd="@dimen/pip_menu_outer_space"/>
 
     <ScrollView
         android:id="@+id/tv_pip_menu_scroll"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:gravity="center_horizontal"
+        android:layout_alignTop="@+id/tv_pip"
+        android:layout_alignStart="@+id/tv_pip"
+        android:layout_alignEnd="@+id/tv_pip"
+        android:layout_alignBottom="@+id/tv_pip"
         android:scrollbars="none"
-        android:layout_margin="@dimen/pip_menu_outer_space"
         android:visibility="gone"/>
 
     <HorizontalScrollView
         android:id="@+id/tv_pip_menu_horizontal_scroll"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
+        android:layout_alignTop="@+id/tv_pip"
+        android:layout_alignStart="@+id/tv_pip"
+        android:layout_alignEnd="@+id/tv_pip"
+        android:layout_alignBottom="@+id/tv_pip"
         android:gravity="center_vertical"
-        android:scrollbars="none"
-        android:layout_margin="@dimen/pip_menu_outer_space">
+        android:scrollbars="none">
 
         <LinearLayout
             android:id="@+id/tv_pip_menu_action_buttons"
@@ -89,10 +104,44 @@
     </HorizontalScrollView>
 
     <View
+        android:id="@+id/tv_pip_border"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_marginTop="@dimen/pip_menu_outer_space_frame"
+        android:layout_marginStart="@dimen/pip_menu_outer_space_frame"
+        android:layout_marginEnd="@dimen/pip_menu_outer_space_frame"
+        android:background="@drawable/tv_pip_menu_border"/>
+
+    <FrameLayout
+        android:id="@+id/tv_pip_menu_edu_text_container"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_below="@+id/tv_pip"
+        android:layout_alignBottom="@+id/tv_pip_menu_frame"
+        android:layout_alignStart="@+id/tv_pip"
+        android:layout_alignEnd="@+id/tv_pip"
+        android:background="@color/tv_pip_menu_background"
+        android:clipChildren="true">
+
+        <TextView
+            android:id="@+id/tv_pip_menu_edu_text"
+            android:layout_width="wrap_content"
+            android:layout_height="@dimen/pip_menu_edu_text_view_height"
+            android:layout_gravity="bottom|center"
+            android:gravity="center"
+            android:paddingBottom="@dimen/pip_menu_border_width"
+            android:text="@string/pip_edu_text"
+            android:singleLine="true"
+            android:ellipsize="marquee"
+            android:marqueeRepeatLimit="1"
+            android:scrollHorizontally="true"
+            android:textAppearance="@style/TvPipEduText"/>
+    </FrameLayout>
+
+    <View
         android:id="@+id/tv_pip_menu_frame"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:alpha="0"
         android:layout_margin="@dimen/pip_menu_outer_space_frame"
         android:background="@drawable/tv_pip_menu_border"/>
 
diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu_background.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu_background.xml
new file mode 100644
index 0000000..5af4020
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/tv_pip_menu_background.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.
+-->
+<!-- Layout for the back surface of the PiP menu -->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_margin="@dimen/pip_menu_outer_space_frame"
+        android:background="@drawable/tv_pip_menu_background"
+        android:elevation="@dimen/pip_menu_elevation"/>
+</FrameLayout>
+
diff --git a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
index 558ec51..776b18e 100644
--- a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
+++ b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
@@ -22,7 +22,12 @@
     <dimen name="pip_menu_button_margin">4dp</dimen>
     <dimen name="pip_menu_button_wrapper_margin">26dp</dimen>
     <dimen name="pip_menu_border_width">4dp</dimen>
-    <dimen name="pip_menu_border_radius">4dp</dimen>
+    <integer name="pip_menu_fade_animation_duration">500</integer>
+    <!-- The pip menu front border corner radius is 2dp smaller than
+        the background corner radius to hide the background from
+        showing through. -->
+    <dimen name="pip_menu_border_corner_radius">4dp</dimen>
+    <dimen name="pip_menu_background_corner_radius">6dp</dimen>
     <dimen name="pip_menu_outer_space">24dp</dimen>
 
     <!-- outer space minus border width -->
@@ -30,5 +35,14 @@
 
     <dimen name="pip_menu_arrow_size">24dp</dimen>
     <dimen name="pip_menu_arrow_elevation">5dp</dimen>
+
+    <dimen name="pip_menu_elevation">1dp</dimen>
+
+    <dimen name="pip_menu_edu_text_view_height">24dp</dimen>
+    <dimen name="pip_menu_edu_text_home_icon">9sp</dimen>
+    <dimen name="pip_menu_edu_text_home_icon_outline">14sp</dimen>
+    <integer name="pip_edu_text_show_duration_ms">10500</integer>
+    <integer name="pip_edu_text_window_exit_animation_duration_ms">1000</integer>
+    <integer name="pip_edu_text_view_exit_animation_duration_ms">300</integer>
 </resources>
 
diff --git a/libs/WindowManager/Shell/res/values/colors_tv.xml b/libs/WindowManager/Shell/res/values/colors_tv.xml
index 64b146e..fa90fe3 100644
--- a/libs/WindowManager/Shell/res/values/colors_tv.xml
+++ b/libs/WindowManager/Shell/res/values/colors_tv.xml
@@ -23,4 +23,8 @@
     <color name="tv_pip_menu_icon_bg_focused">#E8EAED</color>
     <color name="tv_pip_menu_icon_bg_unfocused">#990E0E0F</color>
     <color name="tv_pip_menu_focus_border">#E8EAED</color>
-</resources>
\ No newline at end of file
+    <color name="tv_pip_menu_background">#1E232C</color>
+
+    <color name="tv_pip_edu_text">#99D2E3FC</color>
+    <color name="tv_pip_edu_text_home_icon">#D2E3FC</color>
+</resources>
diff --git a/libs/WindowManager/Shell/res/values/styles.xml b/libs/WindowManager/Shell/res/values/styles.xml
index 7733201..19f7c3e 100644
--- a/libs/WindowManager/Shell/res/values/styles.xml
+++ b/libs/WindowManager/Shell/res/values/styles.xml
@@ -47,4 +47,13 @@
         <item name="android:layout_width">96dp</item>
         <item name="android:layout_height">48dp</item>
     </style>
+
+    <style name="TvPipEduText">
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
+        <item name="android:textAllCaps">true</item>
+        <item name="android:textSize">10sp</item>
+        <item name="android:lineSpacingExtra">4sp</item>
+        <item name="android:lineHeight">16sp</item>
+        <item name="android:textColor">@color/tv_pip_edu_text</item>
+    </style>
 </resources>
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 577ced5..42ac195 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
@@ -48,18 +48,16 @@
  * Controls the window animation run when a user initiates a back gesture.
  */
 public class BackAnimationController implements RemoteCallable<BackAnimationController> {
-
-    private static final String BACK_PREDICTABILITY_PROGRESS_THRESHOLD_PROP =
-            "persist.debug.back_predictability_progress_threshold";
-    // By default, enable new back dispatching without any animations.
-    private static final int BACK_PREDICTABILITY_PROP =
-            SystemProperties.getInt("persist.debug.back_predictability", 1);
-    public static final boolean IS_ENABLED = BACK_PREDICTABILITY_PROP > 0;
-    private static final int PROGRESS_THRESHOLD = SystemProperties
-            .getInt(BACK_PREDICTABILITY_PROGRESS_THRESHOLD_PROP, -1);
     private static final String TAG = "BackAnimationController";
+    private static final String PREDICTIVE_BACK_PROGRESS_THRESHOLD_PROP =
+            "persist.wm.debug.predictive_back_progress_threshold";
+    public static final boolean IS_ENABLED =
+            SystemProperties.getInt("persist.wm.debug.predictive_back", 1) != 0;
+    private static final int PROGRESS_THRESHOLD = SystemProperties
+            .getInt(PREDICTIVE_BACK_PROGRESS_THRESHOLD_PROP, -1);
     @VisibleForTesting
-    boolean mEnableAnimations = (BACK_PREDICTABILITY_PROP & (1 << 1)) != 0;
+    boolean mEnableAnimations = SystemProperties.getInt(
+            "persist.wm.debug.predictive_back_anim", 0) != 0;
 
     /**
      * Location of the initial touch event of the back gesture.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index a7052bc..3b83f15 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -40,7 +40,6 @@
 import android.view.animation.Interpolator;
 import android.view.animation.PathInterpolator;
 
-import androidx.annotation.BinderThread;
 import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.view.IInputMethodManager;
@@ -325,7 +324,8 @@
         }
 
         @Override
-        public void topFocusedWindowChanged(String packageName) {
+        public void topFocusedWindowChanged(String packageName,
+                InsetsVisibilities requestedVisibilities) {
             // Do nothing
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
index 565f148..b670544 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
@@ -23,6 +23,7 @@
 import android.view.IWindowManager;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
+import android.view.InsetsVisibilities;
 
 import androidx.annotation.BinderThread;
 
@@ -170,13 +171,14 @@
             }
         }
 
-        private void topFocusedWindowChanged(String packageName) {
+        private void topFocusedWindowChanged(String packageName,
+                InsetsVisibilities requestedVisibilities) {
             CopyOnWriteArrayList<OnInsetsChangedListener> listeners = mListeners.get(mDisplayId);
             if (listeners == null) {
                 return;
             }
             for (OnInsetsChangedListener listener : listeners) {
-                listener.topFocusedWindowChanged(packageName);
+                listener.topFocusedWindowChanged(packageName, requestedVisibilities);
             }
         }
 
@@ -184,9 +186,10 @@
         private class DisplayWindowInsetsControllerImpl
                 extends IDisplayWindowInsetsController.Stub {
             @Override
-            public void topFocusedWindowChanged(String packageName) throws RemoteException {
+            public void topFocusedWindowChanged(String packageName,
+                    InsetsVisibilities requestedVisibilities) throws RemoteException {
                 mMainExecutor.execute(() -> {
-                    PerDisplay.this.topFocusedWindowChanged(packageName);
+                    PerDisplay.this.topFocusedWindowChanged(packageName, requestedVisibilities);
                 });
             }
 
@@ -231,9 +234,11 @@
         /**
          * Called when top focused window changes to determine whether or not to take over insets
          * control. Won't be called if config_remoteInsetsControllerControlsSystemBars is false.
-         * @param packageName: Passes the top package name
+         * @param packageName The name of the package that is open in the top focussed window.
+         * @param requestedVisibilities The insets visibilities requested by the focussed window.
          */
-        default void topFocusedWindowChanged(String packageName) {}
+        default void topFocusedWindowChanged(String packageName,
+                InsetsVisibilities requestedVisibilities) {}
 
         /**
          * Called when the window insets configuration has changed.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 73f3931..7f9ecca2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -63,6 +63,7 @@
 import com.android.wm.shell.pip.phone.PhonePipMenuController;
 import com.android.wm.shell.pip.phone.PipAppOpsListener;
 import com.android.wm.shell.pip.phone.PipController;
+import com.android.wm.shell.pip.phone.PipKeepClearAlgorithm;
 import com.android.wm.shell.pip.phone.PipMotionHelper;
 import com.android.wm.shell.pip.phone.PipTouchHandler;
 import com.android.wm.shell.recents.RecentTasksController;
@@ -207,7 +208,8 @@
     @Provides
     static Optional<Pip> providePip(Context context, DisplayController displayController,
             PipAppOpsListener pipAppOpsListener, PipBoundsAlgorithm pipBoundsAlgorithm,
-            PipBoundsState pipBoundsState, PipMediaController pipMediaController,
+            PipKeepClearAlgorithm pipKeepClearAlgorithm, PipBoundsState pipBoundsState,
+            PipMotionHelper pipMotionHelper, PipMediaController pipMediaController,
             PhonePipMenuController phonePipMenuController, PipTaskOrganizer pipTaskOrganizer,
             PipTouchHandler pipTouchHandler, PipTransitionController pipTransitionController,
             WindowManagerShellWrapper windowManagerShellWrapper,
@@ -215,9 +217,11 @@
             Optional<OneHandedController> oneHandedController,
             @ShellMainThread ShellExecutor mainExecutor) {
         return Optional.ofNullable(PipController.create(context, displayController,
-                pipAppOpsListener, pipBoundsAlgorithm, pipBoundsState, pipMediaController,
-                phonePipMenuController, pipTaskOrganizer, pipTouchHandler, pipTransitionController,
-                windowManagerShellWrapper, taskStackListener, oneHandedController, mainExecutor));
+                pipAppOpsListener, pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState,
+                pipMotionHelper,
+                pipMediaController, phonePipMenuController, pipTaskOrganizer, pipTouchHandler,
+                pipTransitionController, windowManagerShellWrapper, taskStackListener,
+                oneHandedController, mainExecutor));
     }
 
     @WMSingleton
@@ -234,6 +238,12 @@
 
     @WMSingleton
     @Provides
+    static PipKeepClearAlgorithm providePipKeepClearAlgorithm() {
+        return new PipKeepClearAlgorithm();
+    }
+
+    @WMSingleton
+    @Provides
     static PipBoundsAlgorithm providesPipBoundsAlgorithm(Context context,
             PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm) {
         return new PipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 623ef05..175a244 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -107,7 +107,9 @@
     private PipAppOpsListener mAppOpsListener;
     private PipMediaController mMediaController;
     private PipBoundsAlgorithm mPipBoundsAlgorithm;
+    private PipKeepClearAlgorithm mPipKeepClearAlgorithm;
     private PipBoundsState mPipBoundsState;
+    private PipMotionHelper mPipMotionHelper;
     private PipTouchHandler mTouchHandler;
     private PipTransitionController mPipTransitionController;
     private TaskStackListenerImpl mTaskStackListener;
@@ -241,6 +243,10 @@
                         Set<Rect> unrestricted) {
                     if (mPipBoundsState.getDisplayId() == displayId) {
                         mPipBoundsState.setKeepClearAreas(restricted, unrestricted);
+                        mPipMotionHelper.moveToBounds(mPipKeepClearAlgorithm.adjust(
+                                mPipBoundsState.getBounds(),
+                                mPipBoundsState.getRestrictedKeepClearAreas(),
+                                mPipBoundsState.getUnrestrictedKeepClearAreas()));
                     }
                 }
             };
@@ -293,7 +299,8 @@
     @Nullable
     public static Pip create(Context context, DisplayController displayController,
             PipAppOpsListener pipAppOpsListener, PipBoundsAlgorithm pipBoundsAlgorithm,
-            PipBoundsState pipBoundsState, PipMediaController pipMediaController,
+            PipKeepClearAlgorithm pipKeepClearAlgorithm, PipBoundsState pipBoundsState,
+            PipMotionHelper pipMotionHelper, PipMediaController pipMediaController,
             PhonePipMenuController phonePipMenuController, PipTaskOrganizer pipTaskOrganizer,
             PipTouchHandler pipTouchHandler, PipTransitionController pipTransitionController,
             WindowManagerShellWrapper windowManagerShellWrapper,
@@ -307,9 +314,9 @@
         }
 
         return new PipController(context, displayController, pipAppOpsListener, pipBoundsAlgorithm,
-                pipBoundsState, pipMediaController, phonePipMenuController, pipTaskOrganizer,
-                pipTouchHandler, pipTransitionController, windowManagerShellWrapper,
-                taskStackListener, oneHandedController, mainExecutor)
+                pipKeepClearAlgorithm, pipBoundsState, pipMotionHelper, pipMediaController,
+                phonePipMenuController, pipTaskOrganizer,  pipTouchHandler, pipTransitionController,
+                windowManagerShellWrapper, taskStackListener, oneHandedController, mainExecutor)
                 .mImpl;
     }
 
@@ -317,7 +324,9 @@
             DisplayController displayController,
             PipAppOpsListener pipAppOpsListener,
             PipBoundsAlgorithm pipBoundsAlgorithm,
+            PipKeepClearAlgorithm pipKeepClearAlgorithm,
             @NonNull PipBoundsState pipBoundsState,
+            PipMotionHelper pipMotionHelper,
             PipMediaController pipMediaController,
             PhonePipMenuController phonePipMenuController,
             PipTaskOrganizer pipTaskOrganizer,
@@ -339,7 +348,9 @@
         mWindowManagerShellWrapper = windowManagerShellWrapper;
         mDisplayController = displayController;
         mPipBoundsAlgorithm = pipBoundsAlgorithm;
+        mPipKeepClearAlgorithm = pipKeepClearAlgorithm;
         mPipBoundsState = pipBoundsState;
+        mPipMotionHelper = pipMotionHelper;
         mPipTaskOrganizer = pipTaskOrganizer;
         mMainExecutor = mainExecutor;
         mMediaController = pipMediaController;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithm.java
new file mode 100644
index 0000000..a83258f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithm.java
@@ -0,0 +1,44 @@
+/*
+ * 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.wm.shell.pip.phone;
+
+import android.graphics.Rect;
+
+import java.util.Set;
+
+/**
+ * Calculates the adjusted position that does not occlude keep clear areas.
+ */
+public class PipKeepClearAlgorithm {
+
+    /** Returns a new {@code Rect} that does not occlude the provided keep clear areas. */
+    public Rect adjust(Rect defaultBounds, Set<Rect> restrictedKeepClearAreas,
+            Set<Rect> unrestrictedKeepClearAreas) {
+        if (restrictedKeepClearAreas.isEmpty()) {
+            return defaultBounds;
+        }
+        // TODO(b/183746978): implement the adjustment algorithm
+        // naively check if areas intersect, an if so move PiP upwards
+        Rect outBounds = new Rect(defaultBounds);
+        for (Rect r : restrictedKeepClearAreas) {
+            if (r.intersect(outBounds)) {
+                outBounds.offset(0, r.top - outBounds.bottom);
+            }
+        }
+        return outBounds;
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/CenteredImageSpan.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/CenteredImageSpan.java
new file mode 100644
index 0000000..6efdd57
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/CenteredImageSpan.java
@@ -0,0 +1,76 @@
+/*
+ * 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.wm.shell.pip.tv;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.text.style.ImageSpan;
+
+/** An ImageSpan for a Drawable that is centered vertically in the line. */
+public class CenteredImageSpan extends ImageSpan {
+
+    private Drawable mDrawable;
+
+    public CenteredImageSpan(Drawable drawable) {
+        super(drawable);
+    }
+
+    @Override
+    public int getSize(
+            Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fontMetrics) {
+        final Drawable drawable = getCachedDrawable();
+        final Rect rect = drawable.getBounds();
+
+        if (fontMetrics != null) {
+            Paint.FontMetricsInt paintFontMetrics = paint.getFontMetricsInt();
+            fontMetrics.ascent = paintFontMetrics.ascent;
+            fontMetrics.descent = paintFontMetrics.descent;
+            fontMetrics.top = paintFontMetrics.top;
+            fontMetrics.bottom = paintFontMetrics.bottom;
+        }
+
+        return rect.right;
+    }
+
+    @Override
+    public void draw(
+            Canvas canvas,
+            CharSequence text,
+            int start,
+            int end,
+            float x,
+            int top,
+            int y,
+            int bottom,
+            Paint paint) {
+        final Drawable drawable = getCachedDrawable();
+        canvas.save();
+        final int transY = (bottom - drawable.getBounds().bottom) / 2;
+        canvas.translate(x, transY);
+        drawable.draw(canvas);
+        canvas.restore();
+    }
+
+    private Drawable getCachedDrawable() {
+        if (mDrawable == null) {
+            mDrawable = getDrawable();
+        }
+        return mDrawable;
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
index 1aefd77..21d5d40 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
@@ -27,6 +27,7 @@
 
 import android.content.Context;
 import android.content.res.Resources;
+import android.graphics.Insets;
 import android.graphics.Rect;
 import android.os.SystemClock;
 import android.util.ArraySet;
@@ -150,6 +151,10 @@
         mKeepClearAlgorithm.setScreenSize(screenSize);
         mKeepClearAlgorithm.setMovementBounds(insetBounds);
         mKeepClearAlgorithm.setStashOffset(mTvPipBoundsState.getStashOffset());
+        mKeepClearAlgorithm.setPipPermanentDecorInsets(
+                mTvPipBoundsState.getPipMenuPermanentDecorInsets());
+        mKeepClearAlgorithm.setPipTemporaryDecorInsets(
+                mTvPipBoundsState.getPipMenuTemporaryDecorInsets());
 
         final Placement placement = mKeepClearAlgorithm.calculatePipPosition(
                 pipSize,
@@ -340,6 +345,7 @@
         final DisplayLayout displayLayout = mTvPipBoundsState.getDisplayLayout();
         final float expandedRatio =
                 mTvPipBoundsState.getDesiredTvExpandedAspectRatio(); // width / height
+        final Insets pipDecorations = mTvPipBoundsState.getPipMenuPermanentDecorInsets();
 
         final Size expandedSize;
         if (expandedRatio == 0) {
@@ -352,7 +358,8 @@
             if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_HORIZONTAL) {
                 expandedSize = mTvPipBoundsState.getTvExpandedSize();
             } else {
-                int maxHeight = displayLayout.height() - (2 * mScreenEdgeInsets.y);
+                int maxHeight = displayLayout.height() - (2 * mScreenEdgeInsets.y)
+                        - pipDecorations.top - pipDecorations.bottom;
                 float aspectRatioHeight = mFixedExpandedWidthInPx / expandedRatio;
 
                 if (maxHeight > aspectRatioHeight) {
@@ -374,7 +381,8 @@
             if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_VERTICAL) {
                 expandedSize = mTvPipBoundsState.getTvExpandedSize();
             } else {
-                int maxWidth = displayLayout.width() - (2 * mScreenEdgeInsets.x);
+                int maxWidth = displayLayout.width() - (2 * mScreenEdgeInsets.x)
+                        - pipDecorations.left - pipDecorations.right;
                 float aspectRatioWidth = mFixedExpandedHeightInPx * expandedRatio;
                 if (maxWidth > aspectRatioWidth) {
                     if (DEBUG) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
index 9865548..ea07499 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
@@ -24,6 +24,7 @@
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
+import android.graphics.Insets;
 import android.util.Size;
 import android.view.Gravity;
 
@@ -60,7 +61,8 @@
     private @Orientation int mTvFixedPipOrientation;
     private int mTvPipGravity;
     private @Nullable Size mTvExpandedSize;
-
+    private @NonNull Insets mPipMenuPermanentDecorInsets = Insets.NONE;
+    private @NonNull Insets mPipMenuTemporaryDecorInsets = Insets.NONE;
 
     public TvPipBoundsState(@NonNull Context context) {
         super(context);
@@ -159,4 +161,19 @@
         return mIsTvExpandedPipSupported;
     }
 
+    public void setPipMenuPermanentDecorInsets(@NonNull Insets permanentInsets) {
+        mPipMenuPermanentDecorInsets = permanentInsets;
+    }
+
+    public @NonNull Insets getPipMenuPermanentDecorInsets() {
+        return mPipMenuPermanentDecorInsets;
+    }
+
+    public void setPipMenuTemporaryDecorInsets(@NonNull Insets temporaryDecorInsets) {
+        mPipMenuTemporaryDecorInsets = temporaryDecorInsets;
+    }
+
+    public @NonNull Insets getPipMenuTemporaryDecorInsets() {
+        return mPipMenuTemporaryDecorInsets;
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index 46b8e60..0e1f5a2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -115,6 +115,7 @@
     private int mPipForceCloseDelay;
 
     private int mResizeAnimationDuration;
+    private int mEduTextWindowExitAnimationDurationMs;
 
     public static Pip create(
             Context context,
@@ -323,11 +324,15 @@
         }
     }
 
+    private void updatePinnedStackBounds() {
+        updatePinnedStackBounds(mResizeAnimationDuration);
+    }
+
     /**
      * Update the PiP bounds based on the state of the PiP and keep clear areas.
      * Animates to the current PiP bounds, and schedules unstashing the PiP if necessary.
      */
-    private void updatePinnedStackBounds() {
+    private void updatePinnedStackBounds(int animationDuration) {
         if (mState == STATE_NO_PIP) {
             return;
         }
@@ -353,23 +358,26 @@
             mUnstashRunnable = null;
         }
         if (!disallowStashing && placement.getUnstashDestinationBounds() != null) {
-            mUnstashRunnable = () -> movePinnedStackTo(placement.getUnstashDestinationBounds());
+            mUnstashRunnable = () -> {
+                movePinnedStackTo(placement.getUnstashDestinationBounds(), animationDuration);
+            };
             mMainHandler.postAtTime(mUnstashRunnable, placement.getUnstashTime());
         }
     }
 
     /** Animates the PiP to the given bounds. */
     private void movePinnedStackTo(Rect bounds) {
+        movePinnedStackTo(bounds, mResizeAnimationDuration);
+    }
+
+    /** Animates the PiP to the given bounds with the given animation duration. */
+    private void movePinnedStackTo(Rect bounds, int animationDuration) {
         if (DEBUG) {
             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                     "%s: movePinnedStack() - new pip bounds: %s", TAG, bounds.toShortString());
         }
         mPipTaskOrganizer.scheduleAnimateResizePip(bounds,
-                mResizeAnimationDuration, rect -> {
-                    if (DEBUG) {
-                        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                                "%s: movePinnedStack() animation done", TAG);
-                    }
+                animationDuration, rect -> {
                     mTvPipMenuController.updateExpansionState();
                 });
     }
@@ -408,6 +416,11 @@
         onPipDisappeared();
     }
 
+    @Override
+    public void closeEduText() {
+        updatePinnedStackBounds(mEduTextWindowExitAnimationDurationMs);
+    }
+
     private void registerSessionListenerForCurrentUser() {
         mPipMediaController.registerSessionListenerForCurrentUser();
     }
@@ -457,6 +470,7 @@
             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                     "%s: onPipTransition_Started(), state=%s", TAG, stateToName(mState));
         }
+        mTvPipMenuController.notifyPipAnimating(true);
     }
 
     @Override
@@ -465,6 +479,7 @@
             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                     "%s: onPipTransition_Canceled(), state=%s", TAG, stateToName(mState));
         }
+        mTvPipMenuController.notifyPipAnimating(false);
     }
 
     @Override
@@ -476,6 +491,7 @@
             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                     "%s: onPipTransition_Finished(), state=%s", TAG, stateToName(mState));
         }
+        mTvPipMenuController.notifyPipAnimating(false);
     }
 
     private void setState(@State int state) {
@@ -491,6 +507,8 @@
         final Resources res = mContext.getResources();
         mResizeAnimationDuration = res.getInteger(R.integer.config_pipResizeAnimationDuration);
         mPipForceCloseDelay = res.getInteger(R.integer.config_pipForceCloseDelay);
+        mEduTextWindowExitAnimationDurationMs =
+                res.getInteger(R.integer.pip_edu_text_window_exit_animation_duration_ms);
     }
 
     private void registerTaskStackListenerCallback(TaskStackListenerImpl taskStackListener) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt
index 5ac7a72..9ede443 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.pip.tv
 
+import android.graphics.Insets
 import android.graphics.Point
 import android.graphics.Rect
 import android.util.Size
@@ -94,6 +95,13 @@
     private var lastAreasOverlappingUnstashPosition: Set<Rect> = emptySet()
     private var lastStashTime: Long = Long.MIN_VALUE
 
+    /** Spaces around the PiP that we should leave space for when placing the PiP. Permanent PiP
+     * decorations are relevant for calculating intersecting keep clear areas */
+    private var pipPermanentDecorInsets = Insets.NONE
+    /** Spaces around the PiP that we should leave space for when placing the PiP. Temporary PiP
+     * decorations are not relevant for calculating intersecting keep clear areas */
+    private var pipTemporaryDecorInsets = Insets.NONE
+
     /**
      * Calculates the position the PiP should be placed at, taking into consideration the
      * given keep clear areas.
@@ -120,20 +128,29 @@
     ): Placement {
         val transformedRestrictedAreas = transformAndFilterAreas(restrictedAreas)
         val transformedUnrestrictedAreas = transformAndFilterAreas(unrestrictedAreas)
-        val pipAnchorBounds = getNormalPipAnchorBounds(pipSize, transformedMovementBounds)
 
+        val pipSizeWithAllDecors = addDecors(pipSize)
+        val pipAnchorBoundsWithAllDecors =
+                getNormalPipAnchorBounds(pipSizeWithAllDecors, transformedMovementBounds)
+
+        val pipAnchorBoundsWithPermanentDecors = removeTemporaryDecors(pipAnchorBoundsWithAllDecors)
         val result = calculatePipPositionTransformed(
-            pipAnchorBounds,
+            pipAnchorBoundsWithPermanentDecors,
             transformedRestrictedAreas,
             transformedUnrestrictedAreas
         )
 
-        val screenSpaceBounds = fromTransformedSpace(result.bounds)
+        val pipBounds = removePermanentDecors(fromTransformedSpace(result.bounds))
+        val anchorBounds = removePermanentDecors(fromTransformedSpace(result.anchorBounds))
+        val unstashedDestBounds = result.unstashDestinationBounds?.let {
+            removePermanentDecors(fromTransformedSpace(it))
+        }
+
         return Placement(
-            screenSpaceBounds,
-            fromTransformedSpace(result.anchorBounds),
-            getStashType(screenSpaceBounds, movementBounds),
-            result.unstashDestinationBounds?.let { fromTransformedSpace(it) },
+            pipBounds,
+            anchorBounds,
+            getStashType(pipBounds, movementBounds),
+            unstashedDestBounds,
             result.unstashTime
         )
     }
@@ -447,6 +464,16 @@
         transformedMovementBounds = toTransformedSpace(movementBounds)
     }
 
+    fun setPipPermanentDecorInsets(insets: Insets) {
+        if (pipPermanentDecorInsets == insets) return
+        pipPermanentDecorInsets = insets
+    }
+
+    fun setPipTemporaryDecorInsets(insets: Insets) {
+        if (pipTemporaryDecorInsets == insets) return
+        pipTemporaryDecorInsets = insets
+    }
+
     /**
      * @param open Whether this event marks the opening of an occupied segment
      * @param pos The coordinate of this event
@@ -735,6 +762,35 @@
         return horizontal && vertical
     }
 
+    /**
+     * Adds space around [size] to leave space for decorations that will be drawn around the pip
+     */
+    private fun addDecors(size: Size): Size {
+        val bounds = Rect(0, 0, size.width, size.height)
+        bounds.inset(pipPermanentDecorInsets)
+        bounds.inset(pipTemporaryDecorInsets)
+
+        return Size(bounds.width(), bounds.height())
+    }
+
+    /**
+     * Removes the space that was reserved for permanent decorations around the pip
+     */
+    private fun removePermanentDecors(bounds: Rect): Rect {
+        val pipDecorReverseInsets = Insets.subtract(Insets.NONE, pipPermanentDecorInsets)
+        bounds.inset(pipDecorReverseInsets)
+        return bounds
+    }
+
+    /**
+     * Removes the space that was reserved for temporary decorations around the pip
+     */
+    private fun removeTemporaryDecors(bounds: Rect): Rect {
+        val pipDecorReverseInsets = Insets.subtract(Insets.NONE, pipTemporaryDecorInsets)
+        bounds.inset(pipDecorReverseInsets)
+        return bounds
+    }
+
     private fun Rect.offsetCopy(dx: Int, dy: Int) = Rect(this).apply { offset(dx, dy) }
     private fun Rect.intersectsY(other: Rect) = bottom >= other.top && top <= other.bottom
     private fun Rect.intersectsX(other: Rect) = right >= other.left && left <= other.right
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
index 35c34ac..7b8dcf7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
@@ -25,13 +25,17 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ParceledListSlice;
+import android.graphics.Insets;
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.os.Handler;
 import android.os.RemoteException;
+import android.view.LayoutInflater;
 import android.view.SurfaceControl;
 import android.view.SyncRtSurfaceTransactionApplier;
+import android.view.View;
+import android.view.ViewRootImpl;
 import android.view.WindowManagerGlobal;
 
 import androidx.annotation.Nullable;
@@ -53,15 +57,20 @@
 public class TvPipMenuController implements PipMenuController, TvPipMenuView.Listener {
     private static final String TAG = "TvPipMenuController";
     private static final boolean DEBUG = TvPipController.DEBUG;
+    private static final String BACKGROUND_WINDOW_TITLE = "PipBackgroundView";
 
     private final Context mContext;
     private final SystemWindows mSystemWindows;
     private final TvPipBoundsState mTvPipBoundsState;
     private final Handler mMainHandler;
+    private final int mPipMenuBorderWidth;
+    private final int mPipEduTextShowDurationMs;
+    private final int mPipEduTextHeight;
 
     private Delegate mDelegate;
     private SurfaceControl mLeash;
     private TvPipMenuView mPipMenuView;
+    private View mPipBackgroundView;
 
     // User can actively move the PiP via the DPAD.
     private boolean mInMoveMode;
@@ -74,6 +83,7 @@
     private RemoteAction mCloseAction;
 
     private SyncRtSurfaceTransactionApplier mApplier;
+    private SyncRtSurfaceTransactionApplier mBackgroundApplier;
     RectF mTmpSourceRectF = new RectF();
     RectF mTmpDestinationRectF = new RectF();
     Matrix mMoveTransform = new Matrix();
@@ -91,6 +101,7 @@
             if (DEBUG) e.printStackTrace();
         }
     };
+    private final Runnable mCloseEduTextRunnable = this::closeEduText;
 
     public TvPipMenuController(Context context, TvPipBoundsState tvPipBoundsState,
             SystemWindows systemWindows, PipMediaController pipMediaController,
@@ -113,6 +124,13 @@
                 mainHandler, Context.RECEIVER_EXPORTED);
 
         pipMediaController.addActionListener(this::onMediaActionsChanged);
+
+        mPipEduTextShowDurationMs = context.getResources()
+                .getInteger(R.integer.pip_edu_text_show_duration_ms);
+        mPipEduTextHeight = context.getResources()
+                .getDimensionPixelSize(R.dimen.pip_menu_edu_text_view_height);
+        mPipMenuBorderWidth = context.getResources()
+                .getDimensionPixelSize(R.dimen.pip_menu_border_width);
     }
 
     void setDelegate(Delegate delegate) {
@@ -138,24 +156,63 @@
         }
 
         mLeash = leash;
-        attachPipMenuView();
+        attachPipMenu();
     }
 
-    private void attachPipMenuView() {
+    private void attachPipMenu() {
         if (DEBUG) {
             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                    "%s: attachPipMenuView()", TAG);
+                    "%s: attachPipMenu()", TAG);
         }
 
         if (mPipMenuView != null) {
-            detachPipMenuView();
+            detachPipMenu();
         }
 
+        attachPipBackgroundView();
+        attachPipMenuView();
+
+        mTvPipBoundsState.setPipMenuPermanentDecorInsets(Insets.of(-mPipMenuBorderWidth,
+                    -mPipMenuBorderWidth, -mPipMenuBorderWidth, -mPipMenuBorderWidth));
+        mTvPipBoundsState.setPipMenuTemporaryDecorInsets(Insets.of(0, 0, 0, -mPipEduTextHeight));
+        mMainHandler.postDelayed(mCloseEduTextRunnable, mPipEduTextShowDurationMs);
+    }
+
+    private void attachPipMenuView() {
         mPipMenuView = new TvPipMenuView(mContext);
         mPipMenuView.setListener(this);
-        mSystemWindows.addView(mPipMenuView,
-                getPipMenuLayoutParams(MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */),
-                0, SHELL_ROOT_LAYER_PIP);
+        setUpViewSurfaceZOrder(mPipMenuView, 1);
+        addPipMenuViewToSystemWindows(mPipMenuView, MENU_WINDOW_TITLE);
+    }
+
+    private void attachPipBackgroundView() {
+        mPipBackgroundView = LayoutInflater.from(mContext)
+                .inflate(R.layout.tv_pip_menu_background, null);
+        setUpViewSurfaceZOrder(mPipBackgroundView, -1);
+        addPipMenuViewToSystemWindows(mPipBackgroundView, BACKGROUND_WINDOW_TITLE);
+    }
+
+    private void setUpViewSurfaceZOrder(View v, int zOrderRelativeToPip) {
+        v.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
+                @Override
+                public void onViewAttachedToWindow(View v) {
+                    v.getViewRootImpl().addSurfaceChangedCallback(
+                            new PipMenuSurfaceChangedCallback(v, zOrderRelativeToPip));
+                }
+
+                @Override
+                public void onViewDetachedFromWindow(View v) {
+                }
+        });
+    }
+
+    private void addPipMenuViewToSystemWindows(View v, String title) {
+        mSystemWindows.addView(v, getPipMenuLayoutParams(title, 0 /* width */, 0 /* height */),
+                0 /* displayId */, SHELL_ROOT_LAYER_PIP);
+    }
+
+    void notifyPipAnimating(boolean animating) {
+        mPipMenuView.setEduTextActive(!animating);
     }
 
     void showMovementMenuOnly() {
@@ -171,8 +228,7 @@
     @Override
     public void showMenu() {
         if (DEBUG) {
-            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                    "%s: showMenu()", TAG);
+            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMenu()", TAG);
         }
         mInMoveMode = false;
         mCloseAfterExitMoveMenu = false;
@@ -183,27 +239,31 @@
         if (mPipMenuView == null) {
             return;
         }
-        Rect menuBounds = getMenuBounds(mTvPipBoundsState.getBounds());
-        mSystemWindows.updateViewLayout(mPipMenuView, getPipMenuLayoutParams(
-                MENU_WINDOW_TITLE, menuBounds.width(), menuBounds.height()));
+        maybeCloseEduText();
         maybeUpdateMenuViewActions();
         updateExpansionState();
 
-        SurfaceControl menuSurfaceControl = getSurfaceControl();
-        if (menuSurfaceControl != null) {
-            SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-            t.setRelativeLayer(mPipMenuView.getWindowSurfaceControl(), mLeash, 1);
-            t.setPosition(menuSurfaceControl, menuBounds.left, menuBounds.top);
-            t.apply();
-        }
         grantPipMenuFocus(true);
         if (mInMoveMode) {
             mPipMenuView.showMoveMenu(mDelegate.getPipGravity());
         } else {
-            mPipMenuView.showButtonMenu();
+            mPipMenuView.showButtonsMenu();
         }
     }
 
+    private void maybeCloseEduText() {
+        if (mMainHandler.hasCallbacks(mCloseEduTextRunnable)) {
+            mMainHandler.removeCallbacks(mCloseEduTextRunnable);
+            mCloseEduTextRunnable.run();
+        }
+    }
+
+    private void closeEduText() {
+        mTvPipBoundsState.setPipMenuTemporaryDecorInsets(Insets.NONE);
+        mPipMenuView.hideEduText();
+        mDelegate.closeEduText();
+    }
+
     void updateGravity(int gravity) {
         mPipMenuView.showMovementHints(gravity);
     }
@@ -214,12 +274,8 @@
         mPipMenuView.setIsExpanded(mTvPipBoundsState.isTvPipExpanded());
     }
 
-    private Rect getMenuBounds(Rect pipBounds) {
-        int extraSpaceInPx = mContext.getResources()
-                .getDimensionPixelSize(R.dimen.pip_menu_outer_space);
-        Rect menuBounds = new Rect(pipBounds);
-        menuBounds.inset(-extraSpaceInPx, -extraSpaceInPx);
-        return menuBounds;
+    private Rect calculateMenuSurfaceBounds(Rect pipBounds) {
+        return mPipMenuView.getPipMenuContainerBounds(pipBounds);
     }
 
     void closeMenu() {
@@ -227,11 +283,12 @@
             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                     "%s: closeMenu()", TAG);
         }
+
         if (mPipMenuView == null) {
             return;
         }
 
-        mPipMenuView.hideAll();
+        mPipMenuView.hideAllUserControls();
         grantPipMenuFocus(false);
         mDelegate.onMenuClosed();
     }
@@ -266,7 +323,7 @@
         }
         if (mInMoveMode) {
             mInMoveMode = false;
-            mPipMenuView.showButtonMenu();
+            mPipMenuView.showButtonsMenu();
             return true;
         }
         return false;
@@ -287,7 +344,8 @@
     @Override
     public void detach() {
         closeMenu();
-        detachPipMenuView();
+        mMainHandler.removeCallbacks(mCloseEduTextRunnable);
+        detachPipMenu();
         mLeash = null;
     }
 
@@ -346,20 +404,15 @@
 
     @Override
     public boolean isMenuVisible() {
-        boolean isVisible = mPipMenuView != null && mPipMenuView.isVisible();
-        if (DEBUG) {
-            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                    "%s: isMenuVisible: %b", TAG, isVisible);
-        }
-        return isVisible;
+        return true;
     }
 
     /**
      * Does an immediate window crop of the PiP menu.
      */
     @Override
-    public void resizePipMenu(@android.annotation.Nullable SurfaceControl pipLeash,
-            @android.annotation.Nullable SurfaceControl.Transaction t,
+    public void resizePipMenu(@Nullable SurfaceControl pipLeash,
+            @Nullable SurfaceControl.Transaction t,
             Rect destinationBounds) {
         if (DEBUG) {
             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
@@ -373,24 +426,36 @@
             return;
         }
 
-        SurfaceControl surfaceControl = getSurfaceControl();
-        SyncRtSurfaceTransactionApplier.SurfaceParams
-                params = new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(surfaceControl)
-                .withWindowCrop(getMenuBounds(destinationBounds))
+        final Rect menuBounds = calculateMenuSurfaceBounds(destinationBounds);
+
+        final SurfaceControl frontSurface = getSurfaceControl(mPipMenuView);
+        final SyncRtSurfaceTransactionApplier.SurfaceParams frontParams =
+                new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(frontSurface)
+                .withWindowCrop(menuBounds)
                 .build();
+
+        final SurfaceControl backSurface = getSurfaceControl(mPipBackgroundView);
+        final SyncRtSurfaceTransactionApplier.SurfaceParams backParams =
+                new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(backSurface)
+                .withWindowCrop(menuBounds)
+                .build();
+
+        // TODO(b/226580399): switch to using SurfaceSyncer (see b/200284684) to synchronize the
+        // animations of the pip surface with the content of the front and back menu surfaces
+        mBackgroundApplier.scheduleApply(backParams);
         if (pipLeash != null && t != null) {
-            SyncRtSurfaceTransactionApplier.SurfaceParams
+            final SyncRtSurfaceTransactionApplier.SurfaceParams
                     pipParams = new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(pipLeash)
                     .withMergeTransaction(t)
                     .build();
-            mApplier.scheduleApply(params, pipParams);
+            mApplier.scheduleApply(frontParams, pipParams);
         } else {
-            mApplier.scheduleApply(params);
+            mApplier.scheduleApply(frontParams);
         }
     }
 
-    private SurfaceControl getSurfaceControl() {
-        return mSystemWindows.getViewSurface(mPipMenuView);
+    private SurfaceControl getSurfaceControl(View v) {
+        return mSystemWindows.getViewSurface(v);
     }
 
     @Override
@@ -412,44 +477,52 @@
             return;
         }
 
-        Rect menuDestBounds = getMenuBounds(pipDestBounds);
-        Rect mTmpSourceBounds = new Rect();
+        final Rect menuDestBounds = calculateMenuSurfaceBounds(pipDestBounds);
+        final Rect tmpSourceBounds = new Rect();
         // If there is no pip leash supplied, that means the PiP leash is already finalized
         // resizing and the PiP menu is also resized. We then want to do a scale from the current
         // new menu bounds.
         if (pipLeash != null && transaction != null) {
             if (DEBUG) {
                 ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                        "%s: mTmpSourceBounds based on mPipMenuView.getBoundsOnScreen()", TAG);
+                        "%s: tmpSourceBounds based on mPipMenuView.getBoundsOnScreen()", TAG);
             }
-            mPipMenuView.getBoundsOnScreen(mTmpSourceBounds);
+            mPipMenuView.getBoundsOnScreen(tmpSourceBounds);
         } else {
             if (DEBUG) {
                 ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                        "%s: mTmpSourceBounds based on menu width and height", TAG);
+                        "%s: tmpSourceBounds based on menu width and height", TAG);
             }
-            mTmpSourceBounds.set(0, 0, menuDestBounds.width(), menuDestBounds.height());
+            tmpSourceBounds.set(0, 0, menuDestBounds.width(), menuDestBounds.height());
         }
 
-        mTmpSourceRectF.set(mTmpSourceBounds);
+        mTmpSourceRectF.set(tmpSourceBounds);
         mTmpDestinationRectF.set(menuDestBounds);
-        mMoveTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL);
+        mMoveTransform.setTranslate(mTmpDestinationRectF.left, mTmpDestinationRectF.top);
 
-        SurfaceControl surfaceControl = getSurfaceControl();
-        SyncRtSurfaceTransactionApplier.SurfaceParams params =
-                new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
-                        surfaceControl)
+        final SurfaceControl frontSurface = getSurfaceControl(mPipMenuView);
+        final SyncRtSurfaceTransactionApplier.SurfaceParams frontParams =
+                new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(frontSurface)
                         .withMatrix(mMoveTransform)
                         .build();
 
+        final SurfaceControl backSurface = getSurfaceControl(mPipBackgroundView);
+        final SyncRtSurfaceTransactionApplier.SurfaceParams backParams =
+                new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(backSurface)
+                        .withMatrix(mMoveTransform)
+                        .build();
+
+        // TODO(b/226580399): switch to using SurfaceSyncer (see b/200284684) to synchronize the
+        // animations of the pip surface with the content of the front and back menu surfaces
+        mBackgroundApplier.scheduleApply(backParams);
         if (pipLeash != null && transaction != null) {
-            SyncRtSurfaceTransactionApplier.SurfaceParams
-                    pipParams = new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(pipLeash)
+            final SyncRtSurfaceTransactionApplier.SurfaceParams pipParams =
+                    new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(pipLeash)
                     .withMergeTransaction(transaction)
                     .build();
-            mApplier.scheduleApply(params, pipParams);
+            mApplier.scheduleApply(frontParams, pipParams);
         } else {
-            mApplier.scheduleApply(params);
+            mApplier.scheduleApply(frontParams);
         }
 
         if (mPipMenuView.getViewRootImpl() != null) {
@@ -470,29 +543,40 @@
         if (mApplier == null) {
             mApplier = new SyncRtSurfaceTransactionApplier(mPipMenuView);
         }
+        if (mBackgroundApplier == null) {
+            mBackgroundApplier = new SyncRtSurfaceTransactionApplier(mPipBackgroundView);
+        }
         return true;
     }
 
-    private void detachPipMenuView() {
-        if (mPipMenuView == null) {
-            return;
+    private void detachPipMenu() {
+        if (mPipMenuView != null) {
+            mApplier = null;
+            mSystemWindows.removeView(mPipMenuView);
+            mPipMenuView = null;
         }
 
-        mApplier = null;
-        mSystemWindows.removeView(mPipMenuView);
-        mPipMenuView = null;
+        if (mPipBackgroundView != null) {
+            mBackgroundApplier = null;
+            mSystemWindows.removeView(mPipBackgroundView);
+            mPipBackgroundView = null;
+        }
     }
 
     @Override
     public void updateMenuBounds(Rect destinationBounds) {
-        Rect menuBounds = getMenuBounds(destinationBounds);
+        final Rect menuBounds = calculateMenuSurfaceBounds(destinationBounds);
         if (DEBUG) {
             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                     "%s: updateMenuBounds: %s", TAG, menuBounds.toShortString());
         }
+        mSystemWindows.updateViewLayout(mPipBackgroundView,
+                getPipMenuLayoutParams(BACKGROUND_WINDOW_TITLE, menuBounds.width(),
+                        menuBounds.height()));
         mSystemWindows.updateViewLayout(mPipMenuView,
                 getPipMenuLayoutParams(MENU_WINDOW_TITLE, menuBounds.width(),
                         menuBounds.height()));
+
         if (mPipMenuView != null) {
             mPipMenuView.updateLayout(destinationBounds);
         }
@@ -538,6 +622,8 @@
 
         void onMenuClosed();
 
+        void closeEduText();
+
         void closePip();
     }
 
@@ -555,4 +641,30 @@
                     "%s: Unable to update focus, %s", TAG, e);
         }
     }
+
+    private class PipMenuSurfaceChangedCallback implements ViewRootImpl.SurfaceChangedCallback {
+        private final View mView;
+        private final int mZOrder;
+
+        PipMenuSurfaceChangedCallback(View v, int zOrder) {
+            mView = v;
+            mZOrder = zOrder;
+        }
+
+        @Override
+        public void surfaceCreated(SurfaceControl.Transaction t) {
+            final SurfaceControl sc = getSurfaceControl(mView);
+            if (sc != null) {
+                t.setRelativeLayer(sc, mLeash, mZOrder);
+            }
+        }
+
+        @Override
+        public void surfaceReplaced(SurfaceControl.Transaction t) {
+        }
+
+        @Override
+        public void surfaceDestroyed() {
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
index 9529d04..5b0db8c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
@@ -25,11 +25,17 @@
 import static android.view.KeyEvent.KEYCODE_DPAD_UP;
 import static android.view.KeyEvent.KEYCODE_ENTER;
 
+import android.animation.ValueAnimator;
 import android.app.PendingIntent;
 import android.app.RemoteAction;
 import android.content.Context;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
 import android.os.Handler;
+import android.text.Annotation;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.SpannedString;
 import android.util.AttributeSet;
 import android.view.Gravity;
 import android.view.KeyEvent;
@@ -40,6 +46,7 @@
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
+import android.widget.TextView;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -49,6 +56,7 @@
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
 
@@ -67,6 +75,15 @@
     private final LinearLayout mActionButtonsContainer;
     private final View mMenuFrameView;
     private final List<TvPipMenuActionButton> mAdditionalButtons = new ArrayList<>();
+    private final View mPipFrameView;
+    private final View mPipView;
+    private final TextView mEduTextView;
+    private final View mEduTextContainerView;
+    private final int mPipMenuOuterSpace;
+    private final int mPipMenuBorderWidth;
+    private final int mEduTextFadeExitAnimationDurationMs;
+    private final int mEduTextSlideExitAnimationDurationMs;
+    private int mEduTextHeight;
 
     private final ImageView mArrowUp;
     private final ImageView mArrowRight;
@@ -76,11 +93,13 @@
     private final ViewGroup mScrollView;
     private final ViewGroup mHorizontalScrollView;
 
-    private Rect mCurrentBounds;
+    private Rect mCurrentPipBounds;
 
     private final TvPipMenuActionButton mExpandButton;
     private final TvPipMenuActionButton mCloseButton;
 
+    private final int mPipMenuFadeAnimationDuration;
+
     public TvPipMenuView(@NonNull Context context) {
         this(context, null);
     }
@@ -116,21 +135,86 @@
         mHorizontalScrollView = findViewById(R.id.tv_pip_menu_horizontal_scroll);
 
         mMenuFrameView = findViewById(R.id.tv_pip_menu_frame);
+        mPipFrameView = findViewById(R.id.tv_pip_border);
+        mPipView = findViewById(R.id.tv_pip);
 
         mArrowUp = findViewById(R.id.tv_pip_menu_arrow_up);
         mArrowRight = findViewById(R.id.tv_pip_menu_arrow_right);
         mArrowDown = findViewById(R.id.tv_pip_menu_arrow_down);
         mArrowLeft = findViewById(R.id.tv_pip_menu_arrow_left);
+
+        mEduTextView = findViewById(R.id.tv_pip_menu_edu_text);
+        mEduTextContainerView = findViewById(R.id.tv_pip_menu_edu_text_container);
+
+        mPipMenuFadeAnimationDuration = context.getResources()
+                .getInteger(R.integer.pip_menu_fade_animation_duration);
+        mPipMenuOuterSpace = context.getResources()
+                .getDimensionPixelSize(R.dimen.pip_menu_outer_space);
+        mPipMenuBorderWidth = context.getResources()
+                .getDimensionPixelSize(R.dimen.pip_menu_border_width);
+        mEduTextHeight = context.getResources()
+                .getDimensionPixelSize(R.dimen.pip_menu_edu_text_view_height);
+        mEduTextFadeExitAnimationDurationMs = context.getResources()
+                .getInteger(R.integer.pip_edu_text_view_exit_animation_duration_ms);
+        mEduTextSlideExitAnimationDurationMs = context.getResources()
+                .getInteger(R.integer.pip_edu_text_window_exit_animation_duration_ms);
+
+        initEduText();
     }
 
-    void updateLayout(Rect updatedBounds) {
-        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                "%s: update menu layout: %s", TAG, updatedBounds.toShortString());
-        boolean previouslyVertical =
-                mCurrentBounds != null && mCurrentBounds.height() > mCurrentBounds.width();
-        boolean vertical = updatedBounds.height() > updatedBounds.width();
+    void initEduText() {
+        final SpannedString eduText = (SpannedString) getResources().getText(R.string.pip_edu_text);
+        final SpannableString spannableString = new SpannableString(eduText);
+        Arrays.stream(eduText.getSpans(0, eduText.length(), Annotation.class)).findFirst()
+                .ifPresent(annotation -> {
+                    final Drawable icon =
+                            getResources().getDrawable(R.drawable.home_icon, mContext.getTheme());
+                    if (icon != null) {
+                        icon.mutate();
+                        icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
+                        spannableString.setSpan(new CenteredImageSpan(icon),
+                                eduText.getSpanStart(annotation),
+                                eduText.getSpanEnd(annotation),
+                                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+                    }
+                });
 
-        mCurrentBounds = updatedBounds;
+        mEduTextView.setText(spannableString);
+    }
+
+    void setEduTextActive(boolean active) {
+        mEduTextView.setSelected(active);
+    }
+
+    void hideEduText() {
+        final ValueAnimator heightAnimation = ValueAnimator.ofInt(mEduTextHeight, 0);
+        heightAnimation.setDuration(mEduTextSlideExitAnimationDurationMs);
+        heightAnimation.setInterpolator(TvPipInterpolators.BROWSE);
+        heightAnimation.addUpdateListener(animator -> {
+            mEduTextHeight = (int) animator.getAnimatedValue();
+        });
+        mEduTextView.animate()
+                .alpha(0f)
+                .setInterpolator(TvPipInterpolators.EXIT)
+                .setDuration(mEduTextFadeExitAnimationDurationMs)
+                .withEndAction(() -> {
+                    mEduTextContainerView.setVisibility(GONE);
+                }).start();
+        heightAnimation.start();
+    }
+
+    void updateLayout(Rect updatedPipBounds) {
+        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                "%s: update menu layout: %s", TAG, updatedPipBounds.toShortString());
+
+        boolean previouslyVertical =
+                mCurrentPipBounds != null && mCurrentPipBounds.height() > mCurrentPipBounds.width();
+        boolean vertical = updatedPipBounds.height() > updatedPipBounds.width();
+
+        mCurrentPipBounds = updatedPipBounds;
+
+        updatePipFrameBounds();
+
         if (previouslyVertical == vertical) {
             if (DEBUG) {
                 ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
@@ -158,6 +242,38 @@
         mHorizontalScrollView.setVisibility(vertical ? GONE : VISIBLE);
     }
 
+    Rect getPipMenuContainerBounds(Rect pipBounds) {
+        final Rect menuUiBounds = new Rect(pipBounds);
+        menuUiBounds.inset(-mPipMenuOuterSpace, -mPipMenuOuterSpace);
+        menuUiBounds.bottom += mEduTextHeight;
+        return menuUiBounds;
+    }
+
+    /**
+     * Update mPipFrameView's bounds according to the new pip window bounds. We can't
+     * make mPipFrameView match_parent, because the pip menu might contain other content around
+     * the pip window (e.g. edu text).
+     * TvPipMenuView needs to account for this so that it can draw a white border around the whole
+     * pip menu when it gains focus.
+     */
+    private void updatePipFrameBounds() {
+        final ViewGroup.LayoutParams pipFrameParams = mPipFrameView.getLayoutParams();
+        if (pipFrameParams != null) {
+            pipFrameParams.width = mCurrentPipBounds.width() + 2 * mPipMenuBorderWidth;
+            pipFrameParams.height = mCurrentPipBounds.height() + 2 * mPipMenuBorderWidth;
+            mPipFrameView.setLayoutParams(pipFrameParams);
+        }
+
+        final ViewGroup.LayoutParams pipViewParams = mPipView.getLayoutParams();
+        if (pipViewParams != null) {
+            pipViewParams.width = mCurrentPipBounds.width();
+            pipViewParams.height = mCurrentPipBounds.height();
+            mPipView.setLayoutParams(pipViewParams);
+        }
+
+
+    }
+
     void setListener(@Nullable Listener listener) {
         mListener = listener;
     }
@@ -184,30 +300,32 @@
         if (DEBUG) {
             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMoveMenu()", TAG);
         }
-        showMenuButtons(false);
+        showButtonsMenu(false);
         showMovementHints(gravity);
-        showMenuFrame(true);
+        setFrameHighlighted(true);
     }
 
-    void showButtonMenu() {
+    void showButtonsMenu() {
         if (DEBUG) {
-            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showButtonMenu()", TAG);
+            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                    "%s: showButtonsMenu()", TAG);
         }
-        showMenuButtons(true);
+        showButtonsMenu(true);
         hideMovementHints();
-        showMenuFrame(true);
+        setFrameHighlighted(true);
     }
 
     /**
      * Hides all menu views, including the menu frame.
      */
-    void hideAll() {
+    void hideAllUserControls() {
         if (DEBUG) {
-            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: hideAll()", TAG);
+            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                    "%s: hideAllUserControls()", TAG);
         }
-        showMenuButtons(false);
+        showButtonsMenu(false);
         hideMovementHints();
-        showMenuFrame(false);
+        setFrameHighlighted(false);
     }
 
     private void animateAlphaTo(float alpha, View view) {
@@ -217,7 +335,7 @@
         view.animate()
                 .alpha(alpha)
                 .setInterpolator(alpha == 0f ? TvPipInterpolators.EXIT : TvPipInterpolators.ENTER)
-                .setDuration(500)
+                .setDuration(mPipMenuFadeAnimationDuration)
                 .withStartAction(() -> {
                     if (alpha != 0) {
                         view.setVisibility(VISIBLE);
@@ -230,15 +348,6 @@
                 });
     }
 
-    boolean isVisible() {
-        return mMenuFrameView.getAlpha() != 0f
-                || mActionButtonsContainer.getAlpha() != 0f
-                || mArrowUp.getAlpha() != 0f
-                || mArrowRight.getAlpha() != 0f
-                || mArrowDown.getAlpha() != 0f
-                || mArrowLeft.getAlpha() != 0f;
-    }
-
     void setAdditionalActions(List<RemoteAction> actions, RemoteAction closeAction,
             Handler mainHandler) {
         if (DEBUG) {
@@ -423,18 +532,18 @@
     }
 
     /**
-     * Show or hide the pip user actions.
+     * Show or hide the pip buttons menu.
      */
-    public void showMenuButtons(boolean show) {
+    public void showButtonsMenu(boolean show) {
         if (DEBUG) {
             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                    "%s: showMenuButtons: %b", TAG, show);
+                    "%s: showUserActions: %b", TAG, show);
         }
         animateAlphaTo(show ? 1 : 0, mActionButtonsContainer);
     }
 
-    private void showMenuFrame(boolean show) {
-        animateAlphaTo(show ? 1 : 0, mMenuFrameView);
+    private void setFrameHighlighted(boolean highlighted) {
+        mMenuFrameView.setActivated(highlighted);
     }
 
     interface Listener {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
index fd6e59e..cde4247 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
@@ -20,8 +20,10 @@
 import static android.graphics.Color.WHITE;
 import static android.graphics.Color.alpha;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+import static android.view.ViewRootImpl.LOCAL_LAYOUT;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
+import static android.view.WindowLayout.UNSPECIFIED_LENGTH;
 import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
 import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
 import static android.view.WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
@@ -51,6 +53,7 @@
 import android.app.ActivityManager;
 import android.app.ActivityManager.TaskDescription;
 import android.app.ActivityThread;
+import android.app.WindowConfiguration;
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Color;
@@ -77,6 +80,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowInsets;
+import android.view.WindowLayout;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
 import android.window.ClientWindowFrames;
@@ -208,6 +212,8 @@
         final IWindowSession session = WindowManagerGlobal.getWindowSession();
         final SurfaceControl surfaceControl = new SurfaceControl();
         final ClientWindowFrames tmpFrames = new ClientWindowFrames();
+        final WindowLayout windowLayout = new WindowLayout();
+        final Rect displayCutoutSafe = new Rect();
 
         final InsetsSourceControl[] tmpControls = new InsetsSourceControl[0];
         final MergedConfiguration tmpMergedConfiguration = new MergedConfiguration();
@@ -244,9 +250,25 @@
         window.setOuter(snapshotSurface);
         try {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "TaskSnapshot#relayout");
-            session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0,
-                    tmpFrames, tmpMergedConfiguration, surfaceControl, tmpInsetsState,
-                    tmpControls, new Bundle());
+            if (LOCAL_LAYOUT) {
+                if (!surfaceControl.isValid()) {
+                    session.updateVisibility(window, layoutParams, View.VISIBLE,
+                            tmpMergedConfiguration, surfaceControl, tmpInsetsState, tmpControls);
+                }
+                tmpInsetsState.getDisplayCutoutSafe(displayCutoutSafe);
+                final WindowConfiguration winConfig =
+                        tmpMergedConfiguration.getMergedConfiguration().windowConfiguration;
+                windowLayout.computeFrames(layoutParams, tmpInsetsState, displayCutoutSafe,
+                        winConfig.getBounds(), winConfig.getWindowingMode(), UNSPECIFIED_LENGTH,
+                        UNSPECIFIED_LENGTH, info.requestedVisibilities,
+                        null /* attachedWindowFrame */, 1f /* compatScale */, tmpFrames);
+                session.updateLayout(window, layoutParams, 0 /* flags */, tmpFrames,
+                        UNSPECIFIED_LENGTH, UNSPECIFIED_LENGTH);
+            } else {
+                session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0,
+                        tmpFrames, tmpMergedConfiguration, surfaceControl, tmpInsetsState,
+                        tmpControls, new Bundle());
+            }
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         } catch (RemoteException e) {
             snapshotSurface.clearWindowSynced();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 542ddee..3ea57b0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -437,15 +437,15 @@
                     cornerRadius = 0;
                 }
 
-                if (a.getShowBackground()) {
+                if (a.getShowBackdrop()) {
                     if (info.getAnimationOptions().getBackgroundColor() != 0) {
                         // If available use the background color provided through AnimationOptions
                         backgroundColorForTransition =
                                 info.getAnimationOptions().getBackgroundColor();
-                    } else if (a.getBackgroundColor() != 0) {
+                    } else if (a.getBackdropColor() != 0) {
                         // Otherwise fallback on the background color provided through the animation
                         // definition.
-                        backgroundColorForTransition = a.getBackgroundColor();
+                        backgroundColorForTransition = a.getBackdropColor();
                     } else if (change.getBackgroundColor() != 0) {
                         // Otherwise default to the window's background color if provided through
                         // the theme as the background color for the animation - the top most window
diff --git a/libs/WindowManager/Shell/tests/OWNERS b/libs/WindowManager/Shell/tests/OWNERS
index a244d14..f4efc37 100644
--- a/libs/WindowManager/Shell/tests/OWNERS
+++ b/libs/WindowManager/Shell/tests/OWNERS
@@ -1,4 +1,4 @@
-# Bug component: 909476
+# Bug component: 1157642
 # includes OWNERS from parent directories
 natanieljr@google.com
 pablogamito@google.com
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
index fb404b9..684e5cad 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
@@ -17,6 +17,8 @@
 package com.android.wm.shell.flicker.bubble
 
 import android.platform.test.annotations.Presubmit
+import android.view.WindowInsets
+import android.view.WindowManager
 import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import androidx.test.uiautomator.By
@@ -59,6 +61,16 @@
                 }
             }
             transitions {
+                // Swipe & wait for the notification shade to expand so all can be seen
+                val wm = context.getSystemService(WindowManager::class.java)
+                val metricInsets = wm.getCurrentWindowMetrics().windowInsets
+                val insets = metricInsets.getInsetsIgnoringVisibility(
+                        WindowInsets.Type.statusBars()
+                        or WindowInsets.Type.displayCutout())
+                device.swipe(100, insets.top + 100, 100, device.getDisplayHeight() / 2, 4)
+                device.waitForIdle(2000)
+                instrumentation.uiAutomation.syncInputTransactions()
+
                 val notification = device.wait(Until.findObject(
                     By.text("BubbleChat")), FIND_OBJECT_TIMEOUT)
                 notification?.click() ?: error("Notification not found")
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
index 3fe6f02..9a8c894 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.flicker.pip
 
+import android.platform.test.annotations.Presubmit
 import android.view.Surface
 import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
@@ -84,7 +85,7 @@
     override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
 
     /** {@inheritDoc}  */
-    @FlakyTest(bugId = 197726610)
+    @Presubmit
     @Test
     override fun pipLayerExpands() = super.pipLayerExpands()
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
index 5fc80db..9f3fcea 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
@@ -111,7 +111,7 @@
     /**
      * Checks that the visible region of [pipApp] always expands during the animation
      */
-    @Presubmit
+    @FlakyTest(bugId = 228012337)
     @Test
     fun pipLayerExpands() {
         val layerName = pipApp.component.toLayerName()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
index 6af01e2..c1ee1a7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
@@ -33,7 +33,6 @@
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.wm.shell.flicker.helpers.FixedAppHelper
 import org.junit.Assume
-import org.junit.Before
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -69,11 +68,6 @@
     private val screenBoundsStart = WindowUtils.getDisplayBounds(testSpec.startRotation)
     private val screenBoundsEnd = WindowUtils.getDisplayBounds(testSpec.endRotation)
 
-    @Before
-    open fun before() {
-        Assume.assumeFalse(isShellTransitionsEnabled)
-    }
-
     override val transition: FlickerBuilder.() -> Unit
         get() = buildTransition(eachRun = false) {
             setup {
@@ -135,15 +129,30 @@
     /**
      * Checks that [pipApp] layer is within [screenBoundsStart] at the start of the transition
      */
-    @Presubmit
-    @Test
-    open fun pipLayerRotates_StartingBounds() {
+    private fun pipLayerRotates_StartingBounds_internal() {
         testSpec.assertLayersStart {
             visibleRegion(pipApp.component).coversAtMost(screenBoundsStart)
         }
     }
 
     /**
+     * Checks that [pipApp] layer is within [screenBoundsStart] at the start of the transition
+     */
+    @Presubmit
+    @Test
+    fun pipLayerRotates_StartingBounds() {
+        Assume.assumeFalse(isShellTransitionsEnabled)
+        pipLayerRotates_StartingBounds_internal()
+    }
+
+    @FlakyTest(bugId = 228024285)
+    @Test
+    fun pipLayerRotates_StartingBounds_ShellTransit() {
+        Assume.assumeTrue(isShellTransitionsEnabled)
+        pipLayerRotates_StartingBounds_internal()
+    }
+
+    /**
      * Checks that [pipApp] layer is within [screenBoundsEnd] at the end of the transition
      */
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
index 81403d0..e40f2bc 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
@@ -47,7 +47,6 @@
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @Group4
-@FlakyTest(bugId = 218604389)
 open class SetRequestedOrientationWhilePinnedTest(
     testSpec: FlickerTestParameter
 ) : PipTransition(testSpec) {
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleHelper.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleHelper.java
index d743dff..6cd93ef 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleHelper.java
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleHelper.java
@@ -22,7 +22,6 @@
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.Person;
-import android.app.RemoteInput;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Point;
@@ -116,24 +115,20 @@
     private Notification.Builder getNotificationBuilder(int id) {
         Person chatBot = new Person.Builder()
                 .setBot(true)
-                .setName("BubbleBot")
+                .setName("BubbleChat")
                 .setImportant(true)
                 .build();
-
-        RemoteInput remoteInput = new RemoteInput.Builder("key")
-                .setLabel("Reply")
-                .build();
-
         String shortcutId = "BubbleChat";
         return new Notification.Builder(mContext, CHANNEL_ID)
                 .setChannelId(CHANNEL_ID)
                 .setShortcutId(shortcutId)
+                .setContentTitle("BubbleChat")
                 .setContentIntent(PendingIntent.getActivity(mContext, 0,
                         new Intent(mContext, LaunchBubbleActivity.class),
                         PendingIntent.FLAG_UPDATE_CURRENT))
                 .setStyle(new Notification.MessagingStyle(chatBot)
-                        .setConversationTitle("Bubble Chat")
-                        .addMessage("Hello? This is bubble: " + id,
+                        .setConversationTitle("BubbleChat")
+                        .addMessage("BubbleChat",
                                 SystemClock.currentThreadTimeMillis() - 300000, chatBot)
                         .addMessage("Is it me, " + id + ", you're looking for?",
                                 SystemClock.currentThreadTimeMillis(), chatBot)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
index b66c2b4..3bf06cc 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
@@ -19,11 +19,8 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.notNull;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
@@ -33,6 +30,7 @@
 import android.view.IWindowManager;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
+import android.view.InsetsVisibilities;
 
 import androidx.test.filters.SmallTest;
 
@@ -42,7 +40,6 @@
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
-import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 import java.util.List;
@@ -99,7 +96,8 @@
         mController.addInsetsChangedListener(DEFAULT_DISPLAY, defaultListener);
         mController.addInsetsChangedListener(SECOND_DISPLAY, secondListener);
 
-        mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).topFocusedWindowChanged(null);
+        mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).topFocusedWindowChanged(null,
+                new InsetsVisibilities());
         mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).insetsChanged(null);
         mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).insetsControlChanged(null, null);
         mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).showInsets(0, false);
@@ -118,7 +116,8 @@
         assertTrue(secondListener.showInsetsCount == 0);
         assertTrue(secondListener.hideInsetsCount == 0);
 
-        mInsetsControllersByDisplayId.get(SECOND_DISPLAY).topFocusedWindowChanged(null);
+        mInsetsControllersByDisplayId.get(SECOND_DISPLAY).topFocusedWindowChanged(null,
+                new InsetsVisibilities());
         mInsetsControllersByDisplayId.get(SECOND_DISPLAY).insetsChanged(null);
         mInsetsControllersByDisplayId.get(SECOND_DISPLAY).insetsControlChanged(null, null);
         mInsetsControllersByDisplayId.get(SECOND_DISPLAY).showInsets(0, false);
@@ -165,7 +164,8 @@
         int hideInsetsCount = 0;
 
         @Override
-        public void topFocusedWindowChanged(String packageName) {
+        public void topFocusedWindowChanged(String packageName,
+                InsetsVisibilities requestedVisibilities) {
             topFocusedWindowChangedCount++;
         }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index aef298e..deb955b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -74,6 +74,7 @@
     @Mock private PhonePipMenuController mMockPhonePipMenuController;
     @Mock private PipAppOpsListener mMockPipAppOpsListener;
     @Mock private PipBoundsAlgorithm mMockPipBoundsAlgorithm;
+    @Mock private PipKeepClearAlgorithm mMockPipKeepClearAlgorithm;
     @Mock private PipSnapAlgorithm mMockPipSnapAlgorithm;
     @Mock private PipMediaController mMockPipMediaController;
     @Mock private PipTaskOrganizer mMockPipTaskOrganizer;
@@ -97,9 +98,10 @@
             return null;
         }).when(mMockExecutor).execute(any());
         mPipController = new PipController(mContext, mMockDisplayController,
-                mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipBoundsState,
-                mMockPipMediaController, mMockPhonePipMenuController, mMockPipTaskOrganizer,
-                mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper,
+                mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
+                mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController,
+                mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTouchHandler,
+                mMockPipTransitionController, mMockWindowManagerShellWrapper,
                 mMockTaskStackListener, mMockOneHandedController, mMockExecutor);
         when(mMockPipBoundsAlgorithm.getSnapAlgorithm()).thenReturn(mMockPipSnapAlgorithm);
         when(mMockPipTouchHandler.getMotionHelper()).thenReturn(mMockPipMotionHelper);
@@ -128,9 +130,10 @@
         when(spyContext.getPackageManager()).thenReturn(mockPackageManager);
 
         assertNull(PipController.create(spyContext, mMockDisplayController,
-                mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipBoundsState,
-                mMockPipMediaController, mMockPhonePipMenuController, mMockPipTaskOrganizer,
-                mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper,
+                mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
+                mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController,
+                mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTouchHandler,
+                mMockPipTransitionController, mMockWindowManagerShellWrapper,
                 mMockTaskStackListener, mMockOneHandedController, mMockExecutor));
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithmTest.java
new file mode 100644
index 0000000..f657b5e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithmTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.wm.shell.pip.phone;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import android.graphics.Rect;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Set;
+
+/**
+ * Unit tests against {@link PipKeepClearAlgorithm}.
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class PipKeepClearAlgorithmTest extends ShellTestCase {
+
+    private PipKeepClearAlgorithm mPipKeepClearAlgorithm;
+
+
+    @Before
+    public void setUp() throws Exception {
+        mPipKeepClearAlgorithm = new PipKeepClearAlgorithm();
+    }
+
+    @Test
+    public void adjust_withCollidingRestrictedKeepClearAreas_movesBounds() {
+        final Rect inBounds = new Rect(0, 0, 100, 100);
+        final Rect keepClearRect = new Rect(50, 50, 150, 150);
+
+        final Rect outBounds = mPipKeepClearAlgorithm.adjust(inBounds, Set.of(keepClearRect),
+                Set.of());
+
+        assertFalse(outBounds.contains(keepClearRect));
+    }
+
+    @Test
+    public void adjust_withNonCollidingRestrictedKeepClearAreas_boundsDoNotChange() {
+        final Rect inBounds = new Rect(0, 0, 100, 100);
+        final Rect keepClearRect = new Rect(100, 100, 150, 150);
+
+        final Rect outBounds = mPipKeepClearAlgorithm.adjust(inBounds, Set.of(keepClearRect),
+                Set.of());
+
+        assertEquals(inBounds, outBounds);
+    }
+
+    @Test
+    public void adjust_withCollidingUnrestrictedKeepClearAreas_boundsDoNotChange() {
+        // TODO(b/183746978): update this test to accommodate for the updated algorithm
+        final Rect inBounds = new Rect(0, 0, 100, 100);
+        final Rect keepClearRect = new Rect(50, 50, 150, 150);
+
+        final Rect outBounds = mPipKeepClearAlgorithm.adjust(inBounds, Set.of(),
+                Set.of(keepClearRect));
+
+        assertEquals(inBounds, outBounds);
+    }
+
+    @Test
+    public void adjust_withNonCollidingUnrestrictedKeepClearAreas_boundsDoNotChange() {
+        final Rect inBounds = new Rect(0, 0, 100, 100);
+        final Rect keepClearRect = new Rect(100, 100, 150, 150);
+
+        final Rect outBounds = mPipKeepClearAlgorithm.adjust(inBounds, Set.of(),
+                Set.of(keepClearRect));
+
+        assertEquals(inBounds, outBounds);
+    }
+}
diff --git a/location/java/android/location/LocationDeviceConfig.java b/location/java/android/location/LocationDeviceConfig.java
index c55eed9..7d22681 100644
--- a/location/java/android/location/LocationDeviceConfig.java
+++ b/location/java/android/location/LocationDeviceConfig.java
@@ -24,6 +24,30 @@
 public final class LocationDeviceConfig {
 
     /**
+     * Package/tag combinations that are allowlisted for ignoring location settings (may retrieve
+     * location even when user location settings are off), for advanced driver-assistance systems
+     * only.
+     *
+     * <p>Package/tag combinations are separated by commas (","), and with in each combination is a
+     * package name followed by 0 or more attribution tags, separated by semicolons (";"). If a
+     * package is followed by 0 attribution tags, this is interpreted the same as the wildcard
+     * value. There are two special interpreted values for attribution tags, the wildcard value
+     * ("*") which represents all attribution tags, and the null value ("null"), which is converted
+     * to the null string (since attribution tags may be null). This format implies that attribution
+     * tags which should be on this list may not contain semicolons.
+     *
+     * <p>Examples of valid entries:
+     *
+     * <ul>
+     *   <li>android
+     *   <li>android;*
+     *   <li>android;*,com.example.app;null;my_attr
+     *   <li>android;*,com.example.app;null;my_attr,com.example.otherapp;my_attr
+     * </ul>
+     */
+    public static final String ADAS_SETTINGS_ALLOWLIST = "adas_settings_allowlist";
+
+    /**
      * Package/tag combinations that are allowedlisted for ignoring location settings (may retrieve
      * location even when user location settings are off, and may ignore throttling, etc), for
      * emergency purposes only.
@@ -39,10 +63,10 @@
      * <p>Examples of valid entries:
      *
      * <ul>
-     *     <li>android</li>
-     *     <li>android;*</li>
-     *     <li>android;*,com.example.app;null;my_attr</li>
-     *     <li>android;*,com.example.app;null;my_attr,com.example.otherapp;my_attr</li>
+     *   <li>android
+     *   <li>android;*
+     *   <li>android;*,com.example.app;null;my_attr
+     *   <li>android;*,com.example.app;null;my_attr,com.example.otherapp;my_attr
      * </ul>
      */
     public static final String IGNORE_SETTINGS_ALLOWLIST = "ignore_settings_allowlist";
diff --git a/media/Android.bp b/media/Android.bp
index 2f9c520..36da253 100644
--- a/media/Android.bp
+++ b/media/Android.bp
@@ -36,7 +36,7 @@
     ],
     imports: [
         "android.media.audio.common.types",
-        "android.media.soundtrigger.types",
+        "android.media.soundtrigger.types-V1",
         "media_permission-aidl",
     ],
 }
@@ -166,4 +166,11 @@
     imports: [
         "android.media.audio.common.types",
     ],
+    versions_with_info: [
+        {
+            version: "1",
+            imports: ["android.media.audio.common.types-V1"],
+        },
+    ],
+
 }
diff --git a/media/aidl_api/android.media.audio.common.types/1/.hash b/media/aidl_api/android.media.audio.common.types/1/.hash
new file mode 100644
index 0000000..328aab4
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/1/.hash
@@ -0,0 +1 @@
+985ad49c876a50c60c726dc87f60cb598fd087ad
diff --git a/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioChannelLayout.aidl b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioChannelLayout.aidl
new file mode 100644
index 0000000..6845ac1
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioChannelLayout.aidl
@@ -0,0 +1,134 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+union AudioChannelLayout {
+  int none = 0;
+  int invalid = 0;
+  int indexMask;
+  int layoutMask;
+  int voiceMask;
+  const int INDEX_MASK_1 = 1;
+  const int INDEX_MASK_2 = 3;
+  const int INDEX_MASK_3 = 7;
+  const int INDEX_MASK_4 = 15;
+  const int INDEX_MASK_5 = 31;
+  const int INDEX_MASK_6 = 63;
+  const int INDEX_MASK_7 = 127;
+  const int INDEX_MASK_8 = 255;
+  const int INDEX_MASK_9 = 511;
+  const int INDEX_MASK_10 = 1023;
+  const int INDEX_MASK_11 = 2047;
+  const int INDEX_MASK_12 = 4095;
+  const int INDEX_MASK_13 = 8191;
+  const int INDEX_MASK_14 = 16383;
+  const int INDEX_MASK_15 = 32767;
+  const int INDEX_MASK_16 = 65535;
+  const int INDEX_MASK_17 = 131071;
+  const int INDEX_MASK_18 = 262143;
+  const int INDEX_MASK_19 = 524287;
+  const int INDEX_MASK_20 = 1048575;
+  const int INDEX_MASK_21 = 2097151;
+  const int INDEX_MASK_22 = 4194303;
+  const int INDEX_MASK_23 = 8388607;
+  const int INDEX_MASK_24 = 16777215;
+  const int LAYOUT_MONO = 1;
+  const int LAYOUT_STEREO = 3;
+  const int LAYOUT_2POINT1 = 11;
+  const int LAYOUT_TRI = 7;
+  const int LAYOUT_TRI_BACK = 259;
+  const int LAYOUT_3POINT1 = 15;
+  const int LAYOUT_2POINT0POINT2 = 786435;
+  const int LAYOUT_2POINT1POINT2 = 786443;
+  const int LAYOUT_3POINT0POINT2 = 786439;
+  const int LAYOUT_3POINT1POINT2 = 786447;
+  const int LAYOUT_QUAD = 51;
+  const int LAYOUT_QUAD_SIDE = 1539;
+  const int LAYOUT_SURROUND = 263;
+  const int LAYOUT_PENTA = 55;
+  const int LAYOUT_5POINT1 = 63;
+  const int LAYOUT_5POINT1_SIDE = 1551;
+  const int LAYOUT_5POINT1POINT2 = 786495;
+  const int LAYOUT_5POINT1POINT4 = 184383;
+  const int LAYOUT_6POINT1 = 319;
+  const int LAYOUT_7POINT1 = 1599;
+  const int LAYOUT_7POINT1POINT2 = 788031;
+  const int LAYOUT_7POINT1POINT4 = 185919;
+  const int LAYOUT_9POINT1POINT4 = 50517567;
+  const int LAYOUT_9POINT1POINT6 = 51303999;
+  const int LAYOUT_13POINT_360RA = 7534087;
+  const int LAYOUT_22POINT2 = 16777215;
+  const int LAYOUT_MONO_HAPTIC_A = 1073741825;
+  const int LAYOUT_STEREO_HAPTIC_A = 1073741827;
+  const int LAYOUT_HAPTIC_AB = 1610612736;
+  const int LAYOUT_MONO_HAPTIC_AB = 1610612737;
+  const int LAYOUT_STEREO_HAPTIC_AB = 1610612739;
+  const int LAYOUT_FRONT_BACK = 260;
+  const int INTERLEAVE_LEFT = 0;
+  const int INTERLEAVE_RIGHT = 1;
+  const int CHANNEL_FRONT_LEFT = 1;
+  const int CHANNEL_FRONT_RIGHT = 2;
+  const int CHANNEL_FRONT_CENTER = 4;
+  const int CHANNEL_LOW_FREQUENCY = 8;
+  const int CHANNEL_BACK_LEFT = 16;
+  const int CHANNEL_BACK_RIGHT = 32;
+  const int CHANNEL_FRONT_LEFT_OF_CENTER = 64;
+  const int CHANNEL_FRONT_RIGHT_OF_CENTER = 128;
+  const int CHANNEL_BACK_CENTER = 256;
+  const int CHANNEL_SIDE_LEFT = 512;
+  const int CHANNEL_SIDE_RIGHT = 1024;
+  const int CHANNEL_TOP_CENTER = 2048;
+  const int CHANNEL_TOP_FRONT_LEFT = 4096;
+  const int CHANNEL_TOP_FRONT_CENTER = 8192;
+  const int CHANNEL_TOP_FRONT_RIGHT = 16384;
+  const int CHANNEL_TOP_BACK_LEFT = 32768;
+  const int CHANNEL_TOP_BACK_CENTER = 65536;
+  const int CHANNEL_TOP_BACK_RIGHT = 131072;
+  const int CHANNEL_TOP_SIDE_LEFT = 262144;
+  const int CHANNEL_TOP_SIDE_RIGHT = 524288;
+  const int CHANNEL_BOTTOM_FRONT_LEFT = 1048576;
+  const int CHANNEL_BOTTOM_FRONT_CENTER = 2097152;
+  const int CHANNEL_BOTTOM_FRONT_RIGHT = 4194304;
+  const int CHANNEL_LOW_FREQUENCY_2 = 8388608;
+  const int CHANNEL_FRONT_WIDE_LEFT = 16777216;
+  const int CHANNEL_FRONT_WIDE_RIGHT = 33554432;
+  const int CHANNEL_HAPTIC_B = 536870912;
+  const int CHANNEL_HAPTIC_A = 1073741824;
+  const int VOICE_UPLINK_MONO = 16384;
+  const int VOICE_DNLINK_MONO = 32768;
+  const int VOICE_CALL_MONO = 49152;
+  const int CHANNEL_VOICE_UPLINK = 16384;
+  const int CHANNEL_VOICE_DNLINK = 32768;
+}
diff --git a/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioConfig.aidl b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioConfig.aidl
new file mode 100644
index 0000000..6b8686c
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioConfig.aidl
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable AudioConfig {
+  android.media.audio.common.AudioConfigBase base;
+  android.media.audio.common.AudioOffloadInfo offloadInfo;
+  long frameCount;
+}
diff --git a/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioConfigBase.aidl b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioConfigBase.aidl
new file mode 100644
index 0000000..f3e716b
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioConfigBase.aidl
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable AudioConfigBase {
+  int sampleRate;
+  android.media.audio.common.AudioChannelLayout channelMask;
+  android.media.audio.common.AudioFormatDescription format;
+}
diff --git a/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioContentType.aidl b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioContentType.aidl
new file mode 100644
index 0000000..f9ac614
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioContentType.aidl
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@Backing(type="int") @VintfStability
+enum AudioContentType {
+  UNKNOWN = 0,
+  SPEECH = 1,
+  MUSIC = 2,
+  MOVIE = 3,
+  SONIFICATION = 4,
+  ULTRASOUND = 1997,
+}
diff --git a/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioDevice.aidl b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioDevice.aidl
new file mode 100644
index 0000000..fb5cb62
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioDevice.aidl
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable AudioDevice {
+  android.media.audio.common.AudioDeviceDescription type;
+  android.media.audio.common.AudioDeviceAddress address;
+}
diff --git a/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioDeviceAddress.aidl b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioDeviceAddress.aidl
new file mode 100644
index 0000000..905d3aa
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioDeviceAddress.aidl
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+union AudioDeviceAddress {
+  @utf8InCpp String id;
+  byte[] mac;
+  byte[] ipv4;
+  int[] ipv6;
+  int[] alsa;
+}
diff --git a/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioDeviceDescription.aidl b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioDeviceDescription.aidl
new file mode 100644
index 0000000..1c66a8f
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioDeviceDescription.aidl
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable AudioDeviceDescription {
+  android.media.audio.common.AudioDeviceType type = android.media.audio.common.AudioDeviceType.NONE;
+  @utf8InCpp String connection;
+  const @utf8InCpp String CONNECTION_ANALOG = "analog";
+  const @utf8InCpp String CONNECTION_BT_A2DP = "bt-a2dp";
+  const @utf8InCpp String CONNECTION_BT_LE = "bt-le";
+  const @utf8InCpp String CONNECTION_BT_SCO = "bt-sco";
+  const @utf8InCpp String CONNECTION_BUS = "bus";
+  const @utf8InCpp String CONNECTION_HDMI = "hdmi";
+  const @utf8InCpp String CONNECTION_HDMI_ARC = "hdmi-arc";
+  const @utf8InCpp String CONNECTION_HDMI_EARC = "hdmi-earc";
+  const @utf8InCpp String CONNECTION_IP_V4 = "ip-v4";
+  const @utf8InCpp String CONNECTION_SPDIF = "spdif";
+  const @utf8InCpp String CONNECTION_WIRELESS = "wireless";
+  const @utf8InCpp String CONNECTION_USB = "usb";
+}
diff --git a/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioDeviceType.aidl b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioDeviceType.aidl
new file mode 100644
index 0000000..6a7b686
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioDeviceType.aidl
@@ -0,0 +1,71 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@Backing(type="int") @VintfStability
+enum AudioDeviceType {
+  NONE = 0,
+  IN_DEFAULT = 1,
+  IN_ACCESSORY = 2,
+  IN_AFE_PROXY = 3,
+  IN_DEVICE = 4,
+  IN_ECHO_REFERENCE = 5,
+  IN_FM_TUNER = 6,
+  IN_HEADSET = 7,
+  IN_LOOPBACK = 8,
+  IN_MICROPHONE = 9,
+  IN_MICROPHONE_BACK = 10,
+  IN_SUBMIX = 11,
+  IN_TELEPHONY_RX = 12,
+  IN_TV_TUNER = 13,
+  IN_DOCK = 14,
+  OUT_DEFAULT = 129,
+  OUT_ACCESSORY = 130,
+  OUT_AFE_PROXY = 131,
+  OUT_CARKIT = 132,
+  OUT_DEVICE = 133,
+  OUT_ECHO_CANCELLER = 134,
+  OUT_FM = 135,
+  OUT_HEADPHONE = 136,
+  OUT_HEADSET = 137,
+  OUT_HEARING_AID = 138,
+  OUT_LINE_AUX = 139,
+  OUT_SPEAKER = 140,
+  OUT_SPEAKER_EARPIECE = 141,
+  OUT_SPEAKER_SAFE = 142,
+  OUT_SUBMIX = 143,
+  OUT_TELEPHONY_TX = 144,
+  OUT_DOCK = 145,
+  OUT_BROADCAST = 146,
+}
diff --git a/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioEncapsulationMetadataType.aidl b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioEncapsulationMetadataType.aidl
new file mode 100644
index 0000000..0ee0dbb
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioEncapsulationMetadataType.aidl
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@Backing(type="int") @VintfStability
+enum AudioEncapsulationMetadataType {
+  NONE = 0,
+  FRAMEWORK_TUNER = 1,
+  DVB_AD_DESCRIPTOR = 2,
+}
diff --git a/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioEncapsulationMode.aidl b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioEncapsulationMode.aidl
new file mode 100644
index 0000000..0cf2f31
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioEncapsulationMode.aidl
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@Backing(type="byte") @VintfStability
+enum AudioEncapsulationMode {
+  INVALID = -1,
+  NONE = 0,
+  ELEMENTARY_STREAM = 1,
+  HANDLE = 2,
+}
diff --git a/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioEncapsulationType.aidl b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioEncapsulationType.aidl
new file mode 100644
index 0000000..8a31fc4
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioEncapsulationType.aidl
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@Backing(type="int") @VintfStability
+enum AudioEncapsulationType {
+  NONE = 0,
+  IEC61937 = 1,
+}
diff --git a/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioFormatDescription.aidl b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioFormatDescription.aidl
new file mode 100644
index 0000000..58c75eb
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioFormatDescription.aidl
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable AudioFormatDescription {
+  android.media.audio.common.AudioFormatType type = android.media.audio.common.AudioFormatType.DEFAULT;
+  android.media.audio.common.PcmType pcm = android.media.audio.common.PcmType.DEFAULT;
+  @utf8InCpp String encoding;
+}
diff --git a/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioFormatType.aidl b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioFormatType.aidl
new file mode 100644
index 0000000..7f55abe
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioFormatType.aidl
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@Backing(type="byte") @VintfStability
+enum AudioFormatType {
+  DEFAULT = 0,
+  NON_PCM = 0,
+  PCM = 1,
+  SYS_RESERVED_INVALID = -1,
+}
diff --git a/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioGain.aidl b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioGain.aidl
new file mode 100644
index 0000000..adc5b672
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioGain.aidl
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable AudioGain {
+  int mode;
+  android.media.audio.common.AudioChannelLayout channelMask;
+  int minValue;
+  int maxValue;
+  int defaultValue;
+  int stepValue;
+  int minRampMs;
+  int maxRampMs;
+  boolean useForVolume;
+}
diff --git a/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioGainConfig.aidl b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioGainConfig.aidl
new file mode 100644
index 0000000..01877c7
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioGainConfig.aidl
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable AudioGainConfig {
+  int index;
+  int mode;
+  android.media.audio.common.AudioChannelLayout channelMask;
+  int[] values;
+  int rampDurationMs;
+}
diff --git a/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioGainMode.aidl b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioGainMode.aidl
new file mode 100644
index 0000000..fddc20c
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioGainMode.aidl
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@Backing(type="byte") @VintfStability
+enum AudioGainMode {
+  JOINT = 0,
+  CHANNELS = 1,
+  RAMP = 2,
+}
diff --git a/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioInputFlags.aidl b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioInputFlags.aidl
new file mode 100644
index 0000000..37aa64a
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioInputFlags.aidl
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@Backing(type="int") @VintfStability
+enum AudioInputFlags {
+  FAST = 0,
+  HW_HOTWORD = 1,
+  RAW = 2,
+  SYNC = 3,
+  MMAP_NOIRQ = 4,
+  VOIP_TX = 5,
+  HW_AV_SYNC = 6,
+  DIRECT = 7,
+  ULTRASOUND = 8,
+}
diff --git a/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioIoFlags.aidl b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioIoFlags.aidl
new file mode 100644
index 0000000..4a46725
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioIoFlags.aidl
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+union AudioIoFlags {
+  int input;
+  int output;
+}
diff --git a/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioMMapPolicy.aidl b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioMMapPolicy.aidl
new file mode 100644
index 0000000..98bf0e5
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioMMapPolicy.aidl
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@Backing(type="int") @VintfStability
+enum AudioMMapPolicy {
+  UNSPECIFIED = 0,
+  NEVER = 1,
+  AUTO = 2,
+  ALWAYS = 3,
+}
diff --git a/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioMMapPolicyInfo.aidl b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioMMapPolicyInfo.aidl
new file mode 100644
index 0000000..7c4f75e
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioMMapPolicyInfo.aidl
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable AudioMMapPolicyInfo {
+  android.media.audio.common.AudioDevice device;
+  android.media.audio.common.AudioMMapPolicy mmapPolicy = android.media.audio.common.AudioMMapPolicy.UNSPECIFIED;
+}
diff --git a/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioMMapPolicyType.aidl b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioMMapPolicyType.aidl
new file mode 100644
index 0000000..efe8826
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioMMapPolicyType.aidl
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@Backing(type="int") @VintfStability
+enum AudioMMapPolicyType {
+  DEFAULT = 1,
+  EXCLUSIVE = 2,
+}
diff --git a/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioMode.aidl b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioMode.aidl
new file mode 100644
index 0000000..8256c1c
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioMode.aidl
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@Backing(type="int") @VintfStability
+enum AudioMode {
+  SYS_RESERVED_INVALID = -2,
+  SYS_RESERVED_CURRENT = -1,
+  NORMAL = 0,
+  RINGTONE = 1,
+  IN_CALL = 2,
+  IN_COMMUNICATION = 3,
+  CALL_SCREEN = 4,
+  SYS_RESERVED_CALL_REDIRECT = 5,
+  SYS_RESERVED_COMMUNICATION_REDIRECT = 6,
+}
diff --git a/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioOffloadInfo.aidl b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioOffloadInfo.aidl
new file mode 100644
index 0000000..40bd53b2
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioOffloadInfo.aidl
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable AudioOffloadInfo {
+  android.media.audio.common.AudioConfigBase base;
+  android.media.audio.common.AudioStreamType streamType = android.media.audio.common.AudioStreamType.INVALID;
+  int bitRatePerSecond;
+  long durationUs;
+  boolean hasVideo;
+  boolean isStreaming;
+  int bitWidth = 16;
+  int offloadBufferSize;
+  android.media.audio.common.AudioUsage usage = android.media.audio.common.AudioUsage.INVALID;
+  android.media.audio.common.AudioEncapsulationMode encapsulationMode = android.media.audio.common.AudioEncapsulationMode.INVALID;
+  int contentId;
+  int syncId;
+}
diff --git a/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioOutputFlags.aidl b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioOutputFlags.aidl
new file mode 100644
index 0000000..4a512a8
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioOutputFlags.aidl
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@Backing(type="int") @VintfStability
+enum AudioOutputFlags {
+  DIRECT = 0,
+  PRIMARY = 1,
+  FAST = 2,
+  DEEP_BUFFER = 3,
+  COMPRESS_OFFLOAD = 4,
+  NON_BLOCKING = 5,
+  HW_AV_SYNC = 6,
+  TTS = 7,
+  RAW = 8,
+  SYNC = 9,
+  IEC958_NONAUDIO = 10,
+  DIRECT_PCM = 11,
+  MMAP_NOIRQ = 12,
+  VOIP_RX = 13,
+  INCALL_MUSIC = 14,
+  GAPLESS_OFFLOAD = 15,
+  SPATIALIZER = 16,
+  ULTRASOUND = 17,
+}
diff --git a/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioPort.aidl b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioPort.aidl
new file mode 100644
index 0000000..970bbc0
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioPort.aidl
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable AudioPort {
+  int id;
+  @utf8InCpp String name;
+  android.media.audio.common.AudioProfile[] profiles;
+  android.media.audio.common.AudioIoFlags flags;
+  android.media.audio.common.ExtraAudioDescriptor[] extraAudioDescriptors;
+  android.media.audio.common.AudioGain[] gains;
+  android.media.audio.common.AudioPortExt ext;
+}
diff --git a/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioPortConfig.aidl b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioPortConfig.aidl
new file mode 100644
index 0000000..18e6406
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioPortConfig.aidl
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable AudioPortConfig {
+  int id;
+  int portId;
+  @nullable android.media.audio.common.Int sampleRate;
+  @nullable android.media.audio.common.AudioChannelLayout channelMask;
+  @nullable android.media.audio.common.AudioFormatDescription format;
+  @nullable android.media.audio.common.AudioGainConfig gain;
+  @nullable android.media.audio.common.AudioIoFlags flags;
+  android.media.audio.common.AudioPortExt ext;
+}
diff --git a/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioPortDeviceExt.aidl b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioPortDeviceExt.aidl
new file mode 100644
index 0000000..37d7041
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioPortDeviceExt.aidl
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable AudioPortDeviceExt {
+  android.media.audio.common.AudioDevice device;
+  int flags;
+  android.media.audio.common.AudioFormatDescription[] encodedFormats;
+  const int FLAG_INDEX_DEFAULT_DEVICE = 0;
+}
diff --git a/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioPortExt.aidl b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioPortExt.aidl
new file mode 100644
index 0000000..af9d9c4
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioPortExt.aidl
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+union AudioPortExt {
+  boolean unspecified;
+  android.media.audio.common.AudioPortDeviceExt device;
+  android.media.audio.common.AudioPortMixExt mix;
+  int session;
+}
diff --git a/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioPortMixExt.aidl b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioPortMixExt.aidl
new file mode 100644
index 0000000..5b74c0d
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioPortMixExt.aidl
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable AudioPortMixExt {
+  int handle;
+  android.media.audio.common.AudioPortMixExtUseCase usecase;
+  int maxOpenStreamCount;
+  int maxActiveStreamCount;
+  int recommendedMuteDurationMs;
+}
diff --git a/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioPortMixExtUseCase.aidl b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioPortMixExtUseCase.aidl
new file mode 100644
index 0000000..e9acb40
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioPortMixExtUseCase.aidl
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+union AudioPortMixExtUseCase {
+  boolean unspecified;
+  android.media.audio.common.AudioStreamType stream;
+  android.media.audio.common.AudioSource source;
+}
diff --git a/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioProfile.aidl b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioProfile.aidl
new file mode 100644
index 0000000..134cdd9
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioProfile.aidl
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable AudioProfile {
+  @utf8InCpp String name;
+  android.media.audio.common.AudioFormatDescription format;
+  android.media.audio.common.AudioChannelLayout[] channelMasks;
+  int[] sampleRates;
+  android.media.audio.common.AudioEncapsulationType encapsulationType = android.media.audio.common.AudioEncapsulationType.NONE;
+}
diff --git a/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioSource.aidl b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioSource.aidl
new file mode 100644
index 0000000..acf822e
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioSource.aidl
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@Backing(type="int") @VintfStability
+enum AudioSource {
+  SYS_RESERVED_INVALID = -1,
+  DEFAULT = 0,
+  MIC = 1,
+  VOICE_UPLINK = 2,
+  VOICE_DOWNLINK = 3,
+  VOICE_CALL = 4,
+  CAMCORDER = 5,
+  VOICE_RECOGNITION = 6,
+  VOICE_COMMUNICATION = 7,
+  REMOTE_SUBMIX = 8,
+  UNPROCESSED = 9,
+  VOICE_PERFORMANCE = 10,
+  ECHO_REFERENCE = 1997,
+  FM_TUNER = 1998,
+  HOTWORD = 1999,
+  ULTRASOUND = 2000,
+}
diff --git a/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioStandard.aidl b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioStandard.aidl
new file mode 100644
index 0000000..6c4490f
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioStandard.aidl
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@Backing(type="int") @VintfStability
+enum AudioStandard {
+  NONE = 0,
+  EDID = 1,
+}
diff --git a/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioStreamType.aidl b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioStreamType.aidl
new file mode 100644
index 0000000..bcfd374
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioStreamType.aidl
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@Backing(type="int") @VintfStability
+enum AudioStreamType {
+  INVALID = -2,
+  SYS_RESERVED_DEFAULT = -1,
+  VOICE_CALL = 0,
+  SYSTEM = 1,
+  RING = 2,
+  MUSIC = 3,
+  ALARM = 4,
+  NOTIFICATION = 5,
+  BLUETOOTH_SCO = 6,
+  ENFORCED_AUDIBLE = 7,
+  DTMF = 8,
+  TTS = 9,
+  ACCESSIBILITY = 10,
+  ASSISTANT = 11,
+  SYS_RESERVED_REROUTING = 12,
+  SYS_RESERVED_PATCH = 13,
+  CALL_ASSISTANT = 14,
+}
diff --git a/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioUsage.aidl b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioUsage.aidl
new file mode 100644
index 0000000..4c72455
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioUsage.aidl
@@ -0,0 +1,61 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@Backing(type="int") @VintfStability
+enum AudioUsage {
+  INVALID = -1,
+  UNKNOWN = 0,
+  MEDIA = 1,
+  VOICE_COMMUNICATION = 2,
+  VOICE_COMMUNICATION_SIGNALLING = 3,
+  ALARM = 4,
+  NOTIFICATION = 5,
+  NOTIFICATION_TELEPHONY_RINGTONE = 6,
+  SYS_RESERVED_NOTIFICATION_COMMUNICATION_REQUEST = 7,
+  SYS_RESERVED_NOTIFICATION_COMMUNICATION_INSTANT = 8,
+  SYS_RESERVED_NOTIFICATION_COMMUNICATION_DELAYED = 9,
+  NOTIFICATION_EVENT = 10,
+  ASSISTANCE_ACCESSIBILITY = 11,
+  ASSISTANCE_NAVIGATION_GUIDANCE = 12,
+  ASSISTANCE_SONIFICATION = 13,
+  GAME = 14,
+  VIRTUAL_SOURCE = 15,
+  ASSISTANT = 16,
+  CALL_ASSISTANT = 17,
+  EMERGENCY = 1000,
+  SAFETY = 1001,
+  VEHICLE_STATUS = 1002,
+  ANNOUNCEMENT = 1003,
+}
diff --git a/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioUuid.aidl b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioUuid.aidl
new file mode 100644
index 0000000..af307da
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/AudioUuid.aidl
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable AudioUuid {
+  int timeLow;
+  int timeMid;
+  int timeHiAndVersion;
+  int clockSeq;
+  byte[] node;
+}
diff --git a/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/ExtraAudioDescriptor.aidl b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/ExtraAudioDescriptor.aidl
new file mode 100644
index 0000000..2ae2405
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/ExtraAudioDescriptor.aidl
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable ExtraAudioDescriptor {
+  android.media.audio.common.AudioStandard standard = android.media.audio.common.AudioStandard.NONE;
+  byte[] audioDescriptor;
+  android.media.audio.common.AudioEncapsulationType encapsulationType = android.media.audio.common.AudioEncapsulationType.NONE;
+}
diff --git a/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/Int.aidl b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/Int.aidl
new file mode 100644
index 0000000..b0d3c49
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/Int.aidl
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable Int {
+  int value;
+}
diff --git a/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/PcmType.aidl b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/PcmType.aidl
new file mode 100644
index 0000000..79bfa62
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/1/android/media/audio/common/PcmType.aidl
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@Backing(type="byte") @VintfStability
+enum PcmType {
+  DEFAULT = 0,
+  UINT_8_BIT = 0,
+  INT_16_BIT = 1,
+  INT_32_BIT = 2,
+  FIXED_Q_8_24 = 3,
+  FLOAT_32_BIT = 4,
+  INT_24_BIT = 5,
+}
diff --git a/media/aidl_api/android.media.soundtrigger.types/1/.hash b/media/aidl_api/android.media.soundtrigger.types/1/.hash
new file mode 100644
index 0000000..52fd9ff
--- /dev/null
+++ b/media/aidl_api/android.media.soundtrigger.types/1/.hash
@@ -0,0 +1 @@
+8c9bb119feca43f118028b89bd5d1077bc23bb39
diff --git a/media/aidl_api/android.media.soundtrigger.types/1/android/media/soundtrigger/AudioCapabilities.aidl b/media/aidl_api/android.media.soundtrigger.types/1/android/media/soundtrigger/AudioCapabilities.aidl
new file mode 100644
index 0000000..5d88305
--- /dev/null
+++ b/media/aidl_api/android.media.soundtrigger.types/1/android/media/soundtrigger/AudioCapabilities.aidl
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.soundtrigger;
+/* @hide */
+@Backing(type="int") @VintfStability
+enum AudioCapabilities {
+  ECHO_CANCELLATION = 1,
+  NOISE_SUPPRESSION = 2,
+}
diff --git a/media/aidl_api/android.media.soundtrigger.types/1/android/media/soundtrigger/ConfidenceLevel.aidl b/media/aidl_api/android.media.soundtrigger.types/1/android/media/soundtrigger/ConfidenceLevel.aidl
new file mode 100644
index 0000000..5127a11
--- /dev/null
+++ b/media/aidl_api/android.media.soundtrigger.types/1/android/media/soundtrigger/ConfidenceLevel.aidl
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.soundtrigger;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable ConfidenceLevel {
+  int userId;
+  int levelPercent;
+}
diff --git a/media/aidl_api/android.media.soundtrigger.types/1/android/media/soundtrigger/ModelParameter.aidl b/media/aidl_api/android.media.soundtrigger.types/1/android/media/soundtrigger/ModelParameter.aidl
new file mode 100644
index 0000000..aadbf80
--- /dev/null
+++ b/media/aidl_api/android.media.soundtrigger.types/1/android/media/soundtrigger/ModelParameter.aidl
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.soundtrigger;
+/* @hide */
+@Backing(type="int") @VintfStability
+enum ModelParameter {
+  INVALID = -1,
+  THRESHOLD_FACTOR = 0,
+}
diff --git a/media/aidl_api/android.media.soundtrigger.types/1/android/media/soundtrigger/ModelParameterRange.aidl b/media/aidl_api/android.media.soundtrigger.types/1/android/media/soundtrigger/ModelParameterRange.aidl
new file mode 100644
index 0000000..f29b728
--- /dev/null
+++ b/media/aidl_api/android.media.soundtrigger.types/1/android/media/soundtrigger/ModelParameterRange.aidl
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.soundtrigger;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable ModelParameterRange {
+  int minInclusive;
+  int maxInclusive;
+}
diff --git a/media/aidl_api/android.media.soundtrigger.types/1/android/media/soundtrigger/Phrase.aidl b/media/aidl_api/android.media.soundtrigger.types/1/android/media/soundtrigger/Phrase.aidl
new file mode 100644
index 0000000..11029ba
--- /dev/null
+++ b/media/aidl_api/android.media.soundtrigger.types/1/android/media/soundtrigger/Phrase.aidl
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.soundtrigger;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable Phrase {
+  int id;
+  int recognitionModes;
+  int[] users;
+  String locale;
+  String text;
+}
diff --git a/media/aidl_api/android.media.soundtrigger.types/1/android/media/soundtrigger/PhraseRecognitionEvent.aidl b/media/aidl_api/android.media.soundtrigger.types/1/android/media/soundtrigger/PhraseRecognitionEvent.aidl
new file mode 100644
index 0000000..b75d1b8
--- /dev/null
+++ b/media/aidl_api/android.media.soundtrigger.types/1/android/media/soundtrigger/PhraseRecognitionEvent.aidl
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.soundtrigger;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable PhraseRecognitionEvent {
+  android.media.soundtrigger.RecognitionEvent common;
+  android.media.soundtrigger.PhraseRecognitionExtra[] phraseExtras;
+}
diff --git a/media/aidl_api/android.media.soundtrigger.types/1/android/media/soundtrigger/PhraseRecognitionExtra.aidl b/media/aidl_api/android.media.soundtrigger.types/1/android/media/soundtrigger/PhraseRecognitionExtra.aidl
new file mode 100644
index 0000000..e417c69
--- /dev/null
+++ b/media/aidl_api/android.media.soundtrigger.types/1/android/media/soundtrigger/PhraseRecognitionExtra.aidl
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.soundtrigger;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable PhraseRecognitionExtra {
+  int id;
+  int recognitionModes;
+  int confidenceLevel;
+  android.media.soundtrigger.ConfidenceLevel[] levels;
+}
diff --git a/media/aidl_api/android.media.soundtrigger.types/1/android/media/soundtrigger/PhraseSoundModel.aidl b/media/aidl_api/android.media.soundtrigger.types/1/android/media/soundtrigger/PhraseSoundModel.aidl
new file mode 100644
index 0000000..b4b3854
--- /dev/null
+++ b/media/aidl_api/android.media.soundtrigger.types/1/android/media/soundtrigger/PhraseSoundModel.aidl
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.soundtrigger;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable PhraseSoundModel {
+  android.media.soundtrigger.SoundModel common;
+  android.media.soundtrigger.Phrase[] phrases;
+}
diff --git a/media/aidl_api/android.media.soundtrigger.types/1/android/media/soundtrigger/Properties.aidl b/media/aidl_api/android.media.soundtrigger.types/1/android/media/soundtrigger/Properties.aidl
new file mode 100644
index 0000000..068db52
--- /dev/null
+++ b/media/aidl_api/android.media.soundtrigger.types/1/android/media/soundtrigger/Properties.aidl
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.soundtrigger;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable Properties {
+  String implementor;
+  String description;
+  int version;
+  String uuid;
+  String supportedModelArch;
+  int maxSoundModels;
+  int maxKeyPhrases;
+  int maxUsers;
+  int recognitionModes;
+  boolean captureTransition;
+  int maxBufferMs;
+  boolean concurrentCapture;
+  boolean triggerInEvent;
+  int powerConsumptionMw;
+  int audioCapabilities;
+}
diff --git a/media/aidl_api/android.media.soundtrigger.types/1/android/media/soundtrigger/RecognitionConfig.aidl b/media/aidl_api/android.media.soundtrigger.types/1/android/media/soundtrigger/RecognitionConfig.aidl
new file mode 100644
index 0000000..63cd2cbb
--- /dev/null
+++ b/media/aidl_api/android.media.soundtrigger.types/1/android/media/soundtrigger/RecognitionConfig.aidl
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.soundtrigger;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable RecognitionConfig {
+  boolean captureRequested;
+  android.media.soundtrigger.PhraseRecognitionExtra[] phraseRecognitionExtras;
+  int audioCapabilities;
+  byte[] data;
+}
diff --git a/media/aidl_api/android.media.soundtrigger.types/1/android/media/soundtrigger/RecognitionEvent.aidl b/media/aidl_api/android.media.soundtrigger.types/1/android/media/soundtrigger/RecognitionEvent.aidl
new file mode 100644
index 0000000..0209602
--- /dev/null
+++ b/media/aidl_api/android.media.soundtrigger.types/1/android/media/soundtrigger/RecognitionEvent.aidl
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.soundtrigger;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable RecognitionEvent {
+  android.media.soundtrigger.RecognitionStatus status = android.media.soundtrigger.RecognitionStatus.INVALID;
+  android.media.soundtrigger.SoundModelType type = android.media.soundtrigger.SoundModelType.INVALID;
+  boolean captureAvailable;
+  int captureDelayMs;
+  int capturePreambleMs;
+  boolean triggerInData;
+  @nullable android.media.audio.common.AudioConfig audioConfig;
+  byte[] data;
+  boolean recognitionStillActive;
+}
diff --git a/media/aidl_api/android.media.soundtrigger.types/1/android/media/soundtrigger/RecognitionMode.aidl b/media/aidl_api/android.media.soundtrigger.types/1/android/media/soundtrigger/RecognitionMode.aidl
new file mode 100644
index 0000000..5882910
--- /dev/null
+++ b/media/aidl_api/android.media.soundtrigger.types/1/android/media/soundtrigger/RecognitionMode.aidl
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.soundtrigger;
+/* @hide */
+@Backing(type="int") @VintfStability
+enum RecognitionMode {
+  VOICE_TRIGGER = 1,
+  USER_IDENTIFICATION = 2,
+  USER_AUTHENTICATION = 4,
+  GENERIC_TRIGGER = 8,
+}
diff --git a/media/aidl_api/android.media.soundtrigger.types/1/android/media/soundtrigger/RecognitionStatus.aidl b/media/aidl_api/android.media.soundtrigger.types/1/android/media/soundtrigger/RecognitionStatus.aidl
new file mode 100644
index 0000000..7881c28
--- /dev/null
+++ b/media/aidl_api/android.media.soundtrigger.types/1/android/media/soundtrigger/RecognitionStatus.aidl
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.soundtrigger;
+/* @hide */
+@Backing(type="int") @VintfStability
+enum RecognitionStatus {
+  INVALID = -1,
+  SUCCESS = 0,
+  ABORTED = 1,
+  FAILURE = 2,
+  FORCED = 3,
+}
diff --git a/media/aidl_api/android.media.soundtrigger.types/1/android/media/soundtrigger/SoundModel.aidl b/media/aidl_api/android.media.soundtrigger.types/1/android/media/soundtrigger/SoundModel.aidl
new file mode 100644
index 0000000..fe38264
--- /dev/null
+++ b/media/aidl_api/android.media.soundtrigger.types/1/android/media/soundtrigger/SoundModel.aidl
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.soundtrigger;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable SoundModel {
+  android.media.soundtrigger.SoundModelType type = android.media.soundtrigger.SoundModelType.INVALID;
+  String uuid;
+  String vendorUuid;
+  @nullable ParcelFileDescriptor data;
+  int dataSize;
+}
diff --git a/media/aidl_api/android.media.soundtrigger.types/1/android/media/soundtrigger/SoundModelType.aidl b/media/aidl_api/android.media.soundtrigger.types/1/android/media/soundtrigger/SoundModelType.aidl
new file mode 100644
index 0000000..ac78641
--- /dev/null
+++ b/media/aidl_api/android.media.soundtrigger.types/1/android/media/soundtrigger/SoundModelType.aidl
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.soundtrigger;
+/* @hide */
+@Backing(type="int") @VintfStability
+enum SoundModelType {
+  INVALID = -1,
+  KEYPHRASE = 0,
+  GENERIC = 1,
+}
diff --git a/media/aidl_api/android.media.soundtrigger.types/1/android/media/soundtrigger/Status.aidl b/media/aidl_api/android.media.soundtrigger.types/1/android/media/soundtrigger/Status.aidl
new file mode 100644
index 0000000..29f3167
--- /dev/null
+++ b/media/aidl_api/android.media.soundtrigger.types/1/android/media/soundtrigger/Status.aidl
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.soundtrigger;
+/* @hide */
+@Backing(type="int") @VintfStability
+enum Status {
+  INVALID = -1,
+  SUCCESS = 0,
+  RESOURCE_CONTENTION = 1,
+  OPERATION_NOT_SUPPORTED = 2,
+  TEMPORARY_PERMISSION_DENIED = 3,
+  DEAD_OBJECT = 4,
+  INTERNAL_ERROR = 5,
+}
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 491a5cd..e7eda3e 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -5898,14 +5898,8 @@
      */
     @UnsupportedAppUsage
     @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
-    public void setWiredDeviceConnectionState(int device, int state, String address,
-            String name) {
-        final IAudioService service = getService();
-        int role = isOutputDevice(device)
-                ? AudioDeviceAttributes.ROLE_OUTPUT : AudioDeviceAttributes.ROLE_INPUT;
-        AudioDeviceAttributes attributes = new AudioDeviceAttributes(
-                role, AudioDeviceInfo.convertInternalDeviceToDeviceType(device), address,
-                name, new ArrayList<>()/*mAudioProfiles*/, new ArrayList<>()/*mAudioDescriptors*/);
+    public void setWiredDeviceConnectionState(int device, int state, String address, String name) {
+        AudioDeviceAttributes attributes = new AudioDeviceAttributes(device, address, name);
         setWiredDeviceConnectionState(attributes, state);
     }
 
diff --git a/media/tests/aidltests/Android.bp b/media/tests/aidltests/Android.bp
index c3d5fa2..7a25b6d 100644
--- a/media/tests/aidltests/Android.bp
+++ b/media/tests/aidltests/Android.bp
@@ -33,7 +33,6 @@
     libs: [
         "framework",
     ],
-    sdk_version: "current",
     platform_apis: true,
     test_suites: ["device-tests"],
     certificate: "platform",
diff --git a/mms/OWNERS b/mms/OWNERS
index 2e419c1..7daef4e 100644
--- a/mms/OWNERS
+++ b/mms/OWNERS
@@ -1,18 +1,4 @@
 set noparent
 
-tgunn@google.com
-breadley@google.com
-rgreenwalt@google.com
-amitmahajan@google.com
-fionaxu@google.com
-jackyu@google.com
-jminjie@google.com
-satk@google.com
-shuoq@google.com
-sarahchin@google.com
-xiaotonj@google.com
-huiwang@google.com
-jayachandranc@google.com
-chinmayd@google.com
-amruthr@google.com
-sasindran@google.com
+file:platform/frameworks/base:/telephony/OWNERS
+
diff --git a/packages/CarrierDefaultApp/OWNERS b/packages/CarrierDefaultApp/OWNERS
index a2352e2..447cd51 100644
--- a/packages/CarrierDefaultApp/OWNERS
+++ b/packages/CarrierDefaultApp/OWNERS
@@ -1,18 +1,3 @@
 set noparent
-tgunn@google.com
-breadley@google.com
-rgreenwalt@google.com
-amitmahajan@google.com
-fionaxu@google.com
-jackyu@google.com
-jminjie@google.com
-satk@google.com
-shuoq@google.com
-sarahchin@google.com
-xiaotonj@google.com
-huiwang@google.com
-jayachandranc@google.com
-chinmayd@google.com
-amruthr@google.com
-sasindran@google.com
 
+file:platform/frameworks/base:/telephony/OWNERS
diff --git a/packages/SettingsLib/AndroidManifest.xml b/packages/SettingsLib/AndroidManifest.xml
index 13f8a37..3e67abf 100644
--- a/packages/SettingsLib/AndroidManifest.xml
+++ b/packages/SettingsLib/AndroidManifest.xml
@@ -22,6 +22,16 @@
         <activity
             android:name="com.android.settingslib.users.AvatarPickerActivity"
             android:theme="@style/SudThemeGlifV2.DayNight"/>
+
+        <activity
+            android:name="com.android.settingslib.qrcode.QrCodeScanModeActivity"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.settings.BLUETOOTH_LE_AUDIO_QR_CODE_SCANNER"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
+
     </application>
 
 </manifest>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index 3debd9a..7fbd100 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -47,7 +47,7 @@
     CsipDeviceManager mCsipDeviceManager;
     BluetoothDevice mOngoingSetMemberPair;
 
-    CachedBluetoothDeviceManager(Context context, LocalBluetoothManager localBtManager) {
+    public CachedBluetoothDeviceManager(Context context, LocalBluetoothManager localBtManager) {
         mContext = context;
         mBtManager = localBtManager;
         mHearingAidDeviceManager = new HearingAidDeviceManager(localBtManager, mCachedDevices);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index edcb576..0c652f6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -66,10 +66,6 @@
     private final ServiceListener mServiceListener = new ServiceListener() {
         @Override
         public void onServiceConnected(int profile, BluetoothProfile proxy) {
-            if (profile != BluetoothProfile.LE_AUDIO_BROADCAST) {
-                Log.d(TAG, "The profile is not LE_AUDIO_BROADCAST");
-                return;
-            }
             if (DEBUG) {
                 Log.d(TAG, "Bluetooth service connected");
             }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
index 04c6ee2..c248fff 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
@@ -65,10 +65,6 @@
     private final ServiceListener mServiceListener = new ServiceListener() {
         @Override
         public void onServiceConnected(int profile, BluetoothProfile proxy) {
-            if (profile != BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT) {
-                Log.d(TAG, "The profile is not LE_AUDIO_BROADCAST_ASSISTANT");
-                return;
-            }
             if (DEBUG) {
                 Log.d(TAG, "Bluetooth service connected");
             }
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index c2e36b7..e1a2e8d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -28,6 +28,7 @@
 import android.util.Log;
 
 import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 
@@ -239,13 +240,24 @@
 
     /**
      * Dispatch a change in the about-to-connect device. See
-     * {@link DeviceCallback#onAboutToConnectDeviceChanged} for more information.
+     * {@link DeviceCallback#onAboutToConnectDeviceAdded} for more information.
      */
-    public void dispatchAboutToConnectDeviceChanged(
-            @Nullable String deviceName,
+    public void dispatchAboutToConnectDeviceAdded(
+            @NonNull String deviceAddress,
+            @NonNull String deviceName,
             @Nullable Drawable deviceIcon) {
         for (DeviceCallback callback : getCallbacks()) {
-            callback.onAboutToConnectDeviceChanged(deviceName, deviceIcon);
+            callback.onAboutToConnectDeviceAdded(deviceAddress, deviceName, deviceIcon);
+        }
+    }
+
+    /**
+     * Dispatch a change in the about-to-connect device. See
+     * {@link DeviceCallback#onAboutToConnectDeviceRemoved} for more information.
+     */
+    public void dispatchAboutToConnectDeviceRemoved() {
+        for (DeviceCallback callback : getCallbacks()) {
+            callback.onAboutToConnectDeviceRemoved();
         }
     }
 
@@ -705,13 +717,27 @@
          * connect imminently and should be displayed as the current device in the media player.
          * See [AudioManager.muteAwaitConnection] for more details.
          *
-         * @param deviceName the name of the device (displayed to the user).
-         * @param deviceIcon the icon that should be used with the device.
+         * The information in the most recent callback should override information from any previous
+         * callbacks.
+         *
+         * @param deviceAddress the address of the device. {@see AudioDeviceAttributes.address}.
+         *                      If present, we'll use this address to fetch the full information
+         *                      about the device (if we can find that information).
+         * @param deviceName the name of the device (displayed to the user). Used as a backup in
+         *                   case using deviceAddress doesn't work.
+         * @param deviceIcon the icon that should be used with the device. Used as a backup in case
+         *                   using deviceAddress doesn't work.
          */
-        default void onAboutToConnectDeviceChanged(
-                @Nullable String deviceName,
+        default void onAboutToConnectDeviceAdded(
+                @NonNull String deviceAddress,
+                @NonNull String deviceName,
                 @Nullable Drawable deviceIcon
         ) {}
+
+        /**
+         * Callback for notifying that we no longer have an about-to-connect device.
+         */
+        default void onAboutToConnectDeviceRemoved() {}
     }
 
     /**
diff --git a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeScanModeActivity.java b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeScanModeActivity.java
new file mode 100644
index 0000000..9021fcb
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeScanModeActivity.java
@@ -0,0 +1,109 @@
+/*
+ * 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.qrcode;
+
+import static com.android.settingslib.bluetooth.BluetoothBroadcastUtils.EXTRA_BLUETOOTH_DEVICE_SINK;
+import static com.android.settingslib.bluetooth.BluetoothBroadcastUtils.EXTRA_BLUETOOTH_SINK_IS_GROUP;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+import androidx.fragment.app.FragmentTransaction;
+
+import com.android.settingslib.bluetooth.BluetoothBroadcastUtils;
+import com.android.settingslib.R;
+import com.android.settingslib.bluetooth.BluetoothUtils;
+
+public class QrCodeScanModeActivity extends QrCodeScanModeBaseActivity {
+    private static final boolean DEBUG = BluetoothUtils.D;
+    private static final String TAG = "QrCodeScanModeActivity";
+
+    private boolean mIsGroupOp;
+    private BluetoothDevice mSink;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    protected void handleIntent(Intent intent) {
+        String action = intent != null ? intent.getAction() : null;
+        if (DEBUG) {
+            Log.d(TAG, "handleIntent(), action = " + action);
+        }
+
+        if (action == null) {
+            finish();
+            return;
+        }
+
+        switch (action) {
+            case BluetoothBroadcastUtils.ACTION_BLUETOOTH_LE_AUDIO_QR_CODE_SCANNER:
+                showQrCodeScannerFragment(intent);
+                break;
+            default:
+                if (DEBUG) {
+                    Log.e(TAG, "Launch with an invalid action");
+                }
+                finish();
+        }
+    }
+
+    protected void showQrCodeScannerFragment(Intent intent) {
+        if (DEBUG) {
+            Log.d(TAG, "showQrCodeScannerFragment");
+        }
+
+        if (intent != null) {
+            mSink = intent.getParcelableExtra(EXTRA_BLUETOOTH_DEVICE_SINK);
+            mIsGroupOp = intent.getBooleanExtra(EXTRA_BLUETOOTH_SINK_IS_GROUP, false);
+            if (DEBUG) {
+                Log.d(TAG, "get extra from intent");
+            }
+        } else {
+            if (DEBUG) {
+                Log.d(TAG, "intent is null, can not get bluetooth information from intent.");
+            }
+        }
+
+        QrCodeScanModeFragment fragment =
+                (QrCodeScanModeFragment) mFragmentManager.findFragmentByTag(
+                        BluetoothBroadcastUtils.TAG_FRAGMENT_QR_CODE_SCANNER);
+
+        if (fragment == null) {
+            fragment = new QrCodeScanModeFragment(mIsGroupOp, mSink);
+        } else {
+            if (fragment.isVisible()) {
+                return;
+            }
+
+            // When the fragment in back stack but not on top of the stack, we can simply pop
+            // stack because current fragment transactions are arranged in an order
+            mFragmentManager.popBackStackImmediate();
+            return;
+        }
+        final FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
+
+        fragmentTransaction.replace(R.id.fragment_container, fragment,
+                BluetoothBroadcastUtils.TAG_FRAGMENT_QR_CODE_SCANNER);
+        fragmentTransaction.commit();
+    }
+}
+
diff --git a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeScanModeBaseActivity.java b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeScanModeBaseActivity.java
new file mode 100644
index 0000000..9aaec41
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeScanModeBaseActivity.java
@@ -0,0 +1,46 @@
+/*
+ * 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.qrcode;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+import androidx.fragment.app.FragmentManager;
+
+import com.android.settingslib.core.lifecycle.ObservableActivity;
+import com.android.settingslib.R;
+
+public abstract class QrCodeScanModeBaseActivity extends ObservableActivity {
+
+    protected FragmentManager mFragmentManager;
+
+    protected abstract void handleIntent(Intent intent);
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setTheme(R.style.SudThemeGlifV3_DayNight);
+
+        setContentView(R.layout.qrcode_scan_mode_activity);
+        mFragmentManager = getSupportFragmentManager();
+
+        if (savedInstanceState == null) {
+            handleIntent(getIntent());
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeScanModeController.java b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeScanModeController.java
new file mode 100644
index 0000000..d7640bb
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeScanModeController.java
@@ -0,0 +1,82 @@
+/*
+ * 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.qrcode;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeBroadcastMetadata;
+import android.content.Context;
+import android.util.Log;
+
+import com.android.settingslib.bluetooth.BluetoothUtils;
+import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastMetadata;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+
+public class QrCodeScanModeController {
+
+    private static final boolean DEBUG = BluetoothUtils.D;
+    private static final String TAG = "QrCodeScanModeController";
+
+    private LocalBluetoothLeBroadcastMetadata mLocalBroadcastMetadata;
+    private LocalBluetoothLeBroadcastAssistant mLocalBroadcastAssistant;
+    private LocalBluetoothManager mLocalBluetoothManager;
+    private LocalBluetoothProfileManager mProfileManager;
+
+    private LocalBluetoothManager.BluetoothManagerCallback
+            mOnInitCallback = new LocalBluetoothManager.BluetoothManagerCallback() {
+        @Override
+        public void onBluetoothManagerInitialized(Context appContext,
+                LocalBluetoothManager bluetoothManager) {
+            BluetoothUtils.setErrorListener(mErrorListener);
+        }
+    };
+
+    private BluetoothUtils.ErrorListener
+            mErrorListener = new BluetoothUtils.ErrorListener() {
+        @Override
+        public void onShowError(Context context, String name, int messageResId) {
+            if (DEBUG) {
+                Log.d(TAG, "Get error when initializing BluetoothManager. ");
+            }
+        }
+    };
+
+    public QrCodeScanModeController(Context context) {
+        if (DEBUG) {
+            Log.d(TAG, "QrCodeScanModeController constructor.");
+        }
+        mLocalBluetoothManager = LocalBluetoothManager.getInstance(context, mOnInitCallback);
+        mProfileManager = mLocalBluetoothManager.getProfileManager();
+        mLocalBroadcastMetadata = new LocalBluetoothLeBroadcastMetadata();
+        CachedBluetoothDeviceManager cachedDeviceManager = new CachedBluetoothDeviceManager(context,
+                mLocalBluetoothManager);
+        mLocalBroadcastAssistant = new LocalBluetoothLeBroadcastAssistant(context,
+                cachedDeviceManager, mProfileManager);
+    }
+
+    private BluetoothLeBroadcastMetadata convertToBroadcastMetadata(String qrCodeString) {
+        return mLocalBroadcastMetadata.convertToBroadcastMetadata(qrCodeString);
+    }
+
+    public void addSource(BluetoothDevice sink, String sourceMetadata,
+            boolean isGroupOp) {
+        mLocalBroadcastAssistant.addSource(sink,
+                convertToBroadcastMetadata(sourceMetadata), isGroupOp);
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeScanModeFragment.java b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeScanModeFragment.java
new file mode 100644
index 0000000..81165aa
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeScanModeFragment.java
@@ -0,0 +1,235 @@
+/*
+ * 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.qrcode;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.Outline;
+import android.graphics.Rect;
+import android.graphics.SurfaceTexture;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+import android.util.Size;
+import android.view.LayoutInflater;
+import android.view.TextureView;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.TextView;
+
+import com.android.settingslib.bluetooth.BluetoothBroadcastUtils;
+import com.android.settingslib.bluetooth.BluetoothUtils;
+import com.android.settingslib.R;
+import com.android.settingslib.core.lifecycle.ObservableFragment;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.StringRes;
+
+public class QrCodeScanModeFragment extends ObservableFragment implements
+        TextureView.SurfaceTextureListener,
+        QrCamera.ScannerCallback {
+    private static final boolean DEBUG = BluetoothUtils.D;
+    private static final String TAG = "QrCodeScanModeFragment";
+
+    /** Message sent to hide error message */
+    private static final int MESSAGE_HIDE_ERROR_MESSAGE = 1;
+    /** Message sent to show error message */
+    private static final int MESSAGE_SHOW_ERROR_MESSAGE = 2;
+    /** Message sent to broadcast QR code */
+    private static final int MESSAGE_SCAN_BROADCAST_SUCCESS = 3;
+
+    private static final long SHOW_ERROR_MESSAGE_INTERVAL = 10000;
+    private static final long SHOW_SUCCESS_SQUARE_INTERVAL = 1000;
+
+    private boolean mIsGroupOp;
+    private int mCornerRadius;
+    private BluetoothDevice mSink;
+    private String mBroadcastMetadata;
+    private Context mContext;
+    private QrCamera mCamera;
+    private QrCodeScanModeController mController;
+    private TextureView mTextureView;
+    private TextView mSummary;
+    private TextView mErrorMessage;
+
+    public QrCodeScanModeFragment(boolean isGroupOp, BluetoothDevice sink) {
+        mIsGroupOp = isGroupOp;
+        mSink = sink;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mContext = getContext();
+        mController = new QrCodeScanModeController(mContext);
+    }
+
+    @Override
+    public final View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        return inflater.inflate(R.layout.qrcode_scanner_fragment, container,
+                /* attachToRoot */ false);
+    }
+
+    @Override
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        mTextureView = view.findViewById(R.id.preview_view);
+        mCornerRadius = mContext.getResources().getDimensionPixelSize(
+                R.dimen.qrcode_preview_radius);
+        mTextureView.setSurfaceTextureListener(this);
+        mTextureView.setOutlineProvider(new ViewOutlineProvider() {
+            @Override
+            public void getOutline(View view, Outline outline) {
+                outline.setRoundRect(0,0, view.getWidth(), view.getHeight(), mCornerRadius);
+            }
+        });
+        mTextureView.setClipToOutline(true);
+        mErrorMessage = view.findViewById(R.id.error_message);
+    }
+
+    private void initCamera(SurfaceTexture surface) {
+        // Check if the camera has already created.
+        if (mCamera == null) {
+            mCamera = new QrCamera(mContext, this);
+            mCamera.start(surface);
+        }
+    }
+
+    private void destroyCamera() {
+        if (mCamera != null) {
+            mCamera.stop();
+            mCamera = null;
+        }
+    }
+
+    @Override
+    public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) {
+        initCamera(surface);
+    }
+
+    @Override
+    public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width,
+            int height) {
+    }
+
+    @Override
+    public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {
+        destroyCamera();
+        return true;
+    }
+
+    @Override
+    public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {
+    }
+
+    @Override
+    public void handleSuccessfulResult(String qrCode) {
+        if (DEBUG) {
+            Log.d(TAG, "handleSuccessfulResult(), get the qr code string.");
+        }
+        mBroadcastMetadata = qrCode;
+        handleBtLeAudioScanner();
+    }
+
+    @Override
+    public void handleCameraFailure() {
+        destroyCamera();
+    }
+
+    @Override
+    public Size getViewSize() {
+        return new Size(mTextureView.getWidth(), mTextureView.getHeight());
+    }
+
+    @Override
+    public Rect getFramePosition(Size previewSize, int cameraOrientation) {
+        return new Rect(0, 0, previewSize.getHeight(), previewSize.getHeight());
+    }
+
+    @Override
+    public void setTransform(Matrix transform) {
+        mTextureView.setTransform(transform);
+    }
+
+    @Override
+    public boolean isValid(String qrCode) {
+        if (qrCode.startsWith(BluetoothBroadcastUtils.SCHEME_BT_BROADCAST_METADATA)) {
+            return true;
+        } else {
+            showErrorMessage(R.string.bt_le_audio_qr_code_is_not_valid_format);
+            return false;
+        }
+    }
+
+    protected boolean isDecodeTaskAlive() {
+        return mCamera != null && mCamera.isDecodeTaskAlive();
+    }
+
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MESSAGE_HIDE_ERROR_MESSAGE:
+                    mErrorMessage.setVisibility(View.INVISIBLE);
+                    break;
+
+                case MESSAGE_SHOW_ERROR_MESSAGE:
+                    final String errorMessage = (String) msg.obj;
+
+                    mErrorMessage.setVisibility(View.VISIBLE);
+                    mErrorMessage.setText(errorMessage);
+                    mErrorMessage.sendAccessibilityEvent(
+                            AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+
+                    // Cancel any pending messages to hide error view and requeue the message so
+                    // user has time to see error
+                    removeMessages(MESSAGE_HIDE_ERROR_MESSAGE);
+                    sendEmptyMessageDelayed(MESSAGE_HIDE_ERROR_MESSAGE,
+                            SHOW_ERROR_MESSAGE_INTERVAL);
+                    break;
+
+                case MESSAGE_SCAN_BROADCAST_SUCCESS:
+                    mController.addSource(mSink, mBroadcastMetadata, mIsGroupOp);
+                    updateSummary();
+                    mSummary.sendAccessibilityEvent(
+                            AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+                    break;
+                default:
+            }
+        }
+    };
+
+    private void showErrorMessage(@StringRes int messageResId) {
+        final Message message = mHandler.obtainMessage(MESSAGE_SHOW_ERROR_MESSAGE,
+                getString(messageResId));
+        message.sendToTarget();
+    }
+
+    private void handleBtLeAudioScanner() {
+        Message message = mHandler.obtainMessage(MESSAGE_SCAN_BROADCAST_SUCCESS);
+        mHandler.sendMessageDelayed(message, SHOW_SUCCESS_SQUARE_INTERVAL);
+    }
+
+    private void updateSummary() {
+        mSummary.setText(getString(R.string.bt_le_audio_scan_qr_code_scanner,
+                null /* broadcast_name*/));;
+    }
+}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index a10ca9e..a28c4cf 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -575,6 +575,9 @@
     <uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" />
     <uses-permission android:name="android.permission.TRUST_LISTENER" />
 
+    <!-- Permission required for CTS test - CtsTaskFpsCallbackTestCases -->
+    <uses-permission android:name="android.permission.ACCESS_FPS_COUNTER" />
+
     <!-- Permission required for CTS test - CtsGameManagerTestCases -->
     <uses-permission android:name="android.permission.MANAGE_GAME_MODE" />
 
diff --git a/packages/SystemUI/docs/keyguard/bouncer.md b/packages/SystemUI/docs/device-entry/bouncer.md
similarity index 97%
rename from packages/SystemUI/docs/keyguard/bouncer.md
rename to packages/SystemUI/docs/device-entry/bouncer.md
index 4bfe734..589cb5d 100644
--- a/packages/SystemUI/docs/keyguard/bouncer.md
+++ b/packages/SystemUI/docs/device-entry/bouncer.md
@@ -2,6 +2,8 @@
 
 [KeyguardBouncer][1] is the component responsible for displaying the security method set by the user (password, PIN, pattern) as well as SIM-related security methods, allowing the user to unlock the device or SIM.
 
+![ss-bouncer](./imgs/bouncer_pin.png)
+
 ## Supported States
 
 1. Phone, portrait mode - The default and typically only way to view the bouncer. Screen cannot rotate.
diff --git a/packages/SystemUI/docs/keyguard/doze.md b/packages/SystemUI/docs/device-entry/doze.md
similarity index 99%
rename from packages/SystemUI/docs/keyguard/doze.md
rename to packages/SystemUI/docs/device-entry/doze.md
index a6ccab9..5ff8851 100644
--- a/packages/SystemUI/docs/keyguard/doze.md
+++ b/packages/SystemUI/docs/device-entry/doze.md
@@ -2,6 +2,8 @@
 
 Always-on Display (AOD) provides an alternative 'screen-off' experience. Instead, of completely turning the display off, it provides a distraction-free, glanceable experience for the phone in a low-powered mode. In this low-powered mode, the display will have a lower refresh rate and the UI should frequently shift its displayed contents in order to prevent burn-in. The recommended max on-pixel-ratio (OPR) is 5% to reduce battery consumption.
 
+![ss-aod](./imgs/aod.png)
+
 The default doze component controls AOD and is specified by `config_dozeComponent` in the [framework config][1]. SystemUI provides a default Doze Component: [DozeService][2]. [DozeService][2] builds a [DozeMachine][3] with dependencies specified in [DozeModule][4] and configurations in [AmbientDisplayConfiguration][13] and [DozeParameters][14].
 
 Note: The default UI used in AOD shares views with the Lock Screen and does not create its own new views. Once dozing begins, [DozeUI][17] informs SystemUI's [DozeServiceHost][18] that dozing has begun - which sends this signal to relevant SystemUI Lock Screen views to animate accordingly. Within SystemUI, [StatusBarStateController][19] #isDozing and #getDozeAmount can be used to query dozing state.
diff --git a/packages/SystemUI/docs/device-entry/glossary.md b/packages/SystemUI/docs/device-entry/glossary.md
new file mode 100644
index 0000000..f3d12c2
--- /dev/null
+++ b/packages/SystemUI/docs/device-entry/glossary.md
@@ -0,0 +1,48 @@
+# Device Entry Glossary
+
+## Keyguard
+
+| Term                         | Description |
+| :-----------:                | ----------- |
+| Keyguard, [keyguard.md][1]   | Coordinates the first experience when turning on the display of a device, as long as the user has not specified a security method of NONE. Consists of the lock screen and bouncer.|
+| Lock screen<br><br>![ss_aod](imgs/lockscreen.png)| The first screen available when turning on the display of a device, as long as the user has not specified a security method of NONE. On the lock screen, users can access:<ul><li>Quick Settings - users can swipe down from the top of the screen to interact with quick settings tiles</li><li>[Keyguard Status Bar][9] - This special status bar shows SIM related information and system icons.</li><li>Clock - uses the font specified at [clock.xml][8]. If the clock font supports variable weights, users will experience delightful clock weight animations - in particular, on transitions between the lock screen and AOD.</li><li>Notifications - ability to view and interact with notifications depending on user lock screen notification settings: `Settings > Display > Lock screen > Privacy`</li><li>Message area - contains device information like biometric errors, charging information and device policy information. Also includes user configured information from `Settings > Display > Lock screen > Add text on lock screen`. </li><li>Bouncer - if the user has a primary authentication method, they can swipe up from the bottom of the screen to bring up the bouncer.</li></ul>The lock screen is one state of the notification shade. See [StatusBarState#KEYGUARD][10] and [StatusBarState#SHADE_LOCKED][10].|
+| Bouncer, [bouncer.md][2]<br><br>![ss_aod](imgs/bouncer_pin.png)| The component responsible for displaying the primary security method set by the user (password, PIN, pattern).  The bouncer can also show SIM-related security methods, allowing the user to unlock the device or SIM.|
+| Split shade                  | State of the shade (which keyguard is a part of) in which notifications are on the right side and Quick Settings on the left. For keyguard that means notifications being on the right side and clock with media being on the left.<br><br>Split shade is automatically activated - using resources - for big screens in landscape, see [sw600dp-land/config.xml][3] `config_use_split_notification_shade`.<br><br>In that state we can see the big clock more often - every time when media is not visible on the lock screen. When there is no media and no notifications - or we enter AOD - big clock is always positioned in the center of the screen.<br><br>The magic of positioning views happens by changing constraints of [NotificationsQuickSettingsContainer][4] and positioning elements vertically in [KeyguardClockPositionAlgorithm][5]|
+| Ambient display (AOD), [doze.md][6]<br><br>![ss_aod](imgs/aod.png)| UI shown when the device is in a low-powered display state. This is controlled by the doze component. The same lock screen views (ie: clock, notification shade) are used on AOD. The AOSP image on the left shows the usage of a clock that does not support variable weights which is why the clock is thicker in that image than what users see on Pixel devices.|
+
+## General Authentication Terms
+| Term                     | Description |
+| -----------              | ----------- |
+| Primary Authentication   | The strongest form of authentication. Includes: Pin, pattern and password input.|
+| Biometric Authentication | Face or fingerprint input. Biometric authentication is categorized into different classes of security. See [Measuring Biometric Security][7].|
+
+## Face Authentication Terms
+| Term            | Description |
+| -----------     | ----------- |
+| Passive Authentication   | When a user hasn't explicitly requested an authentication method; however, it may still put the device in an unlocked state.<br><br>For example, face authentication is triggered immediately when waking the device; however, users may not have the intent of unlocking their device. Instead, they could have wanted to just check the lock screen. Because of this, SystemUI provides the option for a bypass OR non-bypass face authentication experience which have different user flows.<br><br>In contrast, fingerprint authentication is considered an active authentication method since users need to actively put their finger on the fingerprint sensor to authenticate. Therefore, it's an explicit request for authentication and SystemUI knows the user has the intent for device-entry.|
+| Bypass                   | Used to refer to the face authentication bypass device entry experience. We have this distinction because face auth is a passive authentication method (see above).|
+| Bypass User Journey <br><br>![ss_bypass](imgs/bypass.png)| Once the user successfully authenticates with face, the keyguard immediately dismisses and the user is brought to the home screen/last app.  This CUJ prioritizes speed of device entry. SystemUI hides interactive views (notifications) on the lock screen to avoid putting users in a state where the lock screen could immediately disappear while they're interacting with affordances on the lock screen.|
+| Non-bypass User Journey  | Once the user successfully authenticates with face, the device remains on keyguard until the user performs an action to indicate they'd like to enter the device (ie: swipe up on the lock screen or long press on the unlocked icon). This CUJ prioritizes notification visibility.|
+
+## Fingerprint Authentication Terms
+| Term                                     | Description |
+| -----------                              | ----------- |
+| Under-display fingerprint sensor (UDFPS) | References the HW affordance for a fingerprint sensor that is under the display, which requires a software visual affordance. System UI supports showing the UDFPS affordance on the lock screen and on AOD. Users cannot authenticate from the screen-off state.<br><br>Supported SystemUI CUJs include:<ul><li> sliding finger on the screen to the UDFPS area to being authentication (as opposed to directly placing finger in the UDFPS area) </li><li> when a11y services are enabled, there is a haptic played when a touch is detected on UDFPS</li><li>after two hard-fingerprint-failures, the primary authentication bouncer is shown</li><li> when tapping on an affordance that requests to dismiss the lock screen, the user may see the UDFPS icon highlighted - see UDFPS bouncer</li></ul>|
+| UDFPS Bouncer                            | UI that highlights the UDFPS sensor. Users can get into this state after tapping on a notification from the lock screen or locked expanded shade.|
+
+## Other Authentication Terms
+| Term             | Description |
+| ----------      | ----------- |
+| Trust Agents    | Provides signals to the keyguard to allow it to lock less frequently.|
+
+
+[1]: /frameworks/base/packages/SystemUI/docs/device-entry/keyguard.md
+[2]: /frameworks/base/packages/SystemUI/docs/device-entry/bouncer.md
+[3]: /frameworks/base/packages/SystemUI/res/values-sw600dp-land/config.xml
+[4]: /frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
+[5]: /frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+[6]: /frameworks/base/packages/SystemUI/docs/device-entry/doze.md
+[7]: https://source.android.com/security/biometric/measure
+[8]: /frameworks/base/packages/SystemUI/res-keyguard/font/clock.xml
+[9]: /frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+[10]: /frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarState.java
diff --git a/packages/SystemUI/docs/device-entry/imgs/aod.png b/packages/SystemUI/docs/device-entry/imgs/aod.png
new file mode 100644
index 0000000..abd554a
--- /dev/null
+++ b/packages/SystemUI/docs/device-entry/imgs/aod.png
Binary files differ
diff --git a/packages/SystemUI/docs/device-entry/imgs/bouncer_pin.png b/packages/SystemUI/docs/device-entry/imgs/bouncer_pin.png
new file mode 100644
index 0000000..da15e41
--- /dev/null
+++ b/packages/SystemUI/docs/device-entry/imgs/bouncer_pin.png
Binary files differ
diff --git a/packages/SystemUI/docs/device-entry/imgs/bypass.png b/packages/SystemUI/docs/device-entry/imgs/bypass.png
new file mode 100644
index 0000000..f4cbd3e
--- /dev/null
+++ b/packages/SystemUI/docs/device-entry/imgs/bypass.png
Binary files differ
diff --git a/packages/SystemUI/docs/device-entry/imgs/lockscreen.png b/packages/SystemUI/docs/device-entry/imgs/lockscreen.png
new file mode 100644
index 0000000..d1fe085
--- /dev/null
+++ b/packages/SystemUI/docs/device-entry/imgs/lockscreen.png
Binary files differ
diff --git a/packages/SystemUI/docs/keyguard.md b/packages/SystemUI/docs/device-entry/keyguard.md
similarity index 74%
rename from packages/SystemUI/docs/keyguard.md
rename to packages/SystemUI/docs/device-entry/keyguard.md
index 8914042..337f73b 100644
--- a/packages/SystemUI/docs/keyguard.md
+++ b/packages/SystemUI/docs/device-entry/keyguard.md
@@ -30,6 +30,12 @@
 
 ### How the device locks
 
+## Debugging Tips
+Enable verbose keyguard logs that will print to logcat. Should only be used temporarily for debugging. See [KeyguardConstants][5].
+```
+adb shell setprop log.tag.Keyguard DEBUG && adb shell am crash com.android.systemui
+```
+
 More coming
 * Screen timeout
 * Smart lock
@@ -38,9 +44,8 @@
 * Lock timeout after screen timeout setting
 
 
-[1]: /frameworks/base/packages/SystemUI/docs/keyguard/bouncer.md
-[2]: /frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java
-[3]: /frameworks/base/packages/SystemUI/docs/keyguard/doze.md
-[4]: /frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
-
-
+[1]: /frameworks/base/packages/SystemUI/docs/device-entry/bouncer.md
+[2]: /com/android/server/power/PowerManagerService.java
+[3]: /frameworks/base/packages/SystemUI/docs/device-entry/doze.md
+[4]: /com/android/server/policy/PhoneWindowManager.java
+[5]: /frameworks/base/packages/SystemUI/src/com/android/keyguard/KeyguardConstants.java
diff --git a/packages/SystemUI/docs/user-switching.md b/packages/SystemUI/docs/user-switching.md
index dcf66b9..b9509eb 100644
--- a/packages/SystemUI/docs/user-switching.md
+++ b/packages/SystemUI/docs/user-switching.md
@@ -37,7 +37,7 @@
 
 Renders user switching as a dialog over the current surface, and supports add guest user/actions if configured.
 
-[1]: /frameworks/base/packages/SystemUI/docs/keyguard/bouncer.md
+[1]: /frameworks/base/packages/SystemUI/docs/device-entry/bouncer.md
 [2]: /frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserController.java
 [3]: /frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
 [4]: /frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
index 75d95e6..f78046d 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
@@ -15,7 +15,6 @@
 package com.android.systemui.plugins.qs;
 
 import android.view.View;
-import android.view.View.OnClickListener;
 
 import com.android.systemui.plugins.FragmentBase;
 import com.android.systemui.plugins.annotations.DependsOn;
@@ -34,7 +33,7 @@
 
     String ACTION = "com.android.systemui.action.PLUGIN_QS";
 
-    int VERSION = 13;
+    int VERSION = 14;
 
     String TAG = "QS";
 
@@ -68,7 +67,12 @@
     void setHeaderListening(boolean listening);
     void notifyCustomizeChanged();
     void setContainerController(QSContainerController controller);
-    void setExpandClickListener(OnClickListener onClickListener);
+
+    /**
+     * Provide an action to collapse if expanded or expand if collapsed.
+     * @param action
+     */
+    void setCollapseExpandAction(Runnable action);
 
     /**
      * Returns the height difference between the QSPanel container and the QuickQSPanel container
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java
index 9a9683d..a8999ff 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java
@@ -26,7 +26,7 @@
 @DependsOn(target = QSIconView.class)
 @DependsOn(target = QSTile.class)
 public abstract class QSTileView extends LinearLayout {
-    public static final int VERSION = 2;
+    public static final int VERSION = 3;
 
     public QSTileView(Context context) {
         super(context);
@@ -71,4 +71,7 @@
     public View getSecondaryLabel() {
         return null;
     }
+
+    /** Sets the index of this tile in its layout */
+    public abstract void setPosition(int position);
 }
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java
index 9829918..9ed3bac 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java
@@ -94,6 +94,12 @@
         }
 
         /**
+         * Callback to be notified about upcoming state changes. Typically, is immediately followed
+         * by #onStateChanged, unless there was an intentional delay in updating the state changed.
+         */
+        default void onUpcomingStateChanged(int upcomingState) {}
+
+        /**
          * Callback to be notified when Dozing changes. Dozing is stored separately from state.
          */
         default void onDozingChanged(boolean isDozing) {}
diff --git a/packages/SystemUI/res-keyguard/layout/fgs_footer.xml b/packages/SystemUI/res-keyguard/layout/fgs_footer.xml
index 9d801d2..9ffafbc 100644
--- a/packages/SystemUI/res-keyguard/layout/fgs_footer.xml
+++ b/packages/SystemUI/res-keyguard/layout/fgs_footer.xml
@@ -16,8 +16,9 @@
 -->
 <FrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
+    android:layout_width="0dp"
     android:layout_height="@dimen/qs_security_footer_single_line_height"
+    android:layout_weight="1"
     android:gravity="center"
     android:clickable="true"
     android:visibility="gone">
@@ -26,7 +27,7 @@
         android:id="@+id/fgs_text_container"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:layout_marginEnd="@dimen/new_qs_footer_action_inset"
+        android:layout_marginEnd="@dimen/qs_footer_action_inset"
         android:background="@drawable/qs_security_footer_background"
         android:layout_gravity="end"
         android:gravity="center"
@@ -86,7 +87,7 @@
             android:layout_height="12dp"
             android:scaleType="fitCenter"
             android:layout_gravity="bottom|end"
-            android:src="@drawable/new_fgs_dot"
+            android:src="@drawable/fgs_dot"
             android:contentDescription="@string/fgs_dot_content_description"
             />
     </FrameLayout>
diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions.xml b/packages/SystemUI/res-keyguard/layout/footer_actions.xml
index fb401ee..6a1d62d 100644
--- a/packages/SystemUI/res-keyguard/layout/footer_actions.xml
+++ b/packages/SystemUI/res-keyguard/layout/footer_actions.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-** Copyright 2021, The Android Open Source Project
+** Copyright 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.
@@ -18,65 +18,80 @@
 <!-- Action buttons for footer in QS/QQS, containing settings button, power off button etc -->
 <com.android.systemui.qs.FooterActionsView
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:layout_width="match_parent"
-    android:layout_height="@dimen/qs_footer_height"
+    android:layout_height="@dimen/footer_actions_height"
+    android:elevation="@dimen/qs_panel_elevation"
+    android:paddingTop="8dp"
+    android:paddingBottom="4dp"
+    android:background="@drawable/qs_footer_actions_background"
     android:gravity="center_vertical"
     android:layout_gravity="bottom"
 >
 
-    <com.android.systemui.statusbar.phone.MultiUserSwitch
-        android:id="@+id/multi_user_switch"
-        android:layout_width="0dp"
+    <LinearLayout
+        android:id="@+id/security_footers_container"
+        android:orientation="horizontal"
         android:layout_height="@dimen/qs_footer_action_button_size"
-        android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
-        android:layout_weight="1"
-        android:background="@drawable/qs_footer_action_chip_background"
-        android:focusable="true">
-
-        <ImageView
-            android:id="@+id/multi_user_avatar"
-            android:layout_width="@dimen/multi_user_avatar_expanded_size"
-            android:layout_height="@dimen/multi_user_avatar_expanded_size"
-            android:layout_gravity="center"
-            android:scaleType="centerInside" />
-    </com.android.systemui.statusbar.phone.MultiUserSwitch>
-
-    <com.android.systemui.statusbar.AlphaOptimizedImageView
-        android:id="@+id/pm_lite"
         android:layout_width="0dp"
-        android:layout_height="@dimen/qs_footer_action_button_size"
-        android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
         android:layout_weight="1"
-        android:background="@drawable/qs_footer_action_chip_background"
-        android:clickable="true"
-        android:clipToPadding="false"
-        android:focusable="true"
-        android:padding="@dimen/qs_footer_icon_padding"
-        android:src="@*android:drawable/ic_lock_power_off"
-        android:contentDescription="@string/accessibility_quick_settings_power_menu"
-        android:tint="?android:attr/textColorPrimary" />
+    />
 
-    <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
-        android:id="@+id/settings_button_container"
-        android:layout_width="0dp"
-        android:layout_height="@dimen/qs_footer_action_button_size"
-        android:background="@drawable/qs_footer_action_chip_background"
-        android:layout_weight="1"
-        android:clipChildren="false"
-        android:clipToPadding="false">
+    <!-- Negative margin equal to -->
+    <LinearLayout
+        android:layout_height="match_parent"
+        android:layout_width="wrap_content"
+        android:layout_marginEnd="@dimen/qs_footer_action_inset_negative"
+        >
 
-        <com.android.systemui.statusbar.phone.SettingsButton
-            android:id="@+id/settings_button"
-            android:layout_width="match_parent"
+        <com.android.systemui.statusbar.phone.MultiUserSwitch
+            android:id="@+id/multi_user_switch"
+            android:layout_width="@dimen/qs_footer_action_button_size"
             android:layout_height="@dimen/qs_footer_action_button_size"
-            android:layout_gravity="center"
-            android:contentDescription="@string/accessibility_quick_settings_settings"
-            android:background="@drawable/qs_footer_action_chip_background_borderless"
+            android:background="@drawable/qs_footer_action_circle"
+            android:focusable="true">
+
+            <ImageView
+                android:id="@+id/multi_user_avatar"
+                android:layout_width="@dimen/qs_footer_icon_size"
+                android:layout_height="@dimen/qs_footer_icon_size"
+                android:layout_gravity="center"
+                android:scaleType="centerInside" />
+        </com.android.systemui.statusbar.phone.MultiUserSwitch>
+
+        <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
+            android:id="@+id/settings_button_container"
+            android:layout_width="@dimen/qs_footer_action_button_size"
+            android:layout_height="@dimen/qs_footer_action_button_size"
+            android:background="@drawable/qs_footer_action_circle"
+            android:clipChildren="false"
+            android:clipToPadding="false">
+
+            <com.android.systemui.statusbar.phone.SettingsButton
+                android:id="@+id/settings_button"
+                android:layout_width="@dimen/qs_footer_icon_size"
+                android:layout_height="@dimen/qs_footer_icon_size"
+                android:layout_gravity="center"
+                android:background="@android:color/transparent"
+                android:contentDescription="@string/accessibility_quick_settings_settings"
+                android:scaleType="centerInside"
+                android:src="@drawable/ic_settings"
+                android:tint="?android:attr/textColorPrimary" />
+
+        </com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
+
+        <com.android.systemui.statusbar.AlphaOptimizedImageView
+            android:id="@+id/pm_lite"
+            android:layout_width="@dimen/qs_footer_action_button_size"
+            android:layout_height="@dimen/qs_footer_action_button_size"
+            android:background="@drawable/qs_footer_action_circle_color"
+            android:clickable="true"
+            android:clipToPadding="false"
+            android:focusable="true"
             android:padding="@dimen/qs_footer_icon_padding"
-            android:scaleType="centerInside"
-            android:src="@drawable/ic_settings"
-            android:tint="?android:attr/textColorPrimary" />
+            android:src="@*android:drawable/ic_lock_power_off"
+            android:contentDescription="@string/accessibility_quick_settings_power_menu"
+            android:tint="?androidprv:attr/textColorOnAccent" />
 
-    </com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
-
+    </LinearLayout>
 </com.android.systemui.qs.FooterActionsView>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml
index b765f49..dae2e56 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml
@@ -59,16 +59,17 @@
                 android:layout_height="wrap_content"
                 android:paddingBottom="4dp"
                 >
+
             <com.android.keyguard.PasswordTextView
-                    android:id="@+id/simPinEntry"
-                    android:layout_width="@dimen/keyguard_security_width"
-                    android:layout_height="@dimen/keyguard_password_height"
-                    android:gravity="center"
-                    android:layout_centerHorizontal="true"
-                    android:layout_marginRight="72dp"
-                    androidprv:scaledTextSize="@integer/scaled_password_text_size"
-                    android:contentDescription="@string/keyguard_accessibility_sim_pin_area"
-                    />
+                android:id="@+id/simPinEntry"
+                style="@style/Widget.TextView.Password"
+                android:layout_width="@dimen/keyguard_security_width"
+                android:layout_height="@dimen/keyguard_password_height"
+                android:layout_centerHorizontal="true"
+                android:layout_marginRight="72dp"
+                android:contentDescription="@string/keyguard_accessibility_sim_pin_area"
+                android:gravity="center"
+                androidprv:scaledTextSize="@integer/scaled_password_text_size" />
         </RelativeLayout>
         <LinearLayout
                 android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml
index 917ea6b..74f7820 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml
@@ -60,16 +60,17 @@
                 android:layout_height="wrap_content"
                 android:paddingBottom="4dp"
                 >
+
             <com.android.keyguard.PasswordTextView
-                    android:id="@+id/pukEntry"
-                    android:layout_width="@dimen/keyguard_security_width"
-                    android:layout_height="@dimen/keyguard_password_height"
-                    android:gravity="center"
-                    android:layout_centerHorizontal="true"
-                    android:layout_marginRight="72dp"
-                    androidprv:scaledTextSize="@integer/scaled_password_text_size"
-                    android:contentDescription="@string/keyguard_accessibility_sim_puk_area"
-                    />
+                android:id="@+id/pukEntry"
+                style="@style/Widget.TextView.Password"
+                android:layout_width="@dimen/keyguard_security_width"
+                android:layout_height="@dimen/keyguard_password_height"
+                android:layout_centerHorizontal="true"
+                android:layout_marginRight="72dp"
+                android:contentDescription="@string/keyguard_accessibility_sim_puk_area"
+                android:gravity="center"
+                androidprv:scaledTextSize="@integer/scaled_password_text_size" />
         </RelativeLayout>
         <LinearLayout
                 android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res-keyguard/layout/new_footer_actions.xml b/packages/SystemUI/res-keyguard/layout/new_footer_actions.xml
deleted file mode 100644
index 59712c0..0000000
--- a/packages/SystemUI/res-keyguard/layout/new_footer_actions.xml
+++ /dev/null
@@ -1,97 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-** Copyright 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.
--->
-
-<!-- Action buttons for footer in QS/QQS, containing settings button, power off button etc -->
-<com.android.systemui.qs.FooterActionsView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="@dimen/new_footer_height"
-    android:elevation="@dimen/qs_panel_elevation"
-    android:paddingTop="8dp"
-    android:paddingBottom="4dp"
-    android:background="@drawable/qs_footer_actions_background"
-    android:gravity="center_vertical"
-    android:layout_gravity="bottom"
->
-
-    <LinearLayout
-        android:id="@+id/security_footers_container"
-        android:orientation="horizontal"
-        android:layout_height="@dimen/qs_footer_action_button_size"
-        android:layout_width="0dp"
-        android:layout_weight="1"
-    />
-
-    <!-- Negative margin equal to -->
-    <LinearLayout
-        android:layout_height="match_parent"
-        android:layout_width="wrap_content"
-        android:layout_marginEnd="@dimen/new_qs_footer_action_inset_negative"
-        >
-
-        <com.android.systemui.statusbar.phone.MultiUserSwitch
-            android:id="@+id/multi_user_switch"
-            android:layout_width="@dimen/qs_footer_action_button_size"
-            android:layout_height="@dimen/qs_footer_action_button_size"
-            android:background="@drawable/qs_footer_action_circle"
-            android:focusable="true">
-
-            <ImageView
-                android:id="@+id/multi_user_avatar"
-                android:layout_width="@dimen/qs_footer_icon_size"
-                android:layout_height="@dimen/qs_footer_icon_size"
-                android:layout_gravity="center"
-                android:scaleType="centerInside" />
-        </com.android.systemui.statusbar.phone.MultiUserSwitch>
-
-        <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
-            android:id="@+id/settings_button_container"
-            android:layout_width="@dimen/qs_footer_action_button_size"
-            android:layout_height="@dimen/qs_footer_action_button_size"
-            android:background="@drawable/qs_footer_action_circle"
-            android:clipChildren="false"
-            android:clipToPadding="false">
-
-            <com.android.systemui.statusbar.phone.SettingsButton
-                android:id="@+id/settings_button"
-                android:layout_width="@dimen/qs_footer_icon_size"
-                android:layout_height="@dimen/qs_footer_icon_size"
-                android:layout_gravity="center"
-                android:background="@android:color/transparent"
-                android:contentDescription="@string/accessibility_quick_settings_settings"
-                android:scaleType="centerInside"
-                android:src="@drawable/ic_settings"
-                android:tint="?android:attr/textColorPrimary" />
-
-        </com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
-
-        <com.android.systemui.statusbar.AlphaOptimizedImageView
-            android:id="@+id/pm_lite"
-            android:layout_width="@dimen/qs_footer_action_button_size"
-            android:layout_height="@dimen/qs_footer_action_button_size"
-            android:background="@drawable/qs_footer_action_circle_color"
-            android:clickable="true"
-            android:clipToPadding="false"
-            android:focusable="true"
-            android:padding="@dimen/qs_footer_icon_padding"
-            android:src="@*android:drawable/ic_lock_power_off"
-            android:contentDescription="@string/accessibility_quick_settings_power_menu"
-            android:tint="?androidprv:attr/textColorOnAccent" />
-
-    </LinearLayout>
-</com.android.systemui.qs.FooterActionsView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/new_fgs_dot.xml b/packages/SystemUI/res/drawable/fgs_dot.xml
similarity index 100%
rename from packages/SystemUI/res/drawable/new_fgs_dot.xml
rename to packages/SystemUI/res/drawable/fgs_dot.xml
diff --git a/packages/SystemUI/res/drawable/ic_media_connecting_container.xml b/packages/SystemUI/res/drawable/ic_media_connecting_container.xml
new file mode 100644
index 0000000..79d2a06
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_media_connecting_container.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

+-->

+<vector xmlns:android="http://schemas.android.com/apk/res/android"

+        android:height="48dp"

+        android:width="48dp"

+        android:viewportHeight="48"

+        android:viewportWidth="48">

+    <group android:name="_R_G">

+        <group android:name="_R_G_L_1_G"

+                android:translateX="24"

+                android:translateY="24"

+                android:scaleX="0.5"

+                android:scaleY="0.5"/>

+        <group android:name="_R_G_L_0_G"

+                android:translateX="24"

+                android:translateY="24"

+                android:scaleX="0.5"

+                android:scaleY="0.5">

+            <path android:name="_R_G_L_0_G_D_0_P_0"

+                    android:fillColor="#ffddb3"

+                    android:fillAlpha="1"

+                    android:fillType="nonZero"

+                    android:pathData=" M48 -16 C48,-16 48,16 48,16 C48,33.67 33.67,48 16,48 C16,48 -16,48 -16,48 C-33.67,48 -48,33.67 -48,16 C-48,16 -48,-16 -48,-16 C-48,-33.67 -33.67,-48 -16,-48 C-16,-48 16,-48 16,-48 C33.67,-48 48,-33.67 48,-16c "/>

+        </group>

+    </group>

+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/qs_footer_action_chip_background.xml b/packages/SystemUI/res/drawable/qs_footer_action_chip_background.xml
deleted file mode 100644
index 9076da7..0000000
--- a/packages/SystemUI/res/drawable/qs_footer_action_chip_background.xml
+++ /dev/null
@@ -1,42 +0,0 @@
-<?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.
-  -->
-<inset xmlns:android="http://schemas.android.com/apk/res/android"
-    android:insetTop="@dimen/qs_footer_action_inset"
-    android:insetBottom="@dimen/qs_footer_action_inset">
-    <ripple
-        android:color="?android:attr/colorControlHighlight"
-        android:height="44dp">
-        <item android:id="@android:id/mask">
-            <shape android:shape="rectangle">
-                <solid android:color="@android:color/white"/>
-                <corners android:radius="@dimen/qs_footer_action_corner_radius"/>
-            </shape>
-        </item>
-        <item>
-            <shape android:shape="rectangle">
-                <solid android:color="?attr/underSurfaceColor"/>
-                <corners android:radius="@dimen/qs_footer_action_corner_radius"/>
-            </shape>
-        </item>
-        <item>
-            <shape android:shape="rectangle">
-                <stroke android:width="1dp" android:color="?android:attr/colorBackground"/>
-                <corners android:radius="@dimen/qs_footer_action_corner_radius"/>
-            </shape>
-        </item>
-    </ripple>
-</inset>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/qs_footer_action_chip_background_borderless.xml b/packages/SystemUI/res/drawable/qs_footer_action_chip_background_borderless.xml
deleted file mode 100644
index bbcfb15..0000000
--- a/packages/SystemUI/res/drawable/qs_footer_action_chip_background_borderless.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?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.
-  -->
-<inset xmlns:android="http://schemas.android.com/apk/res/android"
-    android:insetTop="@dimen/qs_footer_action_inset"
-    android:insetBottom="@dimen/qs_footer_action_inset">
-    <ripple
-        android:color="?android:attr/colorControlHighlight">
-        <item android:id="@android:id/mask">
-            <shape android:shape="rectangle">
-                <solid android:color="@android:color/white"/>
-                <corners android:radius="@dimen/qs_footer_action_corner_radius"/>
-            </shape>
-        </item>
-        <item>
-            <shape android:shape="rectangle">
-                <solid android:color="@android:color/transparent"/>
-                <corners android:radius="@dimen/qs_footer_action_corner_radius"/>
-            </shape>
-        </item>
-    </ripple>
-</inset>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/qs_footer_action_circle.xml b/packages/SystemUI/res/drawable/qs_footer_action_circle.xml
index 31a8c3b..c8c36b0 100644
--- a/packages/SystemUI/res/drawable/qs_footer_action_circle.xml
+++ b/packages/SystemUI/res/drawable/qs_footer_action_circle.xml
@@ -15,7 +15,7 @@
   ~ limitations under the License.
   -->
 <inset xmlns:android="http://schemas.android.com/apk/res/android"
-       android:inset="@dimen/new_qs_footer_action_inset">
+       android:inset="@dimen/qs_footer_action_inset">
     <ripple
         android:color="?android:attr/colorControlHighlight">
         <item android:id="@android:id/mask">
diff --git a/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml b/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml
index 021a85f..6a365000 100644
--- a/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml
+++ b/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml
@@ -15,7 +15,7 @@
   ~ limitations under the License.
   -->
 <inset xmlns:android="http://schemas.android.com/apk/res/android"
-    android:inset="@dimen/new_qs_footer_action_inset">
+    android:inset="@dimen/qs_footer_action_inset">
     <ripple
         android:color="?android:attr/colorControlHighlight">
         <item android:id="@android:id/mask">
diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml
index c0436b2..ccfd3a3 100644
--- a/packages/SystemUI/res/layout/clipboard_overlay.xml
+++ b/packages/SystemUI/res/layout/clipboard_overlay.xml
@@ -136,6 +136,7 @@
             android:layout_height="@dimen/overlay_dismiss_button_tappable_size"
             android:elevation="@dimen/overlay_dismiss_button_elevation"
             android:visibility="gone"
+            android:alpha="0"
             app:layout_constraintStart_toEndOf="@id/clipboard_preview"
             app:layout_constraintEnd_toEndOf="@id/clipboard_preview"
             app:layout_constraintTop_toTopOf="@id/clipboard_preview"
diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml
index b6e3499..b1d3ed05 100644
--- a/packages/SystemUI/res/layout/qs_footer_impl.xml
+++ b/packages/SystemUI/res/layout/qs_footer_impl.xml
@@ -29,11 +29,6 @@
     android:clipChildren="false"
     android:clipToPadding="false">
 
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:orientation="vertical">
-
         <LinearLayout
             android:layout_width="match_parent"
             android:layout_height="@dimen/qs_footer_height"
@@ -80,14 +75,4 @@
 
         </LinearLayout>
 
-        <ViewStub
-            android:id="@+id/footer_stub"
-            android:inflatedId="@+id/qs_footer_actions"
-            android:layout="@layout/footer_actions"
-            android:layout_height="@dimen/qs_footer_height"
-            android:layout_width="match_parent"
-        />
-
-    </LinearLayout>
-
 </com.android.systemui.qs.QSFooterView>
diff --git a/packages/SystemUI/res/layout/qs_panel.xml b/packages/SystemUI/res/layout/qs_panel.xml
index 2040051..1eb05bf 100644
--- a/packages/SystemUI/res/layout/qs_panel.xml
+++ b/packages/SystemUI/res/layout/qs_panel.xml
@@ -47,11 +47,10 @@
 
     <include layout="@layout/quick_status_bar_expanded_header" />
 
-    <ViewStub
-        android:id="@+id/container_stub"
-        android:inflatedId="@+id/qs_footer_actions"
-        android:layout="@layout/new_footer_actions"
-        android:layout_height="@dimen/new_footer_height"
+    <include
+        layout="@layout/footer_actions"
+        android:id="@+id/qs_footer_actions"
+        android:layout_height="@dimen/footer_actions_height"
         android:layout_width="match_parent"
         android:layout_gravity="bottom"
         />
diff --git a/packages/SystemUI/res/layout/qs_tile_label.xml b/packages/SystemUI/res/layout/qs_tile_label.xml
index 02c58e4..77523ec9 100644
--- a/packages/SystemUI/res/layout/qs_tile_label.xml
+++ b/packages/SystemUI/res/layout/qs_tile_label.xml
@@ -24,6 +24,8 @@
     android:orientation="vertical"
     android:layout_marginStart="@dimen/qs_label_container_margin"
     android:layout_marginEnd="0dp"
+    android:focusable="false"
+    android:importantForAccessibility="no"
     android:layout_gravity="center_vertical | start">
 
     <com.android.systemui.util.SafeMarqueeTextView
@@ -35,6 +37,8 @@
         android:ellipsize="marquee"
         android:marqueeRepeatLimit="1"
         android:singleLine="true"
+        android:focusable="false"
+        android:importantForAccessibility="no"
         android:textAppearance="@style/TextAppearance.QS.TileLabel"/>
 
     <com.android.systemui.util.SafeMarqueeTextView
@@ -47,6 +51,8 @@
         android:marqueeRepeatLimit="1"
         android:singleLine="true"
         android:visibility="gone"
+        android:focusable="false"
+        android:importantForAccessibility="no"
         android:textAppearance="@style/TextAppearance.QS.TileLabel.Secondary"
         android:textColor="?android:attr/textColorSecondary"/>
 
diff --git a/packages/SystemUI/res/layout/quick_settings_security_footer.xml b/packages/SystemUI/res/layout/quick_settings_security_footer.xml
index 08bd71c..1b11816 100644
--- a/packages/SystemUI/res/layout/quick_settings_security_footer.xml
+++ b/packages/SystemUI/res/layout/quick_settings_security_footer.xml
@@ -14,19 +14,18 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<com.android.systemui.util.DualHeightHorizontalLinearLayout
+<LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:systemui="http://schemas.android.com/apk/res-auto"
-    android:layout_width="match_parent"
-    android:layout_height="@dimen/qs_security_footer_height"
+    android:layout_width="0dp"
+    android:layout_height="@dimen/qs_security_footer_single_line_height"
+    android:layout_weight="1"
     android:clickable="true"
-    android:padding="@dimen/qs_footer_padding"
+    android:orientation="horizontal"
+    android:paddingHorizontal="@dimen/qs_footer_padding"
     android:gravity="center_vertical"
     android:layout_gravity="center_vertical|center_horizontal"
-    android:layout_marginBottom="@dimen/qs_footers_margin_bottom"
+    android:layout_marginEnd="@dimen/qs_footer_action_inset"
     android:background="@drawable/qs_security_footer_background"
-    systemui:singleLineHeight="@dimen/qs_security_footer_single_line_height"
-    systemui:textViewId="@id/footer_text"
     >
 
     <ImageView
@@ -43,7 +42,7 @@
         android:layout_width="0dp"
         android:layout_height="wrap_content"
         android:layout_weight="1"
-        android:maxLines="@integer/qs_security_footer_maxLines"
+        android:singleLine="true"
         android:ellipsize="end"
         android:textAppearance="@style/TextAppearance.QS.SecurityFooter"
         android:textColor="?android:attr/textColorSecondary"/>
@@ -58,4 +57,4 @@
         android:autoMirrored="true"
         android:tint="?android:attr/textColorSecondary" />
 
-</com.android.systemui.util.DualHeightHorizontalLinearLayout>
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/quick_status_bar_header_date_privacy.xml b/packages/SystemUI/res/layout/quick_status_bar_header_date_privacy.xml
index b1e8c38..60bc373 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_header_date_privacy.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_header_date_privacy.xml
@@ -52,7 +52,6 @@
     <!-- We want this to be centered (to align with notches). In order to do that, the following
          has to hold (in portrait):
          * date_container and privacy_container must have the same width and weight
-         * header_text_container must be gone
          -->
     <android.widget.Space
         android:id="@+id/space"
@@ -61,17 +60,6 @@
         android:layout_gravity="center_vertical|center_horizontal"
         android:visibility="gone" />
 
-    <!-- Will hold security footer in landscape with media -->
-    <FrameLayout
-        android:id="@+id/header_text_container"
-        android:layout_height="match_parent"
-        android:layout_width="0dp"
-        android:layout_weight="1"
-        android:paddingStart="16dp"
-        android:paddingEnd="16dp"
-        android:gravity="center"
-    />
-
     <FrameLayout
         android:id="@+id/privacy_container"
         android:layout_width="0dp"
diff --git a/packages/SystemUI/res/layout/screenshot_static.xml b/packages/SystemUI/res/layout/screenshot_static.xml
index 8de8084..c60609b 100644
--- a/packages/SystemUI/res/layout/screenshot_static.xml
+++ b/packages/SystemUI/res/layout/screenshot_static.xml
@@ -98,6 +98,7 @@
         android:scaleType="fitEnd"
         android:background="@drawable/overlay_preview_background"
         android:adjustViewBounds="true"
+        android:clickable="true"
         app:layout_constraintBottom_toBottomOf="@id/screenshot_preview_border"
         app:layout_constraintStart_toStartOf="@id/screenshot_preview_border"
         app:layout_constraintEnd_toEndOf="@id/screenshot_preview_border"
diff --git a/packages/SystemUI/res/layout/status_bar_notification_shelf.xml b/packages/SystemUI/res/layout/status_bar_notification_shelf.xml
index 87a1bbb..58c5450 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_shelf.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_shelf.xml
@@ -17,6 +17,7 @@
 <!-- Extends FrameLayout -->
 <com.android.systemui.statusbar.NotificationShelf
     xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/notificationShelf"
     android:layout_width="match_parent"
     android:layout_height="@dimen/notification_shelf_height"
     android:focusable="true"
diff --git a/packages/SystemUI/res/layout/udfps_view.xml b/packages/SystemUI/res/layout/udfps_view.xml
index 0fcbfa1..257d238 100644
--- a/packages/SystemUI/res/layout/udfps_view.xml
+++ b/packages/SystemUI/res/layout/udfps_view.xml
@@ -28,10 +28,4 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent"/>
 
-    <com.android.systemui.biometrics.UdfpsSurfaceView
-        android:id="@+id/hbm_view"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:visibility="invisible"/>
-
 </com.android.systemui.biometrics.UdfpsView>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index 62903d5..3228c3c 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -188,12 +188,6 @@
         <attr name="borderColor" format="color" />
     </declare-styleable>
 
-    <declare-styleable name="DualHeightHorizontalLinearLayout">
-        <attr name="singleLineHeight" format="dimension" />
-        <attr name="singleLineVerticalPadding" format="dimension" />
-        <attr name="textViewId" format="reference" />
-    </declare-styleable>
-
     <attr name="overlayButtonTextColor" format="color" />
 
     <declare-styleable name="DreamOverlayDotImageView">
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 50fdc7b..24118d2 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -137,9 +137,11 @@
     <!-- UDFPS colors -->
     <color name="udfps_enroll_icon">#7DA7F1</color>
     <color name="udfps_moving_target_fill">#475670</color>
+    <!-- 50% of udfps_moving_target_fill-->
+    <color name="udfps_moving_target_fill_error">#80475670</color>
     <color name="udfps_enroll_progress">#7DA7F1</color>
     <color name="udfps_enroll_progress_help">#607DA7F1</color>
-    <color name="udfps_enroll_progress_help_with_talkback">#ffEE675C</color>
+    <color name="udfps_enroll_progress_help_with_talkback">#FFEE675C</color>
 
     <!-- Floating overlay actions -->
     <color name="overlay_button_ripple">#1f000000</color>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index d5331e8..f2ddf9e 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -446,6 +446,10 @@
          This name is in the ComponentName flattened format (package/class)  -->
     <string name="config_screenshotEditor" translatable="false"></string>
 
+    <!-- Remote copy default activity.  Must handle REMOTE_COPY_ACTION intents.
+     This name is in the ComponentName flattened format (package/class)  -->
+    <string name="config_remoteCopyPackage" translatable="false"></string>
+
     <!-- On debuggable builds, alert the user if SystemUI PSS goes over this number (in kb) -->
     <integer name="watch_heap_limit">256000</integer>
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 7702724..7672251 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -327,23 +327,20 @@
          car setting. -->
     <dimen name="car_qs_header_system_icons_area_height">54dp</dimen>
 
-    <!-- The height of the quick settings footer that holds the user switcher, settings icon,
-         etc. -->
+    <!-- The height of the quick settings footer that holds the pagination dots and edit button -->
     <dimen name="qs_footer_height">48dp</dimen>
 
     <!-- 40dp (circles) + 8dp (circle padding) + 8dp (top) + 4dp (bottom) -->
-    <dimen name="new_footer_height">60dp</dimen>
+    <dimen name="footer_actions_height">60dp</dimen>
 
     <!-- The size of each of the icon buttons in the QS footer -->
     <dimen name="qs_footer_action_button_size">48dp</dimen>
 
     <dimen name="qs_footer_action_corner_radius">20dp</dimen>
 
-    <!-- (48dp - 44dp) / 2 -->
-    <dimen name="qs_footer_action_inset">2dp</dimen>
     <!-- (48dp - 40dp) / 2 -->
-    <dimen name="new_qs_footer_action_inset">4dp</dimen>
-    <dimen name="new_qs_footer_action_inset_negative">-4dp</dimen>
+    <dimen name="qs_footer_action_inset">4dp</dimen>
+    <dimen name="qs_footer_action_inset_negative">-4dp</dimen>
 
     <!-- Margins on each side of QS Footer -->
     <dimen name="qs_footer_margin">2dp</dimen>
@@ -495,7 +492,7 @@
     <dimen name="qs_panel_padding">16dp</dimen>
     <dimen name="qs_dual_tile_padding_horizontal">6dp</dimen>
     <dimen name="qs_panel_elevation">4dp</dimen>
-    <dimen name="qs_panel_padding_bottom">@dimen/new_footer_height</dimen>
+    <dimen name="qs_panel_padding_bottom">@dimen/footer_actions_height</dimen>
     <dimen name="qs_panel_padding_top">48dp</dimen>
 
     <dimen name="qs_data_usage_text_size">14sp</dimen>
@@ -1429,6 +1426,8 @@
     <!-- The margin applied between complications -->
     <dimen name="dream_overlay_complication_margin">0dp</dimen>
 
+    <dimen name="dream_overlay_y_offset">80dp</dimen>
+
     <dimen name="status_view_margin_horizontal">0dp</dimen>
 
     <!-- Media output broadcast dialog QR code picture size -->
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 926734c..ff71b4f 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -23,6 +23,7 @@
     <item type="id" name="scale_x_animator_tag"/>
     <item type="id" name="scale_y_animator_tag"/>
     <item type="id" name="top_inset_animator_tag"/>
+    <item type="id" name="bottom_inset_animator_tag"/>
     <item type="id" name="height_animator_tag"/>
     <item type="id" name="x_animator_tag"/>
     <item type="id" name="y_animator_tag"/>
@@ -33,6 +34,7 @@
     <item type="id" name="scale_y_animator_end_value_tag"/>
     <item type="id" name="alpha_animator_end_value_tag"/>
     <item type="id" name="top_inset_animator_end_value_tag"/>
+    <item type="id" name="bottom_inset_animator_end_value_tag"/>
     <item type="id" name="height_animator_end_value_tag"/>
     <item type="id" name="x_animator_tag_end_value"/>
     <item type="id" name="y_animator_tag_end_value"/>
@@ -43,6 +45,7 @@
     <item type="id" name="scale_y_animator_start_value_tag"/>
     <item type="id" name="alpha_animator_start_value_tag"/>
     <item type="id" name="top_inset_animator_start_value_tag"/>
+    <item type="id" name="bottom_inset_animator_start_value_tag"/>
     <item type="id" name="height_animator_start_value_tag"/>
     <item type="id" name="x_animator_tag_start_value"/>
     <item type="id" name="y_animator_tag_start_value"/>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 898f66d..b248efe 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -338,7 +338,7 @@
     <!-- 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 face. Press to continue.</string>
+    <string name="biometric_dialog_tap_confirm_with_face">Unlocked by face. Press the unlock icon to continue.</string>
     <!-- Talkback string when a biometric is authenticated [CHAR LIMIT=NONE] -->
     <string name="biometric_dialog_authenticated">Authenticated</string>
 
@@ -796,8 +796,11 @@
     <!-- Message shown when lock screen is tapped or face authentication fails. [CHAR LIMIT=60] -->
     <string name="keyguard_unlock">Swipe up to open</string>
 
-    <!-- Message shown when lock screen is tapped or face authentication fails. Provides extra instructions for how the user can enter their device (unlock or proceed to home) [CHAR LIMIT=60] -->
-    <string name="keyguard_unlock_press">Press to open</string>
+    <!-- Message shown when lock screen is unlocked (ie: by trust agent) and the user taps the empty space on the lock screen and UDFPS is supported. Provides extra instructions for how the user can enter their device [CHAR LIMIT=60] -->
+    <string name="keyguard_unlock_press">Press the unlock icon to open</string>
+
+    <!-- Message shown when non-bypass face authentication succeeds and UDFPS is supported. Provides extra instructions for how the user can enter their device [CHAR LIMIT=60] -->
+    <string name="keyguard_face_successful_unlock_press">Unlocked by face. Press the unlock icon to open.</string>
 
     <!-- Message shown when face authentication fails and the pin pad is visible. [CHAR LIMIT=60] -->
     <string name="keyguard_retry">Swipe up to try again</string>
@@ -2196,6 +2199,8 @@
     <string name="controls_media_button_prev">Previous track</string>
     <!-- Description for button in media controls. Pressing button goes to next track [CHAR_LIMIT=NONE] -->
     <string name="controls_media_button_next">Next track</string>
+    <!-- Description for button in media controls. Used when media is connecting to a remote device (via something like chromecast). Pressing button does nothing [CHAR_LIMIT=NONE] -->
+    <string name="controls_media_button_connecting">Connecting</string>
 
     <!-- Title for Smartspace recommendation card within media controls. The "Play" means the action to play a media [CHAR_LIMIT=10] -->
     <string name="controls_media_smartspace_rec_title">Play</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 847aefd..ee77d21 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -958,6 +958,16 @@
         <item name="android:textAlignment">center</item>
     </style>
 
+    <!-- We explicitly overload this because we don't have control over the style or layout for
+         the cast dialog items, as it's in `@android:layout/media_route_list_item. -->
+    <style name="TextAppearance.CastItem" parent="@android:style/TextAppearance.DeviceDefault.Medium">
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+    </style>
+
+    <style name="Theme.SystemUI.Dialog.Cast">
+        <item name="android:textAppearanceMedium">@style/TextAppearance.CastItem</item>
+    </style>
+    <!-- ************************************************************************************* -->
 
     <style name="Widget" />
     <style name="Widget.Dialog" />
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
index 120b09a..de35514 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
@@ -41,6 +41,7 @@
 import android.os.RemoteException;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.util.SparseArray;
 import android.view.IRecentsAnimationController;
 import android.view.SurfaceControl;
 import android.window.IRemoteTransition;
@@ -244,22 +245,28 @@
         @SuppressLint("NewApi")
         boolean merge(TransitionInfo info, SurfaceControl.Transaction t,
                 RecentsAnimationListener recents) {
-            ArrayList<TransitionInfo.Change> openingTasks = null;
+            SparseArray<TransitionInfo.Change> openingTasks = null;
             boolean cancelRecents = false;
             boolean homeGoingAway = false;
             boolean hasChangingApp = false;
             for (int i = info.getChanges().size() - 1; i >= 0; --i) {
                 final TransitionInfo.Change change = info.getChanges().get(i);
                 if (change.getMode() == TRANSIT_OPEN || change.getMode() == TRANSIT_TO_FRONT) {
-                    if (change.getTaskInfo() != null) {
-                        if (change.getTaskInfo().topActivityType == ACTIVITY_TYPE_HOME) {
+                    final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+                    if (taskInfo != null) {
+                        if (taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
                             // canceling recents animation
                             cancelRecents = true;
                         }
                         if (openingTasks == null) {
-                            openingTasks = new ArrayList<>();
+                            openingTasks = new SparseArray<>();
                         }
-                        openingTasks.add(change);
+                        if (taskInfo.hasParentTask()) {
+                            // Collects opening leaf tasks only since Launcher monitors leaf task
+                            // ids to perform recents animation.
+                            openingTasks.remove(taskInfo.parentTaskId);
+                        }
+                        openingTasks.put(taskInfo.taskId, change);
                     }
                 } else if (change.getMode() == TRANSIT_CLOSE
                         || change.getMode() == TRANSIT_TO_BACK) {
@@ -287,7 +294,7 @@
             int pauseMatches = 0;
             if (!cancelRecents) {
                 for (int i = 0; i < openingTasks.size(); ++i) {
-                    if (mPausingTasks.contains(openingTasks.get(i).getContainer())) {
+                    if (mPausingTasks.contains(openingTasks.valueAt(i).getContainer())) {
                         ++pauseMatches;
                     }
                 }
@@ -308,10 +315,11 @@
             final RemoteAnimationTargetCompat[] targets =
                     new RemoteAnimationTargetCompat[openingTasks.size()];
             for (int i = 0; i < openingTasks.size(); ++i) {
-                mOpeningLeashes.add(openingTasks.get(i).getLeash());
+                final TransitionInfo.Change change = openingTasks.valueAt(i);
+                mOpeningLeashes.add(change.getLeash());
                 // We are receiving new opening tasks, so convert to onTasksAppeared.
                 final RemoteAnimationTargetCompat target = new RemoteAnimationTargetCompat(
-                        openingTasks.get(i), layer, info, t);
+                        change, layer, info, t);
                 mLeashMap.put(mOpeningLeashes.get(i), target.leash);
                 t.reparent(target.leash, mInfo.getRootLeash());
                 t.setLayer(target.leash, layer);
diff --git a/packages/SystemUI/src/com/android/keyguard/BouncerPanelExpansionCalculator.kt b/packages/SystemUI/src/com/android/keyguard/BouncerPanelExpansionCalculator.kt
index 497d81f..1b2ea3b 100644
--- a/packages/SystemUI/src/com/android/keyguard/BouncerPanelExpansionCalculator.kt
+++ b/packages/SystemUI/src/com/android/keyguard/BouncerPanelExpansionCalculator.kt
@@ -46,4 +46,24 @@
     fun getKeyguardClockScaledExpansion(fraction: Float): Float {
         return MathUtils.constrain((fraction - 0.7f) / 0.3f, 0f, 1f)
     }
+
+    /**
+     *  Scale the position of the dream complications.
+     */
+    @JvmStatic
+    fun getDreamYPositionScaledExpansion(fraction: Float): Float {
+        return when {
+            fraction >= 0.98f -> 1f
+            fraction < 0.93 -> 0f
+            else -> (fraction - 0.93f) / 0.05f
+        }
+    }
+
+    /**
+     *  Scale the alpha of the dream complications.
+     */
+    @JvmStatic
+    fun getDreamAlphaScaledExpansion(fraction: Float): Float {
+        return MathUtils.constrain((fraction - 0.94f) / 0.06f, 0f, 1f)
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
index 36fe5ba..5953611 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
@@ -21,7 +21,7 @@
     override val listening: Boolean,
     // keep sorted
     val biometricEnabledForUser: Boolean,
-    val bouncer: Boolean,
+    val bouncerIsOrWillShow: Boolean,
     val canSkipBouncer: Boolean,
     val credentialAttempted: Boolean,
     val deviceInteractive: Boolean,
@@ -51,9 +51,10 @@
     val authInterruptActive: Boolean,
     val becauseCannotSkipBouncer: Boolean,
     val biometricSettingEnabledForUser: Boolean,
-    val bouncer: Boolean,
+    val bouncerFullyShown: Boolean,
     val faceAuthenticated: Boolean,
     val faceDisabled: Boolean,
+    val goingToSleep: Boolean,
     val keyguardAwake: Boolean,
     val keyguardGoingAway: Boolean,
     val listeningForFaceAssistant: Boolean,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
index 33d47fe..3aa5ada 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
@@ -123,10 +123,10 @@
 
     private int getLayoutIdFor(SecurityMode securityMode) {
         switch (securityMode) {
-            case Pattern: return com.android.systemui.R.layout.keyguard_pattern_view;
-            case PIN: return com.android.systemui.R.layout.keyguard_pin_view;
-            case Password: return com.android.systemui.R.layout.keyguard_password_view;
-            case SimPin: return com.android.systemui.R.layout.keyguard_sim_pin_view;
+            case Pattern: return R.layout.keyguard_pattern_view;
+            case PIN: return R.layout.keyguard_pin_view;
+            case Password: return R.layout.keyguard_password_view;
+            case SimPin: return R.layout.keyguard_sim_pin_view;
             case SimPuk: return R.layout.keyguard_sim_puk_view;
             default:
                 return 0;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
index 736e34e..ae9d3df 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
@@ -18,8 +18,13 @@
 
 import android.content.Context;
 import android.content.res.Configuration;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.view.View;
+import android.widget.ImageView;
+
+import androidx.core.graphics.drawable.DrawableCompat;
 
 import com.android.systemui.R;
 
@@ -27,6 +32,7 @@
  * Displays a PIN pad for unlocking.
  */
 public class KeyguardSimPinView extends KeyguardPinBasedInputView {
+    private ImageView mSimImageView;
     public static final String TAG = "KeyguardSimPinView";
 
     public KeyguardSimPinView(Context context) {
@@ -62,6 +68,7 @@
 
     @Override
     protected void onFinishInflate() {
+        mSimImageView = findViewById(R.id.keyguard_sim);
         super.onFinishInflate();
 
         if (mEcaView instanceof EmergencyCarrierArea) {
@@ -79,5 +86,17 @@
         return getContext().getString(
                 com.android.internal.R.string.keyguard_accessibility_sim_pin_unlock);
     }
+
+    @Override
+    public void reloadColors() {
+        super.reloadColors();
+
+        int[] customAttrs = {android.R.attr.textColorSecondary};
+        TypedArray a = getContext().obtainStyledAttributes(customAttrs);
+        int imageColor = a.getColor(0, 0);
+        a.recycle();
+        Drawable wrappedDrawable = DrawableCompat.wrap(mSimImageView.getDrawable());
+        DrawableCompat.setTint(wrappedDrawable, imageColor);
+    }
 }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
index 0394e76..47df70b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
@@ -131,6 +131,12 @@
     }
 
     @Override
+    public void reloadColors() {
+        super.reloadColors();
+        mView.reloadColors();
+    }
+
+    @Override
     protected void verifyPasswordAndUnlock() {
         String entry = mPasswordEntry.getText();
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
index 03b647b..760c2cc 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
@@ -17,8 +17,13 @@
 package com.android.keyguard;
 
 import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.widget.ImageView;
+
+import androidx.core.graphics.drawable.DrawableCompat;
 
 import com.android.systemui.R;
 
@@ -27,6 +32,7 @@
  * Displays a PIN pad for entering a PUK (Pin Unlock Kode) provided by a carrier.
  */
 public class KeyguardSimPukView extends KeyguardPinBasedInputView {
+    private ImageView mSimImageView;
     private static final boolean DEBUG = KeyguardConstants.DEBUG;
     public static final String TAG = "KeyguardSimPukView";
 
@@ -79,6 +85,7 @@
 
     @Override
     protected void onFinishInflate() {
+        mSimImageView = findViewById(R.id.keyguard_sim);
         super.onFinishInflate();
 
         if (mEcaView instanceof EmergencyCarrierArea) {
@@ -96,6 +103,18 @@
         return getContext().getString(
                 com.android.internal.R.string.keyguard_accessibility_sim_puk_unlock);
     }
+
+    @Override
+    public void reloadColors() {
+        super.reloadColors();
+
+        int[] customAttrs = {android.R.attr.textColorSecondary};
+        TypedArray a = getContext().obtainStyledAttributes(customAttrs);
+        int imageColor = a.getColor(0, 0);
+        a.recycle();
+        Drawable wrappedDrawable = DrawableCompat.wrap(mSimImageView.getDrawable());
+        DrawableCompat.setTint(wrappedDrawable, imageColor);
+    }
 }
 
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
index ea94b19..47aa43b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
@@ -113,6 +113,12 @@
     }
 
     @Override
+    public void reloadColors() {
+        super.reloadColors();
+        mView.reloadColors();
+    }
+
+    @Override
     protected void verifyPasswordAndUnlock() {
         mStateMachine.next();
     }
@@ -252,9 +258,6 @@
         return mPinText.equals(mPasswordEntry.getText());
     }
 
-
-
-
     private void updateSim() {
         getSimUnlockProgressDialog().show();
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 3b8a29b..1383635 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -21,6 +21,10 @@
 import static android.content.Intent.ACTION_USER_REMOVED;
 import static android.content.Intent.ACTION_USER_STOPPED;
 import static android.content.Intent.ACTION_USER_UNLOCKED;
+import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_NONE;
+import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_PERMANENT;
+import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_TIMED;
+import static android.hardware.biometrics.BiometricConstants.LockoutMode;
 import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN;
 
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
@@ -61,6 +65,7 @@
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback;
 import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.nfc.NfcAdapter;
 import android.os.Build;
 import android.os.CancellationSignal;
@@ -275,7 +280,8 @@
     private boolean mCredentialAttempted;
     private boolean mKeyguardGoingAway;
     private boolean mGoingToSleep;
-    private boolean mBouncer; // true if bouncerIsOrWillBeShowing
+    private boolean mBouncerFullyShown;
+    private boolean mBouncerIsOrWillBeShowing;
     private boolean mAuthInterruptActive;
     private boolean mNeedsSlowUnlockTransition;
     private boolean mAssistantVisible;
@@ -622,7 +628,8 @@
      */
     public void setKeyguardGoingAway(boolean goingAway) {
         mKeyguardGoingAway = goingAway;
-        updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE);
+        // This is set specifically to stop face authentication from running.
+        updateBiometricListeningState(BIOMETRIC_ACTION_STOP);
     }
 
     /**
@@ -864,10 +871,15 @@
         }
     }
 
-    private void handleFingerprintLockoutReset() {
-        boolean changed = mFingerprintLockedOut || mFingerprintLockedOutPermanent;
-        mFingerprintLockedOut = false;
-        mFingerprintLockedOutPermanent = false;
+    private void handleFingerprintLockoutReset(@LockoutMode int mode) {
+        Log.d(TAG, "handleFingerprintLockoutReset: " + mode);
+        final boolean wasLockout = mFingerprintLockedOut;
+        final boolean wasLockoutPermanent = mFingerprintLockedOutPermanent;
+        mFingerprintLockedOut = (mode == BIOMETRIC_LOCKOUT_TIMED)
+                || mode == BIOMETRIC_LOCKOUT_PERMANENT;
+        mFingerprintLockedOutPermanent = (mode == BIOMETRIC_LOCKOUT_PERMANENT);
+        final boolean changed = (mFingerprintLockedOut != wasLockout)
+                || (mFingerprintLockedOutPermanent != wasLockoutPermanent);
 
         if (isUdfpsEnrolled()) {
             // TODO(b/194825098): update the reset signal(s)
@@ -877,7 +889,7 @@
             // be noticeable.
             mHandler.postDelayed(() -> {
                 updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
-            }, BIOMETRIC_LOCKOUT_RESET_DELAY_MS);
+            }, getBiometricLockoutDelay());
         } else {
             updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
         }
@@ -1075,13 +1087,15 @@
         }
     }
 
-    private void handleFaceLockoutReset() {
-        boolean changed = mFaceLockedOutPermanent;
-        mFaceLockedOutPermanent = false;
+    private void handleFaceLockoutReset(@LockoutMode int mode) {
+        Log.d(TAG, "handleFaceLockoutReset: " + mode);
+        final boolean wasLockoutPermanent = mFaceLockedOutPermanent;
+        mFaceLockedOutPermanent = (mode == BIOMETRIC_LOCKOUT_PERMANENT);
+        final boolean changed = (mFaceLockedOutPermanent != wasLockoutPermanent);
 
         mHandler.postDelayed(() -> {
             updateFaceListeningState(BIOMETRIC_ACTION_UPDATE);
-        }, BIOMETRIC_LOCKOUT_RESET_DELAY_MS);
+        }, getBiometricLockoutDelay());
 
         if (changed) {
             notifyLockedOutStateChanged(BiometricSourceType.FACE);
@@ -1160,7 +1174,11 @@
                 || isSimPinSecure());
     }
 
-    private boolean getIsFaceAuthenticated() {
+    /**
+     * @return whether the current user has been authenticated with face. This may be true
+     * on the lockscreen if the user doesn't have bypass enabled.
+     */
+    public boolean getIsFaceAuthenticated() {
         boolean faceAuthenticated = false;
         BiometricAuthenticated bioFaceAuthenticated = mUserFaceAuthenticated.get(getCurrentUser());
         if (bioFaceAuthenticated != null) {
@@ -1461,7 +1479,7 @@
             = new FingerprintManager.LockoutResetCallback() {
         @Override
         public void onLockoutReset(int sensorId) {
-            handleFingerprintLockoutReset();
+            handleFingerprintLockoutReset(BIOMETRIC_LOCKOUT_NONE);
         }
     };
 
@@ -1469,7 +1487,7 @@
             = new FaceManager.LockoutResetCallback() {
         @Override
         public void onLockoutReset(int sensorId) {
-            handleFaceLockoutReset();
+            handleFaceLockoutReset(BIOMETRIC_LOCKOUT_NONE);
         }
     };
 
@@ -1523,6 +1541,10 @@
                 @Override
                 public void onUdfpsPointerDown(int sensorId) {
                     Log.d(TAG, "onUdfpsPointerDown, sensorId: " + sensorId);
+                    requestFaceAuth(true);
+                    if (isFaceDetectionRunning()) {
+                        mKeyguardBypassController.setUserHasDeviceEntryIntent(true);
+                    }
                 }
 
                 @Override
@@ -1579,10 +1601,13 @@
                 }
     };
 
-    private CancellationSignal mFingerprintCancelSignal;
-    private CancellationSignal mFaceCancelSignal;
+    @VisibleForTesting
+    CancellationSignal mFingerprintCancelSignal;
+    @VisibleForTesting
+    CancellationSignal mFaceCancelSignal;
     private FingerprintManager mFpm;
     private FaceManager mFaceManager;
+    private List<FingerprintSensorPropertiesInternal> mFingerprintSensorProperties;
     private List<FaceSensorPropertiesInternal> mFaceSensorProperties;
     private boolean mFingerprintLockedOut;
     private boolean mFingerprintLockedOutPermanent;
@@ -1722,7 +1747,8 @@
                 cb.onFinishedGoingToSleep(arg1);
             }
         }
-        updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE);
+        // This is set specifically to stop face authentication from running.
+        updateBiometricListeningState(BIOMETRIC_ACTION_STOP);
     }
 
     private void handleScreenTurnedOff() {
@@ -1740,7 +1766,12 @@
                 cb.onDreamingStateChanged(mIsDreaming);
             }
         }
-        updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE);
+        if (mIsDreaming) {
+            updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
+            updateFaceListeningState(BIOMETRIC_ACTION_STOP);
+        } else {
+            updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE);
+        }
     }
 
     private void handleUserInfoChanged(int userId) {
@@ -1865,7 +1896,7 @@
                         handleKeyguardReset();
                         break;
                     case MSG_KEYGUARD_BOUNCER_CHANGED:
-                        handleKeyguardBouncerChanged(msg.arg1);
+                        handleKeyguardBouncerChanged(msg.arg1, msg.arg2);
                         break;
                     case MSG_USER_INFO_CHANGED:
                         handleUserInfoChanged(msg.arg1);
@@ -2017,6 +2048,7 @@
 
         if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
             mFpm = (FingerprintManager) context.getSystemService(Context.FINGERPRINT_SERVICE);
+            mFingerprintSensorProperties = mFpm.getSensorPropertiesInternal();
         }
         if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE)) {
             mFaceManager = (FaceManager) context.getSystemService(Context.FACE_SERVICE);
@@ -2220,7 +2252,7 @@
     public void requestFaceAuth(boolean userInitiatedRequest) {
         if (DEBUG) Log.d(TAG, "requestFaceAuth() userInitiated=" + userInitiatedRequest);
         mIsFaceAuthUserRequested |= userInitiatedRequest;
-        updateFaceListeningState(BIOMETRIC_ACTION_UPDATE);
+        updateFaceListeningState(BIOMETRIC_ACTION_START);
     }
 
     public boolean isFaceAuthUserRequested() {
@@ -2355,7 +2387,7 @@
         final boolean shouldListenKeyguardState =
                 mKeyguardIsVisible
                         || !mDeviceInteractive
-                        || (mBouncer && !mKeyguardGoingAway)
+                        || (mBouncerIsOrWillBeShowing && !mKeyguardGoingAway)
                         || mGoingToSleep
                         || shouldListenForFingerprintAssistant
                         || (mKeyguardOccluded && mIsDreaming)
@@ -2375,17 +2407,16 @@
                         && biometricEnabledForUser;
 
         final boolean shouldListenBouncerState =
-                !(mFingerprintLockedOut && mBouncer && mCredentialAttempted);
+                !(mFingerprintLockedOut && mBouncerIsOrWillBeShowing && mCredentialAttempted);
 
         final boolean isEncryptedOrLockdownForUser = isEncryptedOrLockdown(user);
         final boolean shouldListenUdfpsState = !isUdfps
                 || (!userCanSkipBouncer
                     && !isEncryptedOrLockdownForUser
-                    && userDoesNotHaveTrust
-                    && !mFingerprintLockedOut);
+                    && userDoesNotHaveTrust);
 
         final boolean shouldListen = shouldListenKeyguardState && shouldListenUserState
-                && shouldListenBouncerState && shouldListenUdfpsState;
+                && shouldListenBouncerState && shouldListenUdfpsState && !isFingerprintLockedOut();
 
         if (DEBUG_FINGERPRINT || DEBUG_SPEW) {
             maybeLogListenerModelData(
@@ -2394,7 +2425,7 @@
                         user,
                         shouldListen,
                         biometricEnabledForUser,
-                        mBouncer,
+                        mBouncerIsOrWillBeShowing,
                         userCanSkipBouncer,
                         mCredentialAttempted,
                         mDeviceInteractive,
@@ -2450,11 +2481,14 @@
 
         // Scan even when encrypted or timeout to show a preemptive bouncer when bypassing.
         // Lock-down mode shouldn't scan, since it is more explicit.
-        boolean strongAuthAllowsScanning = (!isEncryptedOrTimedOut || canBypass && !mBouncer);
+        boolean strongAuthAllowsScanning = (!isEncryptedOrTimedOut || canBypass
+                && !mBouncerFullyShown);
 
-        // If the device supports face detection (without authentication), allow it to happen
-        // if the device is in lockdown mode. Otherwise, prevent scanning.
+        // If the device supports face detection (without authentication) and bypass is enabled,
+        // allow face scanning to happen if the device is in lockdown mode.
+        // Otherwise, prevent scanning.
         final boolean supportsDetectOnly = !mFaceSensorProperties.isEmpty()
+                && canBypass
                 && mFaceSensorProperties.get(0).supportsFaceDetection;
         if (isLockDown && !supportsDetectOnly) {
             strongAuthAllowsScanning = false;
@@ -2469,8 +2503,12 @@
         // Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an
         // instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware.
         final boolean shouldListen =
-                (mBouncer || mAuthInterruptActive || mOccludingAppRequestingFace || awakeKeyguard
-                        || shouldListenForFaceAssistant)
+                (mBouncerFullyShown && !mGoingToSleep
+                        || mAuthInterruptActive
+                        || mOccludingAppRequestingFace
+                        || awakeKeyguard
+                        || shouldListenForFaceAssistant
+                        || mAuthController.isUdfpsFingerDown())
                 && !mSwitchingUser && !faceDisabledForUser && becauseCannotSkipBouncer
                 && !mKeyguardGoingAway && biometricEnabledForUser && !mLockIconPressed
                 && strongAuthAllowsScanning && mIsPrimaryUser
@@ -2488,9 +2526,10 @@
                         mAuthInterruptActive,
                         becauseCannotSkipBouncer,
                         biometricEnabledForUser,
-                        mBouncer,
+                        mBouncerFullyShown,
                         faceAuthenticated,
                         faceDisabledForUser,
+                        mGoingToSleep,
                         awakeKeyguard,
                         mKeyguardGoingAway,
                         shouldListenForFaceAssistant,
@@ -2771,6 +2810,16 @@
                 cb.onUserSwitchComplete(userId);
             }
         }
+
+        if (mFaceManager != null && !mFaceSensorProperties.isEmpty()) {
+            handleFaceLockoutReset(mFaceManager.getLockoutModeForUser(
+                    mFaceSensorProperties.get(0).sensorId, userId));
+        }
+        if (mFpm != null && !mFingerprintSensorProperties.isEmpty()) {
+            handleFingerprintLockoutReset(mFpm.getLockoutModeForUser(
+                    mFingerprintSensorProperties.get(0).sensorId, userId));
+        }
+
         mInteractionJankMonitor.end(InteractionJankMonitor.CUJ_USER_SWITCH);
         mLatencyTracker.onActionEnd(LatencyTracker.ACTION_USER_SWITCH);
     }
@@ -3050,13 +3099,21 @@
     /**
      * Handle {@link #MSG_KEYGUARD_BOUNCER_CHANGED}
      *
-     * @see #sendKeyguardBouncerChanged(boolean)
+     * @see #sendKeyguardBouncerChanged(boolean, boolean)
      */
-    private void handleKeyguardBouncerChanged(int bouncerVisible) {
+    private void handleKeyguardBouncerChanged(int bouncerIsOrWillBeShowing, int bouncerFullyShown) {
         Assert.isMainThread();
-        if (DEBUG) Log.d(TAG, "handleKeyguardBouncerChanged(" + bouncerVisible + ")");
-        mBouncer = bouncerVisible == 1;
-        if (mBouncer) {
+        final boolean wasBouncerIsOrWillBeShowing = mBouncerIsOrWillBeShowing;
+        final boolean wasBouncerFullyShown = mBouncerFullyShown;
+        mBouncerIsOrWillBeShowing = bouncerIsOrWillBeShowing == 1;
+        mBouncerFullyShown = bouncerFullyShown == 1;
+        if (DEBUG) {
+            Log.d(TAG, "handleKeyguardBouncerChanged"
+                    + " bouncerIsOrWillBeShowing=" + mBouncerIsOrWillBeShowing
+                    + " bouncerFullyShowing=" + mBouncerFullyShown);
+        }
+
+        if (mBouncerFullyShown) {
             // If the bouncer is shown, always clear this flag. This can happen in the following
             // situations: 1) Default camera with SHOW_WHEN_LOCKED is not chosen yet. 2) Secure
             // camera requests dismiss keyguard (tapping on photos for example). When these happen,
@@ -3066,13 +3123,25 @@
             mCredentialAttempted = false;
         }
 
-        for (int i = 0; i < mCallbacks.size(); i++) {
-            KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
-            if (cb != null) {
-                cb.onKeyguardBouncerChanged(mBouncer);
+        if (wasBouncerIsOrWillBeShowing != mBouncerIsOrWillBeShowing) {
+            for (int i = 0; i < mCallbacks.size(); i++) {
+                KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+                if (cb != null) {
+                    cb.onKeyguardBouncerStateChanged(mBouncerIsOrWillBeShowing);
+                }
             }
+            updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
         }
-        updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE);
+
+        if (wasBouncerFullyShown != mBouncerFullyShown) {
+            for (int i = 0; i < mCallbacks.size(); i++) {
+                KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+                if (cb != null) {
+                    cb.onKeyguardBouncerFullyShowingChanged(mBouncerFullyShown);
+                }
+            }
+            updateFaceListeningState(BIOMETRIC_ACTION_UPDATE);
+        }
     }
 
     /**
@@ -3219,12 +3288,18 @@
     }
 
     /**
-     * @see #handleKeyguardBouncerChanged(int)
+     * @see #handleKeyguardBouncerChanged(int, int)
      */
-    public void sendKeyguardBouncerChanged(boolean bouncerIsOrWillBeShowing) {
-        if (DEBUG) Log.d(TAG, "sendKeyguardBouncerChanged(" + bouncerIsOrWillBeShowing + ")");
+    public void sendKeyguardBouncerChanged(boolean bouncerIsOrWillBeShowing,
+            boolean bouncerFullyShown) {
+        if (DEBUG) {
+            Log.d(TAG, "sendKeyguardBouncerChanged"
+                    + " bouncerIsOrWillBeShowing=" + bouncerIsOrWillBeShowing
+                    + " bouncerFullyShown=" + bouncerFullyShown);
+        }
         Message message = mHandler.obtainMessage(MSG_KEYGUARD_BOUNCER_CHANGED);
         message.arg1 = bouncerIsOrWillBeShowing ? 1 : 0;
+        message.arg2 = bouncerFullyShown ? 1 : 0;
         message.sendToTarget();
     }
 
@@ -3480,6 +3555,10 @@
         }
     }
 
+    protected int getBiometricLockoutDelay() {
+        return BIOMETRIC_LOCKOUT_RESET_DELAY_MS;
+    }
+
     /**
      * Unregister all listeners.
      */
@@ -3561,7 +3640,7 @@
             if (isUdfpsSupported()) {
                 pw.println("        udfpsEnrolled=" + isUdfpsEnrolled());
                 pw.println("        shouldListenForUdfps=" + shouldListenForFingerprint(true));
-                pw.println("        bouncerVisible=" + mBouncer);
+                pw.println("        mBouncerIsOrWillBeShowing=" + mBouncerIsOrWillBeShowing);
                 pw.println("        mStatusBarState=" + StatusBarState.toString(mStatusBarState));
             }
         }
@@ -3585,6 +3664,7 @@
             pw.println("    mFaceLockedOutPermanent=" + mFaceLockedOutPermanent);
             pw.println("    enabledByUser=" + mBiometricEnabledForUser.get(userId));
             pw.println("    mSecureCameraLaunched=" + mSecureCameraLaunched);
+            pw.println("    mBouncerFullyShown=" + mBouncerFullyShown);
         }
         mListenModels.print(pw);
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 9373ea8..2620195 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -97,10 +97,16 @@
 
     /**
      * Called when the keyguard enters or leaves bouncer mode.
-     * @param bouncer if true, keyguard is showing the bouncer or transitioning from/to bouncer
-     *                mode.
+     * @param bouncerIsOrWillBeShowing if true, keyguard is showing the bouncer or transitioning
+     *                                 from/to bouncer mode.
      */
-    public void onKeyguardBouncerChanged(boolean bouncer) { }
+    public void onKeyguardBouncerStateChanged(boolean bouncerIsOrWillBeShowing) { }
+
+    /**
+     * Called when the keyguard fully transitions to the bouncer or is no longer the bouncer
+     * @param bouncerIsFullyShowing if true, keyguard is fully showing the bouncer
+     */
+    public void onKeyguardBouncerFullyShowingChanged(boolean bouncerIsFullyShowing) { }
 
     /**
      * Called when visibility of lockscreen clock changes, such as when
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index ffded65..25f185c 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -468,7 +468,7 @@
                 }
 
                 @Override
-                public void onKeyguardBouncerChanged(boolean bouncer) {
+                public void onKeyguardBouncerStateChanged(boolean bouncer) {
                     mIsBouncerShowing = bouncer;
                     updateVisibility();
                 }
diff --git a/packages/SystemUI/src/com/android/keyguard/mediator/ScreenOnCoordinator.kt b/packages/SystemUI/src/com/android/keyguard/mediator/ScreenOnCoordinator.kt
index 705cf6d..4b7e9a5 100644
--- a/packages/SystemUI/src/com/android/keyguard/mediator/ScreenOnCoordinator.kt
+++ b/packages/SystemUI/src/com/android/keyguard/mediator/ScreenOnCoordinator.kt
@@ -48,18 +48,6 @@
         SysUIUnfoldComponent::getFoldAodAnimationController).getOrNull()
     private val pendingTasks = PendingTasksContainer()
 
-    private var wakeAndUnlockingTask: Runnable? = null
-    var wakeAndUnlocking = false
-        set(value) {
-            if (!value && field) {
-                // When updating the value back to false, mark the task complete in order to
-                // callback onDrawn
-                wakeAndUnlockingTask?.run()
-                wakeAndUnlockingTask = null
-            }
-            field = value
-        }
-
     init {
         screenLifecycle.addObserver(this)
     }
@@ -76,10 +64,6 @@
         unfoldLightRevealAnimation?.onScreenTurningOn(pendingTasks.registerTask("unfold-reveal"))
         foldAodAnimationController?.onScreenTurningOn(pendingTasks.registerTask("fold-to-aod"))
 
-        if (wakeAndUnlocking) {
-            wakeAndUnlockingTask = pendingTasks.registerTask("wake-and-unlocking")
-        }
-
         pendingTasks.onTasksComplete { onDrawn.run() }
         Trace.endSection()
     }
@@ -91,8 +75,4 @@
 
         pendingTasks.reset()
     }
-
-    override fun onScreenTurnedOff() {
-        wakeAndUnlockingTask = null
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 6b6af4c..b2673e9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -126,7 +126,7 @@
         int[] mSensorIds;
         boolean mSkipIntro;
         long mOperationId;
-        long mRequestId;
+        long mRequestId = -1;
         boolean mSkipAnimation = false;
         @BiometricMultiSensorMode int mMultiSensorConfig = BIOMETRIC_MULTI_SENSOR_DEFAULT;
     }
@@ -599,6 +599,11 @@
     }
 
     @Override
+    public long getRequestId() {
+        return mConfig.mRequestId;
+    }
+
+    @Override
     public void animateToCredentialUI() {
         mBiometricView.startTransitionToCredentialUI();
     }
@@ -678,7 +683,9 @@
             return;
         }
         mContainerState = STATE_GONE;
-        mWindowManager.removeView(this);
+        if (isAttachedToWindow()) {
+            mWindowManager.removeView(this);
+        }
     }
 
     private void onDialogAnimatedIn() {
@@ -687,6 +694,11 @@
             animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
             return;
         }
+        if (mContainerState == STATE_ANIMATING_OUT || mContainerState == STATE_GONE) {
+            Log.d(TAG, "onDialogAnimatedIn(): ignore, already animating out or gone - state: "
+                    + mContainerState);
+            return;
+        }
         mContainerState = STATE_SHOWING;
         if (mBiometricView != null) {
             mConfig.mCallback.onDialogAnimatedIn();
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index c100a07..aaf18b3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -768,7 +768,7 @@
     }
 
     @Override
-    public void hideAuthenticationDialog() {
+    public void hideAuthenticationDialog(long requestId) {
         if (DEBUG) Log.d(TAG, "hideAuthenticationDialog: " + mCurrentDialog);
 
         if (mCurrentDialog == null) {
@@ -777,6 +777,11 @@
             if (DEBUG) Log.d(TAG, "dialog already gone");
             return;
         }
+        if (requestId != mCurrentDialog.getRequestId()) {
+            Log.w(TAG, "ignore - ids do not match: " + requestId + " current: "
+                    + mCurrentDialog.getRequestId());
+            return;
+        }
 
         mCurrentDialog.dismissFromSystemServer();
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
index 59ed156..4ff19f6 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
@@ -150,6 +150,9 @@
      */
     String getOpPackageName();
 
+    /** The requestId of the underlying operation within the framework. */
+    long getRequestId();
+
     /**
      * Animate to credential UI. Typically called after biometric is locked out.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 0cde745..33126b3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -280,7 +280,7 @@
             }
         }
 
-        override fun onKeyguardBouncerChanged(bouncerIsOrWillBeShowing: Boolean) {
+        override fun onKeyguardBouncerStateChanged(bouncerIsOrWillBeShowing: Boolean) {
             if (bouncerIsOrWillBeShowing) {
                 mView.fadeDwellRipple()
             }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 975e0c5..2ac2408 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -65,7 +65,6 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
 import com.android.systemui.statusbar.VibratorHelper;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
@@ -124,7 +123,6 @@
     @NonNull private final AccessibilityManager mAccessibilityManager;
     @NonNull private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
     @Nullable private final UdfpsHbmProvider mHbmProvider;
-    @NonNull private final KeyguardBypassController mKeyguardBypassController;
     @NonNull private final ConfigurationController mConfigurationController;
     @NonNull private final SystemClock mSystemClock;
     @NonNull private final UnlockedScreenOffAnimationController
@@ -378,7 +376,6 @@
                 boolean withinSensorArea =
                         isWithinSensorArea(udfpsView, event.getX(), event.getY(), fromUdfpsView);
                 if (withinSensorArea) {
-                    mLatencyTracker.onActionStart(LatencyTracker.ACTION_UDFPS_ILLUMINATE);
                     Trace.beginAsyncSection("UdfpsController.e2e.onPointerDown", 0);
                     Log.v(TAG, "onTouch | action down");
                     // The pointer that causes ACTION_DOWN is always at index 0.
@@ -533,7 +530,6 @@
             @NonNull UdfpsHapticsSimulator udfpsHapticsSimulator,
             @NonNull Optional<UdfpsHbmProvider> hbmProvider,
             @NonNull KeyguardStateController keyguardStateController,
-            @NonNull KeyguardBypassController keyguardBypassController,
             @NonNull DisplayManager displayManager,
             @Main Handler mainHandler,
             @NonNull ConfigurationController configurationController,
@@ -565,7 +561,6 @@
         mHbmProvider = hbmProvider.orElse(null);
         screenLifecycle.addObserver(mScreenObserver);
         mScreenOn = screenLifecycle.getScreenState() == ScreenLifecycle.SCREEN_ON;
-        mKeyguardBypassController = keyguardBypassController;
         mConfigurationController = configurationController;
         mSystemClock = systemClock;
         mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
@@ -796,11 +791,7 @@
                     + " current: " + mOverlay.getRequestId());
             return;
         }
-
-        if (mOverlay.getAnimationViewController() instanceof UdfpsKeyguardViewController
-                && !mStatusBarStateController.isDozing()) {
-            mKeyguardBypassController.setUserHasDeviceEntryIntent(true);
-        }
+        mLatencyTracker.onActionStart(LatencyTracker.ACTION_UDFPS_ILLUMINATE);
 
         if (!mOnFingerDown) {
             playStartHaptic();
@@ -815,11 +806,9 @@
 
         final UdfpsView view = mOverlay.getOverlayView();
         if (view != null) {
-            Trace.beginAsyncSection("UdfpsController.e2e.startIllumination", 0);
             view.startIllumination(() -> {
                 mFingerprintManager.onUiReady(requestId, mSensorProps.sensorId);
                 mLatencyTracker.onActionEnd(LatencyTracker.ACTION_UDFPS_ILLUMINATE);
-                Trace.endAsyncSection("UdfpsController.e2e.startIllumination", 0);
             });
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
index f3a603f..59c658f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
@@ -23,6 +23,7 @@
 import android.graphics.Paint;
 import android.graphics.drawable.Drawable;
 import android.view.accessibility.AccessibilityManager;
+import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
 import android.view.animation.OvershootInterpolator;
 
@@ -40,13 +41,15 @@
 
     private static final long CHECKMARK_ANIMATION_DELAY_MS = 200L;
     private static final long CHECKMARK_ANIMATION_DURATION_MS = 300L;
-    private static final long FILL_COLOR_ANIMATION_DURATION_MS = 200L;
+    private static final long FILL_COLOR_ANIMATION_DURATION_MS = 350L;
     private static final long PROGRESS_ANIMATION_DURATION_MS = 400L;
     private static final float STROKE_WIDTH_DP = 12f;
+    private static final Interpolator DEACCEL = new DecelerateInterpolator();
 
     private final float mStrokeWidthPx;
     @ColorInt private final int mProgressColor;
     @ColorInt private final int mHelpColor;
+    @ColorInt private final int mOnFirstBucketFailedColor;
     @NonNull private final Drawable mCheckmarkDrawable;
     @NonNull private final Interpolator mCheckmarkInterpolator;
     @NonNull private final Paint mBackgroundPaint;
@@ -64,6 +67,9 @@
     @Nullable private ValueAnimator mFillColorAnimator;
     @NonNull private final ValueAnimator.AnimatorUpdateListener mFillColorUpdateListener;
 
+    @Nullable private ValueAnimator mBackgroundColorAnimator;
+    @NonNull private final ValueAnimator.AnimatorUpdateListener mBackgroundColorUpdateListener;
+
     private boolean mComplete = false;
     private float mCheckmarkScale = 0f;
     @Nullable private ValueAnimator mCheckmarkAnimator;
@@ -76,8 +82,10 @@
         final boolean isAccessbilityEnabled = am.isTouchExplorationEnabled();
         if (!isAccessbilityEnabled) {
             mHelpColor = context.getColor(R.color.udfps_enroll_progress_help);
+            mOnFirstBucketFailedColor = context.getColor(R.color.udfps_moving_target_fill_error);
         } else {
             mHelpColor = context.getColor(R.color.udfps_enroll_progress_help_with_talkback);
+            mOnFirstBucketFailedColor = mHelpColor;
         }
         mCheckmarkDrawable = context.getDrawable(R.drawable.udfps_enroll_checkmark);
         mCheckmarkDrawable.mutate();
@@ -112,6 +120,11 @@
             mCheckmarkScale = (float) animation.getAnimatedValue();
             invalidateSelf();
         };
+
+        mBackgroundColorUpdateListener = animation -> {
+            mBackgroundPaint.setColor((int) animation.getAnimatedValue());
+            invalidateSelf();
+        };
     }
 
     void onEnrollmentProgress(int remaining, int totalSteps) {
@@ -163,19 +176,38 @@
         }
     }
 
+    private void animateBackgroundColor() {
+        if (mBackgroundColorAnimator != null && mBackgroundColorAnimator.isRunning()) {
+            mBackgroundColorAnimator.end();
+        }
+        mBackgroundColorAnimator = ValueAnimator.ofArgb(mBackgroundPaint.getColor(),
+                mOnFirstBucketFailedColor);
+        mBackgroundColorAnimator.setDuration(FILL_COLOR_ANIMATION_DURATION_MS);
+        mBackgroundColorAnimator.setRepeatCount(1);
+        mBackgroundColorAnimator.setRepeatMode(ValueAnimator.REVERSE);
+        mBackgroundColorAnimator.setInterpolator(DEACCEL);
+        mBackgroundColorAnimator.addUpdateListener(mBackgroundColorUpdateListener);
+        mBackgroundColorAnimator.start();
+    }
+
     private void updateFillColor(boolean showingHelp) {
-        if (mShowingHelp == showingHelp) {
+        if (!mAfterFirstTouch && showingHelp) {
+            // If we are on the first touch, animate the background color
+            // instead of the progress color.
+            animateBackgroundColor();
             return;
         }
-        mShowingHelp = showingHelp;
 
         if (mFillColorAnimator != null && mFillColorAnimator.isRunning()) {
-            mFillColorAnimator.cancel();
+            mFillColorAnimator.end();
         }
 
         @ColorInt final int targetColor = showingHelp ? mHelpColor : mProgressColor;
         mFillColorAnimator = ValueAnimator.ofArgb(mFillPaint.getColor(), targetColor);
         mFillColorAnimator.setDuration(FILL_COLOR_ANIMATION_DURATION_MS);
+        mFillColorAnimator.setRepeatCount(1);
+        mFillColorAnimator.setRepeatMode(ValueAnimator.REVERSE);
+        mFillColorAnimator.setInterpolator(DEACCEL);
         mFillColorAnimator.addUpdateListener(mFillColorUpdateListener);
         mFillColorAnimator.start();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHbmProvider.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHbmProvider.java
index da24a8f..38c937f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHbmProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHbmProvider.java
@@ -17,9 +17,6 @@
 package com.android.systemui.biometrics;
 
 import android.annotation.Nullable;
-import android.view.Surface;
-
-import com.android.systemui.biometrics.UdfpsHbmTypes.HbmType;
 
 /**
  * Interface for controlling the high-brightness mode (HBM). UdfpsView can use this callback to
@@ -36,24 +33,21 @@
      * This method must be called from the UI thread. The callback, if provided, will also be
      * invoked from the UI thread.
      *
-     * @param hbmType The type of HBM that should be enabled. See {@link UdfpsHbmTypes}.
-     * @param surface The surface for which the HBM is requested, in case the HBM implementation
-     *                needs to set special surface flags to enable the HBM. Can be null.
      * @param onHbmEnabled A runnable that will be executed once HBM is enabled.
      */
-    void enableHbm(@HbmType int hbmType, @Nullable Surface surface,
-            @Nullable Runnable onHbmEnabled);
+    void enableHbm(@Nullable Runnable onHbmEnabled);
 
     /**
-     * UdfpsView will call this to disable the HBM when the illumination is not longer needed.
+     * UdfpsView will call this to disable HBM when illumination is no longer needed.
      *
-     * This method is a no-op when HBM is already disabled. If HBM is enabled, this method will
-     * disable HBM for the {@code hbmType} and {@code surface} that were provided to the
-     * corresponding {@link #enableHbm(int, Surface, Runnable)}.
+     * This method will disable HBM if HBM is enabled. Otherwise, if HBM is already disabled,
+     * this method is a no-op.
      *
      * The call must be made from the UI thread. The callback, if provided, will also be invoked
      * from the UI thread.
      *
+     *
+     *
      * @param onHbmDisabled A runnable that will be executed once HBM is disabled.
      */
     void disableHbm(@Nullable Runnable onHbmDisabled);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHbmTypes.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHbmTypes.java
deleted file mode 100644
index 3ab0bd6..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHbmTypes.java
+++ /dev/null
@@ -1,39 +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 android.annotation.IntDef;
-import android.hardware.fingerprint.IUdfpsHbmListener;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Different high-brightness mode (HBM) types that are relevant to this package.
- */
-public final class UdfpsHbmTypes {
-    /** HBM that applies to the whole screen. */
-    public static final int GLOBAL_HBM = IUdfpsHbmListener.GLOBAL_HBM;
-
-    /** HBM that only applies to a portion of the screen. */
-    public static final int LOCAL_HBM = IUdfpsHbmListener.LOCAL_HBM;
-
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({GLOBAL_HBM, LOCAL_HBM})
-    public @interface HbmType {
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsSurfaceView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsSurfaceView.java
deleted file mode 100644
index 77fad35..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsSurfaceView.java
+++ /dev/null
@@ -1,145 +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 android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
-import android.graphics.RectF;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.Surface;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
-
-/**
- * Surface View for providing the Global High-Brightness Mode (GHBM) illumination for UDFPS.
- */
-public class UdfpsSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
-    private static final String TAG = "UdfpsSurfaceView";
-
-    /**
-     * Notifies {@link UdfpsView} when to enable GHBM illumination.
-     */
-    interface GhbmIlluminationListener {
-        /**
-         * @param surface the surface for which GHBM should be enabled.
-         * @param onIlluminatedRunnable a runnable that should be run after GHBM is enabled.
-         */
-        void enableGhbm(@NonNull Surface surface, @Nullable Runnable onIlluminatedRunnable);
-    }
-
-    @NonNull private final SurfaceHolder mHolder;
-    @NonNull private final Paint mSensorPaint;
-
-    @Nullable private GhbmIlluminationListener mGhbmIlluminationListener;
-    @Nullable private Runnable mOnIlluminatedRunnable;
-    boolean mAwaitingSurfaceToStartIllumination;
-    boolean mHasValidSurface;
-
-    public UdfpsSurfaceView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-
-        // Make this SurfaceView draw on top of everything else in this window. This allows us to
-        // 1) Always show the HBM circle on top of everything else, and
-        // 2) Properly composite this view with any other animations in the same window no matter
-        //    what contents are added in which order to this view hierarchy.
-        setZOrderOnTop(true);
-
-        mHolder = getHolder();
-        mHolder.addCallback(this);
-        mHolder.setFormat(PixelFormat.RGBA_8888);
-
-        mSensorPaint = new Paint(0 /* flags */);
-        mSensorPaint.setAntiAlias(true);
-        mSensorPaint.setARGB(255, 255, 255, 255);
-        mSensorPaint.setStyle(Paint.Style.FILL);
-    }
-
-    @Override public void surfaceCreated(SurfaceHolder holder) {
-        mHasValidSurface = true;
-        if (mAwaitingSurfaceToStartIllumination) {
-            doIlluminate(mOnIlluminatedRunnable);
-            mOnIlluminatedRunnable = null;
-            mAwaitingSurfaceToStartIllumination = false;
-        }
-    }
-
-    @Override
-    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
-        // Unused.
-    }
-
-    @Override public void surfaceDestroyed(SurfaceHolder holder) {
-        mHasValidSurface = false;
-    }
-
-    void setGhbmIlluminationListener(@Nullable GhbmIlluminationListener listener) {
-        mGhbmIlluminationListener = listener;
-    }
-
-    /**
-     * Note: there is no corresponding method to stop GHBM illumination. It is expected that
-     * {@link UdfpsView} will hide this view, which would destroy the surface and remove the
-     * illumination dot.
-     */
-    void startGhbmIllumination(@Nullable Runnable onIlluminatedRunnable) {
-        if (mGhbmIlluminationListener == null) {
-            Log.e(TAG, "startIllumination | mGhbmIlluminationListener is null");
-            return;
-        }
-
-        if (mHasValidSurface) {
-            doIlluminate(onIlluminatedRunnable);
-        } else {
-            mAwaitingSurfaceToStartIllumination = true;
-            mOnIlluminatedRunnable = onIlluminatedRunnable;
-        }
-    }
-
-    private void doIlluminate(@Nullable Runnable onIlluminatedRunnable) {
-        if (mGhbmIlluminationListener == null) {
-            Log.e(TAG, "doIlluminate | mGhbmIlluminationListener is null");
-            return;
-        }
-
-        mGhbmIlluminationListener.enableGhbm(mHolder.getSurface(), onIlluminatedRunnable);
-    }
-
-    /**
-     * Immediately draws the illumination dot on this SurfaceView's surface.
-     */
-    void drawIlluminationDot(@NonNull RectF sensorRect) {
-        if (!mHasValidSurface) {
-            Log.e(TAG, "drawIlluminationDot | the surface is destroyed or was never created.");
-            return;
-        }
-        Canvas canvas = null;
-        try {
-            canvas = mHolder.lockCanvas();
-            canvas.drawOval(sensorRect, mSensorPaint);
-        } finally {
-            // Make sure the surface is never left in a bad state.
-            if (canvas != null) {
-                mHolder.unlockCanvasAndPost(canvas);
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
index 9fbc458..75e6aa0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
@@ -22,26 +22,17 @@
 import android.graphics.PointF
 import android.graphics.RectF
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
-import android.os.Build
-import android.os.UserHandle
-import android.provider.Settings
 import android.util.AttributeSet
 import android.util.Log
 import android.view.MotionEvent
-import android.view.Surface
 import android.widget.FrameLayout
 import com.android.systemui.R
 import com.android.systemui.doze.DozeReceiver
-import com.android.systemui.biometrics.UdfpsHbmTypes.HbmType
 
 private const val TAG = "UdfpsView"
-private const val SETTING_HBM_TYPE = "com.android.systemui.biometrics.UdfpsSurfaceView.hbmType"
-@HbmType
-private const val DEFAULT_HBM_TYPE = UdfpsHbmTypes.LOCAL_HBM
 
 /**
- * A view containing 1) A SurfaceView for HBM, and 2) A normal drawable view for all other
- * animations.
+ * The main view group containing all UDFPS animations.
  */
 class UdfpsView(
     context: Context,
@@ -68,21 +59,6 @@
         com.android.internal.R.integer.config_udfps_illumination_transition_ms
     ).toLong()
 
-    @HbmType
-    private val hbmType = if (Build.IS_ENG || Build.IS_USERDEBUG) {
-        Settings.Secure.getIntForUser(
-            context.contentResolver,
-            SETTING_HBM_TYPE,
-            DEFAULT_HBM_TYPE,
-            UserHandle.USER_CURRENT
-        )
-    } else {
-        DEFAULT_HBM_TYPE
-    }
-
-    // Only used for UdfpsHbmTypes.GLOBAL_HBM.
-    private var ghbmView: UdfpsSurfaceView? = null
-
     /** View controller (can be different for enrollment, BiometricPrompt, Keyguard, etc.). */
     var animationViewController: UdfpsAnimationViewController<*>? = null
 
@@ -109,12 +85,6 @@
         return (animationViewController == null || !animationViewController!!.shouldPauseAuth())
     }
 
-    override fun onFinishInflate() {
-        if (hbmType == UdfpsHbmTypes.GLOBAL_HBM) {
-            ghbmView = findViewById(R.id.hbm_view)
-        }
-    }
-
     override fun dozeTimeTick() {
         animationViewController?.dozeTimeTick()
     }
@@ -180,24 +150,11 @@
     override fun startIllumination(onIlluminatedRunnable: Runnable?) {
         isIlluminationRequested = true
         animationViewController?.onIlluminationStarting()
-
-        val gView = ghbmView
-        if (gView != null) {
-            gView.setGhbmIlluminationListener(this::doIlluminate)
-            gView.visibility = VISIBLE
-            gView.startGhbmIllumination(onIlluminatedRunnable)
-        } else {
-            doIlluminate(null /* surface */, onIlluminatedRunnable)
-        }
+        doIlluminate(onIlluminatedRunnable)
     }
 
-    private fun doIlluminate(surface: Surface?, onIlluminatedRunnable: Runnable?) {
-        if (ghbmView != null && surface == null) {
-            Log.e(TAG, "doIlluminate | surface must be non-null for GHBM")
-        }
-
-        hbmProvider?.enableHbm(hbmType, surface) {
-            ghbmView?.drawIlluminationDot(sensorRect)
+    private fun doIlluminate(onIlluminatedRunnable: Runnable?) {
+        hbmProvider?.enableHbm() {
             if (onIlluminatedRunnable != null) {
                 // No framework API can reliably tell when a frame reaches the panel. A timeout
                 // is the safest solution.
@@ -211,10 +168,6 @@
     override fun stopIllumination() {
         isIlluminationRequested = false
         animationViewController?.onIlluminationStopped()
-        ghbmView?.let { view ->
-            view.setGhbmIlluminationListener(null)
-            view.visibility = INVISIBLE
-        }
         hbmProvider?.disableHbm(null /* onHbmDisabled */)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
index 0ff5805..1999882 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
@@ -41,7 +41,8 @@
     val receiver: BroadcastReceiver,
     val filter: IntentFilter,
     val executor: Executor,
-    val user: UserHandle
+    val user: UserHandle,
+    val permission: String? = null
 )
 
 private const val MSG_ADD_RECEIVER = 0
@@ -96,16 +97,18 @@
      *
      */
     @Deprecated(message = "Replacing Handler for Executor in SystemUI",
-            replaceWith = ReplaceWith("registerReceiver(receiver, filter, executor, user)"))
+        replaceWith = ReplaceWith("registerReceiver(receiver, filter, executor, user, permission)")
+    )
     @JvmOverloads
     open fun registerReceiverWithHandler(
         receiver: BroadcastReceiver,
         filter: IntentFilter,
         handler: Handler,
         user: UserHandle = context.user,
-        @Context.RegisterReceiverFlags flags: Int = Context.RECEIVER_EXPORTED
+        @Context.RegisterReceiverFlags flags: Int = Context.RECEIVER_EXPORTED,
+        permission: String? = null
     ) {
-        registerReceiver(receiver, filter, HandlerExecutor(handler), user, flags)
+        registerReceiver(receiver, filter, HandlerExecutor(handler), user, flags, permission)
     }
 
     /**
@@ -130,15 +133,17 @@
         filter: IntentFilter,
         executor: Executor? = null,
         user: UserHandle? = null,
-        @Context.RegisterReceiverFlags flags: Int = Context.RECEIVER_EXPORTED
+        @Context.RegisterReceiverFlags flags: Int = Context.RECEIVER_EXPORTED,
+        permission: String? = null,
     ) {
         checkFilter(filter)
         val data = ReceiverData(
                 receiver,
                 filter,
                 executor ?: context.mainExecutor,
-                user ?: context.user
-        )
+                user ?: context.user,
+                permission
+            )
         this.handler
                 .obtainMessage(MSG_ADD_RECEIVER, flags, 0, data)
                 .sendToTarget()
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
index d4e9416..eb0cf5e 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
@@ -71,9 +71,16 @@
         }
     }
 
+    // Used for key in actionsToActionsReceivers
+    internal data class ReceiverProperties(
+        val action: String,
+        val flags: Int,
+        val permission: String?
+    )
+
     // Only modify in BG thread
     @VisibleForTesting
-    internal val actionsToActionsReceivers = ArrayMap<Pair<String, Int>, ActionReceiver>()
+    internal val actionsToActionsReceivers = ArrayMap<ReceiverProperties, ActionReceiver>()
     private val receiverToActions = ArrayMap<BroadcastReceiver, MutableSet<String>>()
 
     @VisibleForTesting
@@ -106,14 +113,20 @@
                 .addAll(receiverData.filter.actionsIterator()?.asSequence() ?: emptySequence())
         receiverData.filter.actionsIterator().forEach {
             actionsToActionsReceivers
-                    .getOrPut(it to flags, { createActionReceiver(it, flags) })
-                    .addReceiverData(receiverData)
+                .getOrPut(
+                    ReceiverProperties(it, flags, receiverData.permission),
+                    { createActionReceiver(it, receiverData.permission, flags) })
+                .addReceiverData(receiverData)
         }
         logger.logReceiverRegistered(userId, receiverData.receiver, flags)
     }
 
     @VisibleForTesting
-    internal open fun createActionReceiver(action: String, flags: Int): ActionReceiver {
+    internal open fun createActionReceiver(
+        action: String,
+        permission: String?,
+        flags: Int
+    ): ActionReceiver {
         return ActionReceiver(
                 action,
                 userId,
@@ -122,7 +135,7 @@
                             this,
                             UserHandle.of(userId),
                             it,
-                            null,
+                            permission,
                             bgHandler,
                             flags
                     )
@@ -149,7 +162,7 @@
         if (DEBUG) Log.w(TAG, "Unregister receiver: $receiver")
         receiverToActions.getOrDefault(receiver, mutableSetOf()).forEach {
             actionsToActionsReceivers.forEach { (key, value) ->
-                if (key.first == it) {
+                if (key.action == it) {
                     value.removeReceiver(receiver)
                 }
             }
@@ -160,9 +173,12 @@
 
     override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
         pw.indentIfPossible {
-            actionsToActionsReceivers.forEach { (actionAndFlags, actionReceiver) ->
-                println("(${actionAndFlags.first}: " +
-                        "${BroadcastDispatcherLogger.flagToString(actionAndFlags.second)}):")
+            actionsToActionsReceivers.forEach { (actionFlagsPerm, actionReceiver) ->
+                println(
+                    "(${actionFlagsPerm.action}: " +
+                        BroadcastDispatcherLogger.flagToString(actionFlagsPerm.flags) +
+                        if (actionFlagsPerm.permission == null) "):"
+                            else ":${actionFlagsPerm.permission}):")
                 actionReceiver.dump(fd, pw, args)
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index 9861392..b7c4009 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -31,6 +31,8 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
 import android.annotation.MainThread;
 import android.app.RemoteAction;
@@ -55,6 +57,7 @@
 import android.text.TextUtils;
 import android.util.DisplayMetrics;
 import android.util.Log;
+import android.util.MathUtils;
 import android.util.Size;
 import android.view.Display;
 import android.view.DisplayCutout;
@@ -68,6 +71,8 @@
 import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
+import android.view.animation.LinearInterpolator;
+import android.view.animation.PathInterpolator;
 import android.view.textclassifier.TextClassification;
 import android.view.textclassifier.TextClassificationManager;
 import android.view.textclassifier.TextClassifier;
@@ -80,6 +85,8 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.policy.PhoneWindow;
 import com.android.systemui.R;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.broadcast.BroadcastSender;
 import com.android.systemui.screenshot.DraggableConstraintLayout;
 import com.android.systemui.screenshot.FloatingWindowUtil;
 import com.android.systemui.screenshot.OverlayActionChip;
@@ -105,6 +112,7 @@
 
     private final Context mContext;
     private final UiEventLogger mUiEventLogger;
+    private final BroadcastDispatcher mBroadcastDispatcher;
     private final DisplayManager mDisplayManager;
     private final DisplayMetrics mDisplayMetrics;
     private final WindowManager mWindowManager;
@@ -116,6 +124,7 @@
 
     private final FrameLayout mContainer;
     private final DraggableConstraintLayout mView;
+    private final View mClipboardPreview;
     private final ImageView mImagePreview;
     private final TextView mTextPreview;
     private final View mPreviewBorder;
@@ -137,8 +146,11 @@
 
     private boolean mBlockAttach = false;
 
-    public ClipboardOverlayController(
-            Context context, TimeoutHandler timeoutHandler, UiEventLogger uiEventLogger) {
+    public ClipboardOverlayController(Context context,
+            BroadcastDispatcher broadcastDispatcher,
+            BroadcastSender broadcastSender,
+            TimeoutHandler timeoutHandler, UiEventLogger uiEventLogger) {
+        mBroadcastDispatcher = broadcastDispatcher;
         mDisplayManager = requireNonNull(context.getSystemService(DisplayManager.class));
         final Context displayContext = context.createDisplayContext(getDefaultDisplay());
         mContext = displayContext.createWindowContext(TYPE_SCREENSHOT, null);
@@ -171,6 +183,7 @@
         mActionContainerBackground =
                 requireNonNull(mView.findViewById(R.id.actions_container_background));
         mActionContainer = requireNonNull(mView.findViewById(R.id.actions));
+        mClipboardPreview = requireNonNull(mView.findViewById(R.id.clipboard_preview));
         mImagePreview = requireNonNull(mView.findViewById(R.id.image_preview));
         mTextPreview = requireNonNull(mView.findViewById(R.id.text_preview));
         mPreviewBorder = requireNonNull(mView.findViewById(R.id.preview_border));
@@ -191,7 +204,7 @@
                     @Override
                     public void onAnimationStart(Animator animation) {
                         super.onAnimationStart(animation);
-                        mContainer.animate().alpha(0).start();
+                        mContainer.animate().alpha(0).setDuration(animation.getDuration()).start();
                     }
                 });
             }
@@ -215,17 +228,6 @@
         mRemoteCopyChip.setIcon(
                 Icon.createWithResource(mContext, R.drawable.ic_baseline_devices_24), true);
 
-        // Only show remote copy if it's available.
-        PackageManager packageManager = mContext.getPackageManager();
-        if (packageManager.resolveActivity(getRemoteCopyIntent(), 0) != null) {
-            mRemoteCopyChip.setOnClickListener((v) -> {
-                showNearby();
-            });
-            mRemoteCopyChip.setAlpha(1f);
-        } else {
-            mRemoteCopyChip.setVisibility(View.GONE);
-        }
-
         attachWindow();
         withWindowAttached(() -> {
             mWindow.setContentView(mContainer);
@@ -247,9 +249,9 @@
                 }
             }
         };
-        mContext.registerReceiver(mCloseDialogsReceiver,
-                new IntentFilter(ACTION_CLOSE_SYSTEM_DIALOGS));
 
+        mBroadcastDispatcher.registerReceiver(mCloseDialogsReceiver,
+                new IntentFilter(ACTION_CLOSE_SYSTEM_DIALOGS));
         mScreenshotReceiver = new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
@@ -258,14 +260,16 @@
                 }
             }
         };
-        mContext.registerReceiver(mScreenshotReceiver, new IntentFilter(SCREENSHOT_ACTION),
-                SELF_PERMISSION, null);
+
+        mBroadcastDispatcher.registerReceiver(mScreenshotReceiver,
+                new IntentFilter(SCREENSHOT_ACTION), null, null, Context.RECEIVER_EXPORTED,
+                SELF_PERMISSION);
         monitorOutsideTouches();
 
         Intent copyIntent = new Intent(COPY_OVERLAY_ACTION);
         // Set package name so the system knows it's safe
         copyIntent.setPackage(mContext.getPackageName());
-        mContext.sendBroadcast(copyIntent, SELF_PERMISSION);
+        broadcastSender.sendBroadcast(copyIntent, SELF_PERMISSION);
     }
 
     void setClipData(ClipData clipData, String clipSource) {
@@ -286,6 +290,21 @@
             showTextPreview(
                     mContext.getResources().getString(R.string.clipboard_overlay_text_copied));
         }
+        Intent remoteCopyIntent = getRemoteCopyIntent(clipData);
+        // Only show remote copy if it's available.
+        PackageManager packageManager = mContext.getPackageManager();
+        if (remoteCopyIntent != null && packageManager.resolveActivity(
+                remoteCopyIntent, PackageManager.ResolveInfoFlags.of(0)) != null) {
+            mRemoteCopyChip.setOnClickListener((v) -> {
+                mUiEventLogger.log(CLIPBOARD_OVERLAY_REMOTE_COPY_TAPPED);
+                mContext.startActivity(remoteCopyIntent);
+                animateOut();
+            });
+            mRemoteCopyChip.setAlpha(1f);
+            mActionContainerBackground.setVisibility(View.VISIBLE);
+        } else {
+            mRemoteCopyChip.setVisibility(View.GONE);
+        }
         mTimeoutHandler.resetTimeout();
     }
 
@@ -389,12 +408,6 @@
         animateOut();
     }
 
-    private void showNearby() {
-        mUiEventLogger.log(CLIPBOARD_OVERLAY_REMOTE_COPY_TAPPED);
-        mContext.startActivity(getRemoteCopyIntent());
-        animateOut();
-    }
-
     private void showTextPreview(CharSequence text) {
         mTextPreview.setVisibility(View.VISIBLE);
         mImagePreview.setVisibility(View.GONE);
@@ -405,6 +418,7 @@
     private void showEditableText(CharSequence text) {
         showTextPreview(text);
         mEditChip.setVisibility(View.VISIBLE);
+        mActionContainerBackground.setVisibility(View.VISIBLE);
         mEditChip.setAlpha(1f);
         mEditChip.setContentDescription(
                 mContext.getString(R.string.clipboard_edit_text_description));
@@ -417,6 +431,7 @@
         mTextPreview.setVisibility(View.GONE);
         mImagePreview.setVisibility(View.VISIBLE);
         mEditChip.setAlpha(1f);
+        mActionContainerBackground.setVisibility(View.VISIBLE);
         ContentResolver resolver = mContext.getContentResolver();
         try {
             int size = mContext.getResources().getDimensionPixelSize(R.dimen.overlay_x_scale);
@@ -434,43 +449,89 @@
         mImagePreview.setOnClickListener(listener);
     }
 
-    private Intent getRemoteCopyIntent() {
+    private Intent getRemoteCopyIntent(ClipData clipData) {
+        String remoteCopyPackage = mContext.getString(R.string.config_remoteCopyPackage);
+        if (TextUtils.isEmpty(remoteCopyPackage)) {
+            return null;
+        }
         Intent nearbyIntent = new Intent(REMOTE_COPY_ACTION);
+        nearbyIntent.setComponent(ComponentName.unflattenFromString(remoteCopyPackage));
+        nearbyIntent.setClipData(clipData);
+        nearbyIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
         nearbyIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
         return nearbyIntent;
     }
 
     private void animateIn() {
+        if (mAccessibilityManager.isEnabled()) {
+            mDismissButton.setVisibility(View.VISIBLE);
+        }
         getEnterAnimation().start();
     }
 
     private void animateOut() {
-        mView.dismiss();
+        Animator anim = getExitAnimation();
+        anim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                super.onAnimationEnd(animation);
+                hideImmediate();
+            }
+        });
+        anim.start();
     }
 
-    private ValueAnimator getEnterAnimation() {
-        ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
+    private Animator getEnterAnimation() {
+        TimeInterpolator linearInterpolator = new LinearInterpolator();
+        TimeInterpolator scaleInterpolator = new PathInterpolator(0, 0, 0, 1f);
+        AnimatorSet enterAnim = new AnimatorSet();
 
-        mContainer.setAlpha(0);
-        mDismissButton.setVisibility(View.GONE);
-        final View previewBorder = requireNonNull(mView.findViewById(R.id.preview_border));
-        final View actionBackground = requireNonNull(
-                mView.findViewById(R.id.actions_container_background));
-        mImagePreview.setVisibility(View.VISIBLE);
-        mActionContainerBackground.setVisibility(View.VISIBLE);
-        if (mAccessibilityManager.isEnabled()) {
-            mDismissButton.setVisibility(View.VISIBLE);
-        }
-
-        anim.addUpdateListener(animation -> {
+        ValueAnimator rootAnim = ValueAnimator.ofFloat(0, 1);
+        rootAnim.setInterpolator(linearInterpolator);
+        rootAnim.setDuration(66);
+        rootAnim.addUpdateListener(animation -> {
             mContainer.setAlpha(animation.getAnimatedFraction());
-            float scale = 0.6f + 0.4f * animation.getAnimatedFraction();
-            mView.setPivotY(mView.getHeight() - previewBorder.getHeight() / 2f);
-            mView.setPivotX(actionBackground.getWidth() / 2f);
-            mView.setScaleX(scale);
-            mView.setScaleY(scale);
         });
-        anim.addListener(new AnimatorListenerAdapter() {
+
+        ValueAnimator scaleAnim = ValueAnimator.ofFloat(0, 1);
+        scaleAnim.setInterpolator(scaleInterpolator);
+        scaleAnim.setDuration(333);
+        scaleAnim.addUpdateListener(animation -> {
+            float previewScale = MathUtils.lerp(.9f, 1f, animation.getAnimatedFraction());
+            mClipboardPreview.setScaleX(previewScale);
+            mClipboardPreview.setScaleY(previewScale);
+            mPreviewBorder.setScaleX(previewScale);
+            mPreviewBorder.setScaleY(previewScale);
+
+            float pivotX = mClipboardPreview.getWidth() / 2f + mClipboardPreview.getX();
+            mActionContainerBackground.setPivotX(pivotX - mActionContainerBackground.getX());
+            mActionContainer.setPivotX(pivotX - ((View) mActionContainer.getParent()).getX());
+            float actionsScaleX = MathUtils.lerp(.7f, 1f, animation.getAnimatedFraction());
+            float actionsScaleY = MathUtils.lerp(.9f, 1f, animation.getAnimatedFraction());
+            mActionContainer.setScaleX(actionsScaleX);
+            mActionContainer.setScaleY(actionsScaleY);
+            mActionContainerBackground.setScaleX(actionsScaleX);
+            mActionContainerBackground.setScaleY(actionsScaleY);
+        });
+
+        ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
+        alphaAnim.setInterpolator(linearInterpolator);
+        alphaAnim.setDuration(283);
+        alphaAnim.addUpdateListener(animation -> {
+            float alpha = animation.getAnimatedFraction();
+            mClipboardPreview.setAlpha(alpha);
+            mPreviewBorder.setAlpha(alpha);
+            mDismissButton.setAlpha(alpha);
+            mActionContainer.setAlpha(alpha);
+        });
+
+        mActionContainer.setAlpha(0);
+        mPreviewBorder.setAlpha(0);
+        mClipboardPreview.setAlpha(0);
+        enterAnim.play(rootAnim).with(scaleAnim);
+        enterAnim.play(alphaAnim).after(50).after(rootAnim);
+
+        enterAnim.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
                 super.onAnimationEnd(animation);
@@ -478,7 +539,56 @@
                 mTimeoutHandler.resetTimeout();
             }
         });
-        return anim;
+        return enterAnim;
+    }
+
+    private Animator getExitAnimation() {
+        TimeInterpolator linearInterpolator = new LinearInterpolator();
+        TimeInterpolator scaleInterpolator = new PathInterpolator(.3f, 0, 1f, 1f);
+        AnimatorSet exitAnim = new AnimatorSet();
+
+        ValueAnimator rootAnim = ValueAnimator.ofFloat(0, 1);
+        rootAnim.setInterpolator(linearInterpolator);
+        rootAnim.setDuration(100);
+        rootAnim.addUpdateListener(animation -> {
+            mContainer.setAlpha(1 - animation.getAnimatedFraction());
+        });
+
+        ValueAnimator scaleAnim = ValueAnimator.ofFloat(0, 1);
+        scaleAnim.setInterpolator(scaleInterpolator);
+        scaleAnim.setDuration(250);
+        scaleAnim.addUpdateListener(animation -> {
+            float previewScale = MathUtils.lerp(1f, .9f, animation.getAnimatedFraction());
+            mClipboardPreview.setScaleX(previewScale);
+            mClipboardPreview.setScaleY(previewScale);
+            mPreviewBorder.setScaleX(previewScale);
+            mPreviewBorder.setScaleY(previewScale);
+
+            float pivotX = mClipboardPreview.getWidth() / 2f + mClipboardPreview.getX();
+            mActionContainerBackground.setPivotX(pivotX - mActionContainerBackground.getX());
+            mActionContainer.setPivotX(pivotX - ((View) mActionContainer.getParent()).getX());
+            float actionScaleX = MathUtils.lerp(1f, .8f, animation.getAnimatedFraction());
+            float actionScaleY = MathUtils.lerp(1f, .9f, animation.getAnimatedFraction());
+            mActionContainer.setScaleX(actionScaleX);
+            mActionContainer.setScaleY(actionScaleY);
+            mActionContainerBackground.setScaleX(actionScaleX);
+            mActionContainerBackground.setScaleY(actionScaleY);
+        });
+
+        ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
+        alphaAnim.setInterpolator(linearInterpolator);
+        alphaAnim.setDuration(166);
+        alphaAnim.addUpdateListener(animation -> {
+            float alpha = 1 - animation.getAnimatedFraction();
+            mClipboardPreview.setAlpha(alpha);
+            mPreviewBorder.setAlpha(alpha);
+            mDismissButton.setAlpha(alpha);
+            mActionContainer.setAlpha(alpha);
+        });
+
+        exitAnim.play(alphaAnim).with(scaleAnim);
+        exitAnim.play(rootAnim).after(150).after(alphaAnim);
+        return exitAnim;
     }
 
     private void hideImmediate() {
@@ -490,11 +600,11 @@
             mWindowManager.removeViewImmediate(decorView);
         }
         if (mCloseDialogsReceiver != null) {
-            mContext.unregisterReceiver(mCloseDialogsReceiver);
+            mBroadcastDispatcher.unregisterReceiver(mCloseDialogsReceiver);
             mCloseDialogsReceiver = null;
         }
         if (mScreenshotReceiver != null) {
-            mContext.unregisterReceiver(mScreenshotReceiver);
+            mBroadcastDispatcher.unregisterReceiver(mScreenshotReceiver);
             mScreenshotReceiver = null;
         }
         if (mInputEventReceiver != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerFactory.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerFactory.java
index 275d295..8b0b2a5 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerFactory.java
@@ -19,6 +19,8 @@
 import android.content.Context;
 
 import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.broadcast.BroadcastSender;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.screenshot.TimeoutHandler;
 
@@ -29,17 +31,24 @@
  */
 @SysUISingleton
 public class ClipboardOverlayControllerFactory {
+
     private final UiEventLogger mUiEventLogger;
+    private final BroadcastDispatcher mBroadcastDispatcher;
+    private final BroadcastSender mBroadcastSender;
 
     @Inject
-    public ClipboardOverlayControllerFactory(UiEventLogger uiEventLogger) {
-        mUiEventLogger = uiEventLogger;
+    public ClipboardOverlayControllerFactory(BroadcastDispatcher broadcastDispatcher,
+            BroadcastSender broadcastSender, UiEventLogger uiEventLogger) {
+        this.mBroadcastDispatcher = broadcastDispatcher;
+        this.mBroadcastSender = broadcastSender;
+        this.mUiEventLogger = uiEventLogger;
     }
 
     /**
      * One new ClipboardOverlayController, coming right up!
      */
     public ClipboardOverlayController create(Context context) {
-        return new ClipboardOverlayController(context, new TimeoutHandler(context), mUiEventLogger);
+        return new ClipboardOverlayController(context, mBroadcastDispatcher, mBroadcastSender,
+                new TimeoutHandler(context), mUiEventLogger);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
index 041de05..fa23842 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
@@ -36,7 +36,6 @@
 import android.view.LayoutInflater;
 
 import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
 import com.android.internal.util.NotificationMessagingUtil;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardUpdateMonitor;
@@ -50,7 +49,6 @@
 import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger;
-import com.android.systemui.clipboardoverlay.ClipboardOverlayControllerFactory;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.doze.AlwaysOnDisplayPolicy;
@@ -293,12 +291,4 @@
     public ModeSwitchesController providesModeSwitchesController(Context context) {
         return new ModeSwitchesController(context);
     }
-
-    /***/
-    @Provides
-    @SysUISingleton
-    public ClipboardOverlayControllerFactory provideClipboardOverlayControllerFactory(
-            UiEventLogger uiEventLogger) {
-        return new ClipboardOverlayControllerFactory(uiEventLogger);
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 6d727b4..b172e92 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -65,6 +65,7 @@
 import android.service.dreams.DreamService;
 import android.service.dreams.IDreamManager;
 import android.telecom.TelecomManager;
+import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.view.CrossWindowBlurListeners;
@@ -452,6 +453,12 @@
 
     @Provides
     @Singleton
+    static CarrierConfigManager provideCarrierConfigManager(Context context) {
+        return context.getSystemService(CarrierConfigManager.class);
+    }
+
+    @Provides
+    @Singleton
     static WindowManager provideWindowManager(Context context) {
         return context.getSystemService(WindowManager.class);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index 8e1d645..f05fc2b 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -396,8 +396,8 @@
         }
 
         @Override
-        public void onKeyguardBouncerChanged(boolean bouncer) {
-            traceKeyguardBouncerChanged(bouncer);
+        public void onKeyguardBouncerFullyShowingChanged(boolean fullyShowing) {
+            traceKeyguardBouncerChanged(fullyShowing);
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
index dfb27ef..0024a46 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
@@ -147,6 +147,7 @@
                 setLightSensorEnabled(true);
                 break;
             case DOZE:
+            case DOZE_AOD_PAUSED:
                 setLightSensorEnabled(false);
                 resetBrightnessToDefault();
                 break;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index 4561190..fb1af8b 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -16,14 +16,21 @@
 
 package com.android.systemui.dreams;
 
+import static com.android.keyguard.BouncerPanelExpansionCalculator.getBackScrimScaledExpansion;
+import static com.android.keyguard.BouncerPanelExpansionCalculator.getDreamAlphaScaledExpansion;
+import static com.android.keyguard.BouncerPanelExpansionCalculator.getDreamYPositionScaledExpansion;
 import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
+import static com.android.systemui.dreams.complication.ComplicationLayoutParams.POSITION_BOTTOM;
+import static com.android.systemui.dreams.complication.ComplicationLayoutParams.POSITION_TOP;
 
+import android.content.res.Resources;
 import android.os.Handler;
 import android.util.MathUtils;
 import android.view.View;
 import android.view.ViewGroup;
 
-import com.android.keyguard.BouncerPanelExpansionCalculator;
+import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dreams.complication.ComplicationHostViewController;
 import com.android.systemui.dreams.dagger.DreamOverlayComponent;
@@ -33,6 +40,8 @@
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.util.ViewController;
 
+import java.util.Arrays;
+
 import javax.inject.Inject;
 import javax.inject.Named;
 
@@ -63,6 +72,7 @@
 
     // Main thread handler used to schedule periodic tasks (e.g. burn-in protection updates).
     private final Handler mHandler;
+    private final int mDreamOverlayMaxTranslationY;
 
     private long mJitterStartTimeMillis;
 
@@ -93,13 +103,9 @@
 
                 @Override
                 public void onExpansionChanged(float bouncerHideAmount) {
-                    if (!mBouncerAnimating) return;
-                    final float scaledFraction =
-                            BouncerPanelExpansionCalculator.getBackScrimScaledExpansion(
-                                    bouncerHideAmount);
-                    final int blurRadius =
-                            (int) mBlurUtils.blurRadiusOfRatio(1 - scaledFraction);
-                    updateTransitionState(blurRadius, scaledFraction);
+                    if (mBouncerAnimating) {
+                        updateTransitionState(bouncerHideAmount);
+                    }
                 }
 
                 @Override
@@ -107,7 +113,7 @@
                     // The bouncer may be hidden abruptly without triggering onExpansionChanged.
                     // In this case, we should reset the transition state.
                     if (!isVisible) {
-                        updateTransitionState(0, 1f);
+                        updateTransitionState(1f);
                     }
                 }
             };
@@ -121,6 +127,7 @@
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             BlurUtils blurUtils,
             @Main Handler handler,
+            @Main Resources resources,
             @Named(DreamOverlayModule.MAX_BURN_IN_OFFSET) int maxBurnInOffset,
             @Named(DreamOverlayModule.BURN_IN_PROTECTION_UPDATE_INTERVAL) long
                     burnInProtectionUpdateInterval,
@@ -132,6 +139,8 @@
         mBlurUtils = blurUtils;
 
         mComplicationHostViewController = complicationHostViewController;
+        mDreamOverlayMaxTranslationY = resources.getDimensionPixelSize(
+                R.dimen.dream_overlay_y_offset);
         final View view = mComplicationHostViewController.getView();
 
         mDreamOverlayContentView.addView(view,
@@ -196,8 +205,31 @@
         mHandler.postDelayed(this::updateBurnInOffsets, mBurnInProtectionUpdateInterval);
     }
 
-    private void updateTransitionState(int blurRadiusPixels, float alpha) {
-        mBlurUtils.applyBlur(mView.getViewRootImpl(), blurRadiusPixels, false);
-        mView.setAlpha(alpha);
+    private void updateTransitionState(float bouncerHideAmount) {
+        for (int position : Arrays.asList(POSITION_TOP, POSITION_BOTTOM)) {
+            final float alpha = getAlpha(position, bouncerHideAmount);
+            final float translationY = getTranslationY(position, bouncerHideAmount);
+            mComplicationHostViewController.getViewsAtPosition(position).forEach(v -> {
+                v.setAlpha(alpha);
+                v.setTranslationY(translationY);
+            });
+        }
+
+        mBlurUtils.applyBlur(mView.getViewRootImpl(),
+                (int) mBlurUtils.blurRadiusOfRatio(
+                        1 - getBackScrimScaledExpansion(bouncerHideAmount)), false);
+    }
+
+    private static float getAlpha(int position, float expansion) {
+        return Interpolators.LINEAR_OUT_SLOW_IN.getInterpolation(
+                position == POSITION_TOP ? getDreamAlphaScaledExpansion(expansion)
+                        : getBackScrimScaledExpansion(expansion + 0.03f));
+    }
+
+    private float getTranslationY(int position, float expansion) {
+        final float fraction = Interpolators.LINEAR_OUT_SLOW_IN.getInterpolation(
+                position == POSITION_TOP ? getDreamYPositionScaledExpansion(expansion)
+                        : getBackScrimScaledExpansion(expansion + 0.03f));
+        return MathUtils.lerp(-mDreamOverlayMaxTranslationY, 0, fraction);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
index 4e528bb..4c0154f 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
@@ -31,6 +31,7 @@
 
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.List;
 import java.util.stream.Collectors;
 
 import javax.inject.Inject;
@@ -144,4 +145,11 @@
     public View getView() {
         return mView;
     }
+
+    /**
+     * Gets an unordered list of all the views at a particular position.
+     */
+    public List<View> getViewsAtPosition(@ComplicationLayoutParams.Position int position) {
+        return mLayoutEngine.getViewsAtPosition(position);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
index 4065a25..ded61a8 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
@@ -32,6 +32,7 @@
 import androidx.constraintlayout.widget.Constraints;
 
 import com.android.systemui.R;
+import com.android.systemui.dreams.complication.ComplicationLayoutParams.Position;
 import com.android.systemui.dreams.dagger.DreamOverlayComponent;
 import com.android.systemui.touch.TouchInsetManager;
 
@@ -39,7 +40,9 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.List;
 import java.util.function.Consumer;
+import java.util.stream.Collectors;
 
 import javax.inject.Inject;
 import javax.inject.Named;
@@ -378,6 +381,14 @@
                 directionGroup.updateViews(head.getView());
             }
         }
+
+        private ArrayList<ViewEntry> getViews() {
+            final ArrayList<ViewEntry> views = new ArrayList<>();
+            for (DirectionGroup directionGroup : mDirectionGroups.values()) {
+                views.addAll(directionGroup.getViews());
+            }
+            return views;
+        }
     }
 
     /**
@@ -454,6 +465,10 @@
                 groupHead = viewEntry.getView();
             }
         }
+
+        private List<ViewEntry> getViews() {
+            return mViews;
+        }
     }
 
     private final ConstraintLayout mLayout;
@@ -551,4 +566,15 @@
         entry.remove();
         return true;
     }
+
+    /**
+     * Gets an unordered list of all the views at a particular position.
+     */
+    public List<View> getViewsAtPosition(@Position int position) {
+        return mPositions.entrySet().stream()
+                .filter(entry -> (entry.getKey() & position) == position)
+                .flatMap(entry -> entry.getValue().getViews().stream())
+                .map(ViewEntry::getView)
+                .collect(Collectors.toList());
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
index 5c99cd1..990f04b 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
@@ -117,15 +117,23 @@
                         return false;
                     }
 
+                    // Don't set expansion for downward scroll when the bouncer is hidden.
+                    if (!mBouncerInitiallyShowing && (e1.getY() < e2.getY())) {
+                        return true;
+                    }
+
+                    // Don't set expansion for upward scroll when the bouncer is shown.
+                    if (mBouncerInitiallyShowing && (e1.getY() > e2.getY())) {
+                        return true;
+                    }
+
                     // For consistency, we adopt the expansion definition found in the
                     // PanelViewController. In this case, expansion refers to the view above the
                     // bouncer. As that view's expansion shrinks, the bouncer appears. The bouncer
                     // is fully hidden at full expansion (1) and fully visible when fully collapsed
                     // (0).
-                    final float dy = mBouncerInitiallyShowing ? e2.getY() - e1.getY()
-                            : e1.getY() - e2.getY();
-                    final float screenTravelPercentage = Math.max(0,
-                            dy / mCentralSurfaces.getDisplayHeight());
+                    final float screenTravelPercentage = Math.abs(e1.getY() - e2.getY())
+                            / mCentralSurfaces.getDisplayHeight();
                     setPanelExpansion(mBouncerInitiallyShowing
                             ? screenTravelPercentage : 1 - screenTravelPercentage);
                     return true;
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpsysTableLogger.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpsysTableLogger.kt
new file mode 100644
index 0000000..f7e6b98
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpsysTableLogger.kt
@@ -0,0 +1,125 @@
+/*
+ * 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.dump
+
+import java.io.PrintWriter
+
+/**
+ * Utility for logging nice table data to be parsed (and pretty printed) in bugreports. The general
+ * idea here is to feed your nice, table-like data to this class, which embeds the schema and rows
+ * into the dumpsys, wrapped in a known start and stop tags. Later, one can build a simple parser
+ * and pretty-print this data in a table
+ *
+ * Note: Something should be said here about silently eating errors by filtering out malformed
+ * lines. Because this class is expected to be utilized only during a dumpsys, it doesn't feel
+ * most correct to throw an exception here (since an exception can often be the reason that this
+ * class is created). Because of this, [DumpsysTableLogger] will simply filter out invalid lines
+ * based solely on line length. This behavior might need to be revisited in the future.
+ *
+ * USAGE:
+ * Assuming we have some data that would be logged to dumpsys like so:
+ *
+ * ```
+ *      1: field1=val1, field2=val2..., fieldN=valN
+ *      //...
+ *      M: field1M=val1M, ..., fieldNM
+ * ```
+ *
+ * You can break the `field<n>` values out into a columns spec:
+ * ```
+ *      val cols = [field1, field2,...,fieldN]
+ * ```
+ * And then take all of the historical data lines (1 through M), and break them out into their own
+ * lists:
+ * ```
+ *      val rows = [
+ *          [field10, field20,..., fieldN0],
+ *          //...
+ *          [field1M, field2M,..., fieldNM]
+ *      ]
+ * ```
+ *
+ * Lastly, create a bugreport-unique section name, and use the table logger to write the data to
+ * dumpsys:
+ * ```
+ *      val logger = DumpsysTableLogger(uniqueName, cols, rows)
+ *      logger.printTableData(pw)
+ * ```
+ *
+ * The expected output in the dumpsys would be:
+ * ```
+ *      SystemUI TableSection START: <SectionName>
+ *      version 1
+ *      col1|col2|...|colN
+ *      field10|field20|...|fieldN0
+ *      //...
+ *      field1M|field2M|...|fieldNM
+ *      SystemUI TableSection END: <SectionName>
+ * ```
+ *
+ *  @param sectionName A name for the table data section. Should be unique in the bugreport
+ *  @param columns Definition for the columns of the table. This should be the same length as all
+ *      data rows
+ *  @param rows List of rows to be displayed in the table
+ */
+class DumpsysTableLogger(
+    private val sectionName: String,
+    private val columns: List<String>,
+    private val rows: List<Row>
+) {
+
+    fun printTableData(pw: PrintWriter) {
+        printSectionStart(pw)
+        printSchema(pw)
+        printData(pw)
+        printSectionEnd(pw)
+    }
+
+    private fun printSectionStart(pw: PrintWriter) {
+        pw.println(HEADER_PREFIX + sectionName)
+        pw.println("version $VERSION")
+    }
+
+    private fun printSectionEnd(pw: PrintWriter) {
+        pw.println(FOOTER_PREFIX + sectionName)
+    }
+
+    private fun printSchema(pw: PrintWriter) {
+        pw.println(columns.joinToString(separator = SEPARATOR))
+    }
+
+    private fun printData(pw: PrintWriter) {
+        val count = columns.size
+        rows
+            .filter { it.size == count }
+            .forEach { dataLine ->
+                pw.println(dataLine.joinToString(separator = SEPARATOR))
+        }
+    }
+}
+
+typealias Row = List<String>
+
+/**
+ * DO NOT CHANGE! (but if you must...)
+ *  1. Update the version number
+ *  2. Update any consumers to parse the new version
+ */
+private const val HEADER_PREFIX = "SystemUI TableSection START: "
+private const val FOOTER_PREFIX = "SystemUI TableSection END: "
+private const val SEPARATOR = "|" // TBD
+private const val VERSION = "1"
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 9356b16..44580aa 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -112,6 +112,10 @@
     public static final ResourceBooleanFlag QS_USER_DETAIL_SHORTCUT =
             new ResourceBooleanFlag(503, R.bool.flag_lockscreen_qs_user_detail_shortcut);
 
+    /**
+     * @deprecated Not needed anymore
+     */
+    @Deprecated
     public static final BooleanFlag NEW_FOOTER = new BooleanFlag(504, true);
 
     public static final BooleanFlag NEW_HEADER = new BooleanFlag(505, false);
@@ -158,6 +162,17 @@
     public static final SysPropBooleanFlag WM_ENABLE_SHELL_TRANSITIONS =
             new SysPropBooleanFlag(1100, "persist.wm.debug.shell_transit", false);
 
+    // 1200 - predictive back
+    @Keep
+    public static final SysPropBooleanFlag WM_ENABLE_PREDICTIVE_BACK = new SysPropBooleanFlag(
+            1200, "persist.wm.debug.predictive_back", true);
+    @Keep
+    public static final SysPropBooleanFlag WM_ENABLE_PREDICTIVE_BACK_ANIM = new SysPropBooleanFlag(
+            1201, "persist.wm.debug.predictive_back_anim", false);
+    @Keep
+    public static final SysPropBooleanFlag WM_ALWAYS_ENFORCE_PREDICTIVE_BACK =
+            new SysPropBooleanFlag(1202, "persist.wm.debug.predictive_back_always_enforce", false);
+
     // Pay no attention to the reflection behind the curtain.
     // ========================== Curtain ==========================
     // |                                                           |
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
index 96ae646..290bf0d 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
@@ -41,26 +41,23 @@
 
 import javax.inject.Inject;
 
-import dagger.Lazy;
-
 public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks {
 
     private final Context mContext;
-    private final Lazy<GlobalActionsDialogLite> mGlobalActionsDialogLazy;
     private final KeyguardStateController mKeyguardStateController;
     private final DeviceProvisionedController mDeviceProvisionedController;
     private final BlurUtils mBlurUtils;
     private final CommandQueue mCommandQueue;
-    private GlobalActionsDialogLite mGlobalActionsDialog;
+    private final GlobalActionsDialogLite mGlobalActionsDialog;
     private boolean mDisabled;
 
     @Inject
     public GlobalActionsImpl(Context context, CommandQueue commandQueue,
-            Lazy<GlobalActionsDialogLite> globalActionsDialogLazy, BlurUtils blurUtils,
+            GlobalActionsDialogLite globalActionsDialog, BlurUtils blurUtils,
             KeyguardStateController keyguardStateController,
             DeviceProvisionedController deviceProvisionedController) {
         mContext = context;
-        mGlobalActionsDialogLazy = globalActionsDialogLazy;
+        mGlobalActionsDialog = globalActionsDialog;
         mKeyguardStateController = keyguardStateController;
         mDeviceProvisionedController = deviceProvisionedController;
         mCommandQueue = commandQueue;
@@ -71,16 +68,12 @@
     @Override
     public void destroy() {
         mCommandQueue.removeCallback(this);
-        if (mGlobalActionsDialog != null) {
-            mGlobalActionsDialog.destroy();
-            mGlobalActionsDialog = null;
-        }
+        mGlobalActionsDialog.destroy();
     }
 
     @Override
     public void showGlobalActions(GlobalActionsManager manager) {
         if (mDisabled) return;
-        mGlobalActionsDialog = mGlobalActionsDialogLazy.get();
         mGlobalActionsDialog.showOrHideDialog(mKeyguardStateController.isShowing(),
                 mDeviceProvisionedController.isDeviceProvisioned(), null /* view */);
     }
@@ -189,7 +182,7 @@
         final boolean disabled = (state2 & DISABLE2_GLOBAL_ACTIONS) != 0;
         if (displayId != mContext.getDisplayId() || disabled == mDisabled) return;
         mDisabled = disabled;
-        if (disabled && mGlobalActionsDialog != null) {
+        if (disabled) {
             mGlobalActionsDialog.dismissDialog();
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 890ddf0..4776317 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -318,6 +318,8 @@
     // true if the keyguard is hidden by another window
     private boolean mOccluded = false;
 
+    private boolean mWakeAndUnlocking = false;
+
     /**
      * Helps remember whether the screen has turned on since the last time
      * it turned off due to timeout. see {@link #onScreenTurnedOff(int)}
@@ -452,7 +454,6 @@
     private boolean mLockLater;
     private boolean mShowHomeOverLockscreen;
     private boolean mInGestureNavigationMode;
-
     private CharSequence mCustomMessage;
 
     /**
@@ -1218,7 +1219,7 @@
                 doKeyguardLaterLocked(timeout);
                 mLockLater = true;
             } else if (!mLockPatternUtils.isLockScreenDisabled(currentUser)) {
-                mPendingLock = true;
+                setPendingLock(true);
             }
 
             if (mPendingLock) {
@@ -1248,7 +1249,7 @@
         synchronized (this) {
             mDeviceInteractive = false;
             mGoingToSleep = false;
-            mScreenOnCoordinator.setWakeAndUnlocking(false);
+            mWakeAndUnlocking = false;
             mAnimatingScreenOff = mDozeParameters.shouldAnimateDozingChange();
 
             resetKeyguardDonePendingLocked();
@@ -1262,7 +1263,7 @@
                 mContext.getSystemService(PowerManager.class).wakeUp(SystemClock.uptimeMillis(),
                         PowerManager.WAKE_REASON_CAMERA_LAUNCH,
                         "com.android.systemui:CAMERA_GESTURE_PREVENT_LOCK");
-                mPendingLock = false;
+                setPendingLock(false);
                 mPendingReset = false;
             }
 
@@ -1332,7 +1333,7 @@
             }
 
             doKeyguardLocked(null);
-            mPendingLock = false;
+            setPendingLock(false);
         }
     }
 
@@ -2133,6 +2134,7 @@
             Log.i(TAG, "Device is going to sleep, aborting keyguardDone");
             return;
         }
+        setPendingLock(false); // user may have authenticated during the screen off animation
         if (mExitSecureCallback != null) {
             try {
                 mExitSecureCallback.onKeyguardExitResult(true /* authenciated */);
@@ -2262,8 +2264,8 @@
 
             mHiding = false;
             mKeyguardExitAnimationRunner = null;
-            mScreenOnCoordinator.setWakeAndUnlocking(false);
-            mPendingLock = false;
+            mWakeAndUnlocking = false;
+            setPendingLock(false);
             setShowingLocked(true);
             mKeyguardViewControllerLazy.get().show(options);
             resetKeyguardDonePendingLocked();
@@ -2294,14 +2296,12 @@
 
             int flags = 0;
             if (mKeyguardViewControllerLazy.get().shouldDisableWindowAnimationsForUnlock()
-                    || mScreenOnCoordinator.getWakeAndUnlocking()
-                            && !mWallpaperSupportsAmbientMode) {
+                    || mWakeAndUnlocking && !mWallpaperSupportsAmbientMode) {
                 flags |= WindowManagerPolicyConstants
                         .KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS;
             }
             if (mKeyguardViewControllerLazy.get().isGoingToNotificationShade()
-                    || mScreenOnCoordinator.getWakeAndUnlocking()
-                            && mWallpaperSupportsAmbientMode) {
+                    || mWakeAndUnlocking && mWallpaperSupportsAmbientMode) {
                 // When the wallpaper supports ambient mode, the scrim isn't fully opaque during
                 // wake and unlock, and we should fade in the app on top of the wallpaper
                 flags |= WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE;
@@ -2414,16 +2414,6 @@
             IRemoteAnimationRunner runner = mKeyguardExitAnimationRunner;
             mKeyguardExitAnimationRunner = null;
 
-            if (mScreenOnCoordinator.getWakeAndUnlocking()) {
-
-                // Hack level over 9000: To speed up wake-and-unlock sequence, force it to report
-                // the next draw from here, so we don't have to wait for window manager to signal
-                // this to our ViewRootImpl.
-                mKeyguardViewControllerLazy.get().getViewRootImpl().setReportNextDraw(
-                        false /* syncBuffer */);
-                mScreenOnCoordinator.setWakeAndUnlocking(false);
-            }
-
             LatencyTracker.getInstance(mContext)
                     .onActionEnd(LatencyTracker.ACTION_LOCKSCREEN_UNLOCK);
 
@@ -2546,7 +2536,7 @@
         }
 
         setShowingLocked(false);
-        mScreenOnCoordinator.setWakeAndUnlocking(false);
+        mWakeAndUnlocking = false;
         mDismissCallbackRegistry.notifyDismissSucceeded();
         resetKeyguardDonePendingLocked();
         mHideAnimationRun = false;
@@ -2790,7 +2780,7 @@
 
     public void onWakeAndUnlocking() {
         Trace.beginSection("KeyguardViewMediator#onWakeAndUnlocking");
-        mScreenOnCoordinator.setWakeAndUnlocking(true);
+        mWakeAndUnlocking = true;
         keyguardDone();
         Trace.endSection();
     }
@@ -2926,7 +2916,7 @@
         pw.print("  mHideAnimationRun: "); pw.println(mHideAnimationRun);
         pw.print("  mPendingReset: "); pw.println(mPendingReset);
         pw.print("  mPendingLock: "); pw.println(mPendingLock);
-        pw.print("  wakeAndUnlocking: "); pw.println(mScreenOnCoordinator.getWakeAndUnlocking());
+        pw.print("  wakeAndUnlocking: "); pw.println(mWakeAndUnlocking);
     }
 
     /**
@@ -3000,7 +2990,7 @@
     }
 
     private void setShowingLocked(boolean showing, boolean forceCallbacks) {
-        final boolean aodShowing = mDozing && !mScreenOnCoordinator.getWakeAndUnlocking();
+        final boolean aodShowing = mDozing && !mWakeAndUnlocking;
         final boolean notifyDefaultDisplayCallbacks = showing != mShowing || forceCallbacks;
         final boolean updateActivityLockScreenState = showing != mShowing
                 || aodShowing != mAodShowing || forceCallbacks;
@@ -3051,6 +3041,11 @@
         }
     }
 
+    private void setPendingLock(boolean hasPendingLock) {
+        mPendingLock = hasPendingLock;
+        Trace.traceCounter(Trace.TRACE_TAG_APP, "pendingLock", mPendingLock ? 1 : 0);
+    }
+
     public void addStateMonitorCallback(IKeyguardStateCallback callback) {
         synchronized (this) {
             mKeyguardStateCallbacks.add(callback);
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index d4e2214..d674b2b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -217,17 +217,17 @@
                 oldKey: String?,
                 data: MediaData,
                 immediately: Boolean,
-                receivedSmartspaceCardLatency: Int
+                receivedSmartspaceCardLatency: Int,
+                isSsReactivated: Boolean
             ) {
-                if (addOrUpdatePlayer(key, oldKey, data)) {
+                if (addOrUpdatePlayer(key, oldKey, data, isSsReactivated)) {
                     // Log card received if a new resumable media card is added
                     MediaPlayerData.getMediaPlayer(key)?.let {
                         /* ktlint-disable max-line-length */
                         logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED
                                 it.mSmartspaceId,
                                 it.mUid,
-                                /* isRecommendationCard */ false,
-                                intArrayOf(
+                                surfaces = intArrayOf(
                                         SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE,
                                         SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN),
                                 rank = MediaPlayerData.getMediaPlayerIndex(key))
@@ -250,8 +250,7 @@
                             logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED
                                     it.mSmartspaceId,
                                     it.mUid,
-                                    /* isRecommendationCard */ false,
-                                    intArrayOf(
+                                    surfaces = intArrayOf(
                                             SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE,
                                             SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN),
                                     rank = index,
@@ -285,12 +284,17 @@
             override fun onSmartspaceMediaDataLoaded(
                 key: String,
                 data: SmartspaceMediaData,
-                shouldPrioritize: Boolean,
-                isSsReactivated: Boolean
+                shouldPrioritize: Boolean
             ) {
                 if (DEBUG) Log.d(TAG, "Loading Smartspace media update")
+                // Log the case where the hidden media carousel with the existed inactive resume
+                // media is shown by the Smartspace signal.
                 if (data.isActive) {
-                    if (isSsReactivated && shouldPrioritize) {
+                    val hasActivatedExistedResumeMedia =
+                            !mediaManager.hasActiveMedia() &&
+                                    mediaManager.hasAnyMedia() &&
+                                    shouldPrioritize
+                    if (hasActivatedExistedResumeMedia) {
                         // Log resume card received if resumable media card is reactivated and
                         // recommendation card is valid and ranked first
                         MediaPlayerData.players().forEachIndexed { index, it ->
@@ -302,8 +306,7 @@
                                 logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED
                                         it.mSmartspaceId,
                                         it.mUid,
-                                        /* isRecommendationCard */ false,
-                                        intArrayOf(
+                                        surfaces = intArrayOf(
                                                 SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE,
                                                 SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN),
                                         rank = index,
@@ -318,8 +321,7 @@
                         logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED
                                 it.mSmartspaceId,
                                 it.mUid,
-                                /* isRecommendationCard */ true,
-                                intArrayOf(
+                                surfaces = intArrayOf(
                                         SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE,
                                         SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN),
                                 rank = MediaPlayerData.getMediaPlayerIndex(key),
@@ -417,7 +419,12 @@
     }
 
     // Returns true if new player is added
-    private fun addOrUpdatePlayer(key: String, oldKey: String?, data: MediaData): Boolean {
+    private fun addOrUpdatePlayer(
+        key: String,
+        oldKey: String?,
+        data: MediaData,
+        isSsReactivated: Boolean
+    ): Boolean {
         MediaPlayerData.moveIfExists(oldKey, key)
         val existingPlayer = MediaPlayerData.getMediaPlayer(key)
         val curVisibleMediaKey = MediaPlayerData.playerKeys()
@@ -432,12 +439,12 @@
             newPlayer.mediaViewHolder?.player?.setLayoutParams(lp)
             newPlayer.bindPlayer(data, key)
             newPlayer.setListening(currentlyExpanded)
-            MediaPlayerData.addMediaPlayer(key, data, newPlayer, systemClock)
+            MediaPlayerData.addMediaPlayer(key, data, newPlayer, systemClock, isSsReactivated)
             updatePlayerToState(newPlayer, noAnimation = true)
             reorderAllPlayers(curVisibleMediaKey)
         } else {
             existingPlayer.bindPlayer(data, key)
-            MediaPlayerData.addMediaPlayer(key, data, existingPlayer, systemClock)
+            MediaPlayerData.addMediaPlayer(key, data, existingPlayer, systemClock, isSsReactivated)
             if (isReorderingAllowed || shouldScrollToActivePlayer) {
                 reorderAllPlayers(curVisibleMediaKey)
             } else {
@@ -531,8 +538,10 @@
                             it.targetId, it, MediaPlayerData.shouldPrioritizeSs)
                 }
             } else {
+                val isSsReactivated = MediaPlayerData.isSsReactivated(key)
                 removePlayer(key, dismissMediaData = false, dismissRecommendation = false)
-                addOrUpdatePlayer(key = key, oldKey = null, data = data)
+                addOrUpdatePlayer(
+                        key = key, oldKey = null, data = data, isSsReactivated = isSsReactivated)
             }
         }
     }
@@ -681,12 +690,18 @@
         startDelay: Long = 0
     ) {
         desiredHostState?.let {
+            if (this.desiredLocation != desiredLocation) {
+                // Only log an event when location changes
+                logger.logCarouselPosition(desiredLocation)
+            }
+
             // This is a hosting view, let's remeasure our players
             this.desiredLocation = desiredLocation
             this.desiredHostState = it
             currentlyExpanded = it.expansion > 0
 
-            val shouldCloseGuts = !currentlyExpanded && !mediaManager.hasActiveMedia() &&
+            val shouldCloseGuts = !currentlyExpanded &&
+                    !mediaManager.hasActiveMediaOrRecommendation() &&
                     desiredHostState.showsOnlyActiveMedia
 
             for (mediaPlayer in MediaPlayerData.players()) {
@@ -751,7 +766,6 @@
             val mediaControlPanel = MediaPlayerData.players().elementAt(visibleMediaIndex)
             val hasActiveMediaOrRecommendationCard =
                     MediaPlayerData.hasActiveMediaOrRecommendationCard()
-            val isRecommendationCard = mediaControlPanel.recommendationViewHolder != null
             if (!hasActiveMediaOrRecommendationCard && !qsExpanded) {
                 // Skip logging if on LS or QQS, and there is no active media card
                 return
@@ -759,7 +773,6 @@
             logSmartspaceCardReported(800, // SMARTSPACE_CARD_SEEN
                     mediaControlPanel.mSmartspaceId,
                     mediaControlPanel.mUid,
-                    isRecommendationCard,
                     intArrayOf(mediaControlPanel.surfaceForSmartspaceLogging))
             mediaControlPanel.mIsImpressed = true
         }
@@ -773,7 +786,6 @@
      * @param instanceId id to uniquely identify a card, e.g. each headphone generates a new
      * instanceId
      * @param uid uid for the application that media comes from
-     * @param isRecommendationCard whether the card is media recommendation
      * @param surfaces list of display surfaces the media card is on (e.g. lockscreen, shade) when
      * the event happened
      * @param interactedSubcardRank the rank for interacted media item for recommendation card, -1
@@ -783,21 +795,27 @@
      * @param rank the rank for media card in the media carousel, starting from 0
      * @param receivedLatencyMillis latency in milliseconds for card received events. E.g. latency
      * between headphone connection to sysUI displays media recommendation card
+     * @param isSwipeToDismiss whether is to log swipe-to-dismiss event
      *
      */
     fun logSmartspaceCardReported(
         eventId: Int,
         instanceId: Int,
         uid: Int,
-        isRecommendationCard: Boolean,
         surfaces: IntArray,
         interactedSubcardRank: Int = 0,
         interactedSubcardCardinality: Int = 0,
         rank: Int = mediaCarouselScrollHandler.visibleMediaIndex,
-        receivedLatencyMillis: Int = 0
+        receivedLatencyMillis: Int = 0,
+        isSwipeToDismiss: Boolean = false
     ) {
+        if (MediaPlayerData.players().size <= rank) {
+            return
+        }
+
+        val mediaControlKey = MediaPlayerData.playerKeys().elementAt(rank)
         // Only log media resume card when Smartspace data is available
-        if (!isRecommendationCard &&
+        if (!mediaControlKey.isSsMediaRec &&
                 !mediaManager.smartspaceMediaData.isActive &&
                 MediaPlayerData.smartspaceMediaData == null) {
             return
@@ -813,10 +831,13 @@
                     // card type for each new feature.
                     SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__UNKNOWN_CARD,
                     surface,
-                    rank,
+                    // Use -1 as rank value to indicate user swipe to dismiss the card
+                    if (isSwipeToDismiss) -1 else rank,
                     cardinality,
-                    if (isRecommendationCard)
+                    if (mediaControlKey.isSsMediaRec)
                         15 // MEDIA_RECOMMENDATION
+                    else if (mediaControlKey.isSsReactivated)
+                        43 // MEDIA_RESUME_SS_ACTIVATED
                     else
                         31, // MEDIA_RESUME
                     uid,
@@ -828,7 +849,9 @@
             if (DEBUG) {
                 Log.d(TAG, "Log Smartspace card event id: $eventId instance id: $instanceId" +
                         " surface: $surface rank: $rank cardinality: $cardinality " +
-                        "isRecommendationCard: $isRecommendationCard uid: $uid " +
+                        "isRecommendationCard: ${mediaControlKey.isSsMediaRec} " +
+                        "isSsReactivated: ${mediaControlKey.isSsReactivated}" +
+                        "uid: $uid " +
                         "interactedSubcardRank: $interactedSubcardRank " +
                         "interactedSubcardCardinality: $interactedSubcardCardinality " +
                         "received_latency_millis: $receivedLatencyMillis")
@@ -843,10 +866,9 @@
                 logSmartspaceCardReported(SMARTSPACE_CARD_DISMISS_EVENT,
                         it.mSmartspaceId,
                         it.mUid,
-                        it.recommendationViewHolder != null,
                         intArrayOf(it.surfaceForSmartspaceLogging),
-                        // Use -1 as rank value to indicate user swipe to dismiss the card
-                        rank = -1)
+                        rank = index,
+                        isSwipeToDismiss = true)
                 // Reset card impressed state when swipe to dismissed
                 it.mIsImpressed = false
             }
@@ -897,29 +919,37 @@
         private set
 
     data class MediaSortKey(
-            // Whether the item represents a Smartspace media recommendation.
-        val isSsMediaRec: Boolean,
+        val isSsMediaRec: Boolean, // Whether the item represents a Smartspace media recommendation.
         val data: MediaData,
-        val updateTime: Long = 0
+        val updateTime: Long = 0,
+        val isSsReactivated: Boolean = false
     )
 
     private val comparator =
             compareByDescending<MediaSortKey> { it.data.isPlaying == true &&
                         it.data.playbackLocation == MediaData.PLAYBACK_LOCAL }
-                .thenByDescending { it.data.isPlaying == true &&
-                        it.data.playbackLocation == MediaData.PLAYBACK_CAST_LOCAL }
-                .thenByDescending { if (shouldPrioritizeSs) it.isSsMediaRec else !it.isSsMediaRec }
-                .thenByDescending { !it.data.resumption }
-                .thenByDescending { it.data.playbackLocation != MediaData.PLAYBACK_CAST_REMOTE }
-                .thenByDescending { it.updateTime }
-                .thenByDescending { it.data.notificationKey }
+                    .thenByDescending { it.data.isPlaying == true &&
+                        it.data.playbackLocation == MediaData.PLAYBACK_CAST_LOCAL
+                    }
+                    .thenByDescending { if (shouldPrioritizeSs) it.isSsMediaRec else !it.isSsMediaRec }
+                    .thenByDescending { !it.data.resumption }
+                    .thenByDescending { it.data.playbackLocation != MediaData.PLAYBACK_CAST_REMOTE }
+                    .thenByDescending { it.updateTime }
+                    .thenByDescending { it.data.notificationKey }
 
     private val mediaPlayers = TreeMap<MediaSortKey, MediaControlPanel>(comparator)
     private val mediaData: MutableMap<String, MediaSortKey> = mutableMapOf()
 
-    fun addMediaPlayer(key: String, data: MediaData, player: MediaControlPanel, clock: SystemClock) {
+    fun addMediaPlayer(
+        key: String,
+        data: MediaData,
+        player: MediaControlPanel,
+        clock: SystemClock,
+        isSsReactivated: Boolean
+    ) {
         removeMediaPlayer(key)
-        val sortKey = MediaSortKey(isSsMediaRec = false, data, clock.currentTimeMillis())
+        val sortKey = MediaSortKey(isSsMediaRec = false,
+                data, clock.currentTimeMillis(), isSsReactivated = isSsReactivated)
         mediaData.put(key, sortKey)
         mediaPlayers.put(sortKey, player)
     }
@@ -933,8 +963,8 @@
     ) {
         shouldPrioritizeSs = shouldPrioritize
         removeMediaPlayer(key)
-        val sortKey = MediaSortKey(/* isSsMediaRec= */ true,
-            EMPTY.copy(isPlaying = false), clock.currentTimeMillis())
+        val sortKey = MediaSortKey(isSsMediaRec = true,
+            EMPTY.copy(isPlaying = false), clock.currentTimeMillis(), isSsReactivated = true)
         mediaData.put(key, sortKey)
         mediaPlayers.put(sortKey, player)
         smartspaceMediaData = data
@@ -1014,4 +1044,8 @@
         }
         return false
     }
+
+    fun isSsReactivated(key: String): Boolean = mediaData.get(key)?.let {
+        it.isSsReactivated
+    } ?: false
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index 7ac70bd..3a727ba 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -178,8 +178,7 @@
             if (mPackageName != null && mInstanceId != null) {
                 mLogger.logSeek(mUid, mPackageName, mInstanceId);
             }
-            logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT,
-                    /* isRecommendationCard */ false);
+            logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT);
             return Unit.INSTANCE;
         });
     }
@@ -334,9 +333,8 @@
             mMediaViewHolder.getPlayer().setOnClickListener(v -> {
                 if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return;
                 if (mMediaViewController.isGutsVisible()) return;
-
-                logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT,
-                        /* isRecommendationCard */ false);
+                mLogger.logTapContentView(mUid, mPackageName, mInstanceId);
+                logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT);
                 mActivityStarter.postStartActivityDismissingKeyguard(clickIntent,
                         buildLaunchAnimatorController(mMediaViewHolder.getPlayer()));
             });
@@ -440,9 +438,7 @@
         mMediaViewHolder.getDismiss().setEnabled(isDismissible);
         mMediaViewHolder.getDismiss().setOnClickListener(v -> {
             if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return;
-
-            logSmartspaceCardReported(SMARTSPACE_CARD_DISMISS_EVENT,
-                    /* isRecommendationCard */ false);
+            logSmartspaceCardReported(SMARTSPACE_CARD_DISMISS_EVENT);
             mLogger.logLongPressDismiss(mUid, mPackageName, mInstanceId);
 
             if (mKey != null) {
@@ -664,39 +660,42 @@
             final ImageButton button, MediaAction mediaAction, ConstraintSet collapsedSet,
             ConstraintSet expandedSet, boolean showInCompact) {
 
-        animHandler.unregisterAll();
         if (mediaAction != null) {
-            final Drawable icon = mediaAction.getIcon();
-            button.setImageDrawable(icon);
-            button.setContentDescription(mediaAction.getContentDescription());
-            final Drawable bgDrawable = mediaAction.getBackground();
-            button.setBackground(bgDrawable);
+            if (animHandler.updateRebindId(mediaAction.getRebindId())) {
+                animHandler.unregisterAll();
 
-            animHandler.tryRegister(icon);
-            animHandler.tryRegister(bgDrawable);
+                final Drawable icon = mediaAction.getIcon();
+                button.setImageDrawable(icon);
+                button.setContentDescription(mediaAction.getContentDescription());
+                final Drawable bgDrawable = mediaAction.getBackground();
+                button.setBackground(bgDrawable);
 
-            Runnable action = mediaAction.getAction();
-            if (action == null) {
-                button.setEnabled(false);
-            } else {
-                button.setEnabled(true);
-                button.setOnClickListener(v -> {
-                    if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
-                        mLogger.logTapAction(button.getId(), mUid, mPackageName, mInstanceId);
-                        logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT,
-                                /* isRecommendationCard */ false);
-                        action.run();
+                animHandler.tryRegister(icon);
+                animHandler.tryRegister(bgDrawable);
 
-                        if (icon instanceof Animatable) {
-                            ((Animatable) icon).start();
+                Runnable action = mediaAction.getAction();
+                if (action == null) {
+                    button.setEnabled(false);
+                } else {
+                    button.setEnabled(true);
+                    button.setOnClickListener(v -> {
+                        if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+                            mLogger.logTapAction(button.getId(), mUid, mPackageName, mInstanceId);
+                            logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT);
+                            action.run();
+
+                            if (icon instanceof Animatable) {
+                                ((Animatable) icon).start();
+                            }
+                            if (bgDrawable instanceof Animatable) {
+                                ((Animatable) bgDrawable).start();
+                            }
                         }
-                        if (bgDrawable instanceof Animatable) {
-                            ((Animatable) bgDrawable).start();
-                        }
-                    }
-                });
+                    });
+                }
             }
         } else {
+            animHandler.unregisterAll();
             button.setImageDrawable(null);
             button.setContentDescription(null);
             button.setEnabled(false);
@@ -707,9 +706,29 @@
         setVisibleAndAlpha(expandedSet, button.getId(), mediaAction != null);
     }
 
+    // AnimationBindHandler is responsible for tracking the bound animation state and preventing
+    // jank and conflicts due to media notifications arriving at any time during an animation. It
+    // does this in two parts.
+    //  - Exit animations fired as a result of user input are tracked. When these are running, any
+    //      bind actions are delayed until the animation completes (and then fired in sequence).
+    //  - Continuous animations are tracked using their rebind id. Later calls using the same
+    //      rebind id will be totally ignored to prevent the continuous animation from restarting.
     private static class AnimationBindHandler extends Animatable2.AnimationCallback {
         private ArrayList<Runnable> mOnAnimationsComplete = new ArrayList<>();
         private ArrayList<Animatable2> mRegistrations = new ArrayList<>();
+        private Integer mRebindId = null;
+
+        // This check prevents rebinding to the action button if the identifier has not changed. A
+        // null value is always considered to be changed. This is used to prevent the connecting
+        // animation from rebinding (and restarting) if multiple buffer PlaybackStates are pushed by
+        // an application in a row.
+        public boolean updateRebindId(Integer rebindId) {
+            if (mRebindId == null || rebindId == null || !mRebindId.equals(rebindId)) {
+                mRebindId = rebindId;
+                return true;
+            }
+            return false;
+        }
 
         public void tryRegister(Drawable drawable) {
             if (drawable instanceof Animatable2) {
@@ -932,8 +951,9 @@
         mRecommendationViewHolder.getDismiss().setOnClickListener(v -> {
             if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return;
 
-            logSmartspaceCardReported(SMARTSPACE_CARD_DISMISS_EVENT,
-                    /* isRecommendationCard */ true);
+            logSmartspaceCardReported(
+                    761 // SMARTSPACE_CARD_DISMISS
+            );
             closeGuts();
             mMediaDataManagerLazy.get().dismissSmartspaceRecommendation(
                     data.getTargetId(), MediaViewController.GUTS_ANIMATION_DURATION + 100L);
@@ -1068,7 +1088,6 @@
             if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return;
 
             logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT,
-                    /* isRecommendationCard */ true,
                     interactedSubcardRank,
                     getSmartspaceSubCardCardinality());
 
@@ -1138,18 +1157,17 @@
         return SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DEFAULT_SURFACE;
     }
 
-    private void logSmartspaceCardReported(int eventId, boolean isRecommendationCard) {
-        logSmartspaceCardReported(eventId, isRecommendationCard,
+    private void logSmartspaceCardReported(int eventId) {
+        logSmartspaceCardReported(eventId,
                 /* interactedSubcardRank */ 0,
                 /* interactedSubcardCardinality */ 0);
     }
 
-    private void logSmartspaceCardReported(int eventId, boolean isRecommendationCard,
+    private void logSmartspaceCardReported(int eventId,
             int interactedSubcardRank, int interactedSubcardCardinality) {
         mMediaCarouselController.logSmartspaceCardReported(eventId,
                 mSmartspaceId,
                 mUid,
-                isRecommendationCard,
                 new int[]{getSurfaceForSmartspaceLogging()},
                 interactedSubcardRank,
                 interactedSubcardCardinality);
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
index a4d2f7b..bc8cca5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
@@ -184,7 +184,12 @@
     val icon: Drawable?,
     val action: Runnable?,
     val contentDescription: CharSequence?,
-    val background: Drawable?
+    val background: Drawable?,
+
+    // Rebind Id is used to detect identical rebinds and ignore them. It is intended
+    // to prevent continuously looping animations from restarting due to the arrival
+    // of repeated media notifications that are visually identical.
+    val rebindId: Int? = null
 )
 
 /** State of the media device. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt
index b68f2a7..311973a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt
@@ -32,7 +32,8 @@
         oldKey: String?,
         data: MediaData,
         immediately: Boolean,
-        receivedSmartspaceCardLatency: Int
+        receivedSmartspaceCardLatency: Int,
+        isSsReactivated: Boolean
     ) {
         if (oldKey != null && oldKey != key && entries.contains(oldKey)) {
             entries[key] = data to entries.remove(oldKey)?.second
@@ -46,8 +47,7 @@
     override fun onSmartspaceMediaDataLoaded(
         key: String,
         data: SmartspaceMediaData,
-        shouldPrioritize: Boolean,
-        isSsReactivated: Boolean
+        shouldPrioritize: Boolean
     ) {
         listeners.toSet().forEach { it.onSmartspaceMediaDataLoaded(key, data) }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
index de44a9c..80a407b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
@@ -89,7 +89,8 @@
         oldKey: String?,
         data: MediaData,
         immediately: Boolean,
-        receivedSmartspaceCardLatency: Int
+        receivedSmartspaceCardLatency: Int,
+        isSsReactivated: Boolean
     ) {
         if (oldKey != null && oldKey != key) {
             allEntries.remove(oldKey)
@@ -114,8 +115,7 @@
     override fun onSmartspaceMediaDataLoaded(
         key: String,
         data: SmartspaceMediaData,
-        shouldPrioritize: Boolean,
-        isSsReactivated: Boolean
+        shouldPrioritize: Boolean
     ) {
         if (!data.isActive) {
             Log.d(TAG, "Inactive recommendation data. Skip triggering.")
@@ -140,13 +140,12 @@
             }
         }
 
-        val activeMedia = userEntries.filter { (key, value) -> value.active }
-        var isSsReactivatedMutable = activeMedia.isEmpty() && userEntries.isNotEmpty()
+        val shouldReactivate = !hasActiveMedia() && hasAnyMedia()
 
         if (timeSinceActive < smartspaceMaxAgeMillis) {
             // It could happen there are existing active media resume cards, then we don't need to
             // reactivate.
-            if (isSsReactivatedMutable) {
+            if (shouldReactivate) {
                 val lastActiveKey = sorted.lastKey() // most recently active
                 // Notify listeners to consider this media active
                 Log.d(TAG, "reactivating $lastActiveKey instead of smartspace")
@@ -156,7 +155,7 @@
                     it.onMediaDataLoaded(lastActiveKey, lastActiveKey, mediaData,
                             receivedSmartspaceCardLatency =
                             (systemClock.currentTimeMillis() - data.headphoneConnectionTimeMillis)
-                                    .toInt())
+                                    .toInt(), isSsReactivated = true)
                 }
             }
         } else {
@@ -168,8 +167,7 @@
             Log.d(TAG, "Invalid recommendation data. Skip showing the rec card")
             return
         }
-        listeners.forEach { it.onSmartspaceMediaDataLoaded(key, data, shouldPrioritizeMutable,
-                isSsReactivatedMutable) }
+        listeners.forEach { it.onSmartspaceMediaDataLoaded(key, data, shouldPrioritizeMutable) }
     }
 
     override fun onMediaDataRemoved(key: String) {
@@ -260,14 +258,27 @@
     }
 
     /**
-     * Are there any media notifications active?
+     * Are there any media notifications active, including the recommendation?
      */
-    fun hasActiveMedia() = userEntries.any { it.value.active } || smartspaceMediaData.isActive
+    fun hasActiveMediaOrRecommendation() =
+            userEntries.any { it.value.active } ||
+                    (smartspaceMediaData.isActive && smartspaceMediaData.isValid)
 
     /**
      * Are there any media entries we should display?
      */
-    fun hasAnyMedia() = userEntries.isNotEmpty() || smartspaceMediaData.isActive
+    fun hasAnyMediaOrRecommendation() = userEntries.isNotEmpty() ||
+            (smartspaceMediaData.isActive && smartspaceMediaData.isValid)
+
+    /**
+     * Are there any media notifications active (excluding the recommendation)?
+     */
+    fun hasActiveMedia() = userEntries.any { it.value.active }
+
+    /**
+     * Are there any media entries we should display (excluding the recommendation)?
+     */
+    fun hasAnyMedia() = userEntries.isNotEmpty()
 
     /**
      * Add a listener for filtered [MediaData] changes
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index 08c3395..57c93ba 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -30,6 +30,7 @@
 import android.content.pm.PackageManager
 import android.graphics.Bitmap
 import android.graphics.ImageDecoder
+import android.graphics.drawable.Animatable
 import android.graphics.drawable.Icon
 import android.media.MediaDescription
 import android.media.MediaMetadata
@@ -57,6 +58,7 @@
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.BcSmartspaceDataPlugin
 import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
+import com.android.systemui.statusbar.NotificationMediaManager.isConnectingState
 import com.android.systemui.statusbar.notification.row.HybridGroupManager
 import com.android.systemui.tuner.TunerService
 import com.android.systemui.util.Assert
@@ -777,7 +779,20 @@
         val actions = MediaButton()
         controller.playbackState?.let { state ->
             // First, check for standard actions
-            actions.playOrPause = if (isPlayingState(state.state)) {
+            actions.playOrPause = if (isConnectingState(state.state)) {
+                // Spinner needs to be animating to render anything. Start it here.
+                val drawable = context.getDrawable(
+                        com.android.internal.R.drawable.progress_small_material)
+                (drawable as Animatable).start()
+                MediaAction(
+                    drawable,
+                    null, // no action to perform when clicked
+                    context.getString(R.string.controls_media_button_connecting),
+                    context.getDrawable(R.drawable.ic_media_connecting_container),
+                    // Specify a rebind id to prevent the spinner from restarting on later binds.
+                    com.android.internal.R.drawable.progress_small_material
+                )
+            } else if (isPlayingState(state.state)) {
                 getStandardAction(controller, state.actions, PlaybackState.ACTION_PAUSE)
             } else {
                 getStandardAction(controller, state.actions, PlaybackState.ACTION_PLAY)
@@ -1079,15 +1094,27 @@
     fun onSwipeToDismiss() = mediaDataFilter.onSwipeToDismiss()
 
     /**
-     * Are there any media notifications active?
+     * Are there any media notifications active, including the recommendations?
+     */
+    fun hasActiveMediaOrRecommendation() = mediaDataFilter.hasActiveMediaOrRecommendation()
+
+    /**
+     * Are there any media entries we should display, including the recommendations?
+     * If resumption is enabled, this will include inactive players
+     * If resumption is disabled, we only want to show active players
+     */
+    fun hasAnyMediaOrRecommendation() = mediaDataFilter.hasAnyMediaOrRecommendation()
+
+    /**
+     * Are there any resume media notifications active, excluding the recommendations?
      */
     fun hasActiveMedia() = mediaDataFilter.hasActiveMedia()
 
     /**
-     * Are there any media entries we should display?
-     * If resumption is enabled, this will include inactive players
-     * If resumption is disabled, we only want to show active players
-     */
+    * Are there any resume media notifications active, excluding the recommendations?
+    * If resumption is enabled, this will include inactive players
+    * If resumption is disabled, we only want to show active players
+    */
     fun hasAnyMedia() = mediaDataFilter.hasAnyMedia()
 
     interface Listener {
@@ -1106,13 +1133,17 @@
          * @param receivedSmartspaceCardLatency is the latency between headphone connects and sysUI
          * displays Smartspace media targets. Will be 0 if the data is not activated by Smartspace
          * signal.
+         *
+         * @param isSsReactivated indicates resume media card is reactivated by Smartspace
+         * recommendation signal
          */
         fun onMediaDataLoaded(
             key: String,
             oldKey: String?,
             data: MediaData,
             immediately: Boolean = true,
-            receivedSmartspaceCardLatency: Int = 0
+            receivedSmartspaceCardLatency: Int = 0,
+            isSsReactivated: Boolean = false
         ) {}
 
         /**
@@ -1121,15 +1152,11 @@
          * @param shouldPrioritize indicates the sorting priority of the Smartspace card. If true,
          * it will be prioritized as the first card. Otherwise, it will show up as the last card as
          * default.
-         *
-         * @param isSsReactivated indicates resume media card is reactivated by Smartspace
-         * recommendation signal
          */
         fun onSmartspaceMediaDataLoaded(
             key: String,
             data: SmartspaceMediaData,
-            shouldPrioritize: Boolean = false,
-            isSsReactivated: Boolean = false
+            shouldPrioritize: Boolean = false
         ) {}
 
         /** Called whenever a previously existing Media notification was removed. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
index ffae898..d623191 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
@@ -72,7 +72,8 @@
         oldKey: String?,
         data: MediaData,
         immediately: Boolean,
-        receivedSmartspaceCardLatency: Int
+        receivedSmartspaceCardLatency: Int,
+        isSsReactivated: Boolean
     ) {
         if (oldKey != null && oldKey != key) {
             val oldEntry = entries.remove(oldKey)
@@ -163,7 +164,7 @@
             }
         // A device that is not yet connected but is expected to connect imminently. Because it's
         // expected to connect imminently, it should be displayed as the current device.
-        private var aboutToConnectDeviceOverride: MediaDeviceData? = null
+        private var aboutToConnectDeviceOverride: AboutToConnectDevice? = null
 
         @AnyThread
         fun start() = bgExecutor.execute {
@@ -221,22 +222,34 @@
             }
         }
 
-        override fun onAboutToConnectDeviceChanged(deviceName: String?, deviceIcon: Drawable?) {
-            aboutToConnectDeviceOverride = if (deviceName == null || deviceIcon == null) {
-                null
-            } else {
-                MediaDeviceData(enabled = true, deviceIcon, deviceName)
-            }
+        override fun onAboutToConnectDeviceAdded(
+            deviceAddress: String,
+            deviceName: String,
+            deviceIcon: Drawable?
+        ) {
+            aboutToConnectDeviceOverride = AboutToConnectDevice(
+                fullMediaDevice = localMediaManager.getMediaDeviceById(deviceAddress),
+                backupMediaDeviceData = MediaDeviceData(enabled = true, deviceIcon, deviceName)
+            )
+            updateCurrent()
+        }
+
+        override fun onAboutToConnectDeviceRemoved() {
+            aboutToConnectDeviceOverride = null
             updateCurrent()
         }
 
         @WorkerThread
         private fun updateCurrent() {
-            if (aboutToConnectDeviceOverride != null) {
-                current = aboutToConnectDeviceOverride
-                return
+            val aboutToConnect = aboutToConnectDeviceOverride
+            if (aboutToConnect != null &&
+                aboutToConnect.fullMediaDevice == null &&
+                aboutToConnect.backupMediaDeviceData != null) {
+                    // Only use [backupMediaDeviceData] when we don't have [fullMediaDevice].
+                    current = aboutToConnect.backupMediaDeviceData
+                    return
             }
-            val device = localMediaManager.currentConnectedDevice
+            val device = aboutToConnect?.fullMediaDevice ?: localMediaManager.currentConnectedDevice
             val route = controller?.let { mr2manager.getRoutingSessionForMediaController(it) }
 
             // If we have a controller but get a null route, then don't trust the device
@@ -246,3 +259,17 @@
         }
     }
 }
+
+/**
+ * A class storing information for the about-to-connect device. See
+ * [LocalMediaManager.DeviceCallback.onAboutToConnectDeviceAdded] for more information.
+ *
+ * @property fullMediaDevice a full-fledged [MediaDevice] object representing the device. If
+ *   non-null, prefer using [fullMediaDevice] over [backupMediaDeviceData].
+ * @property backupMediaDeviceData a backup [MediaDeviceData] object containing the minimum
+ *   information required to display the device. Only use if [fullMediaDevice] is null.
+ */
+private data class AboutToConnectDevice(
+    val fullMediaDevice: MediaDevice? = null,
+    val backupMediaDeviceData: MediaDeviceData? = null
+)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
index 3d9f933..30771c7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
@@ -1146,7 +1146,10 @@
 @Retention(AnnotationRetention.SOURCE)
 private annotation class TransformationType
 
-@IntDef(prefix = ["LOCATION_"], value = [MediaHierarchyManager.LOCATION_QS,
-    MediaHierarchyManager.LOCATION_QQS, MediaHierarchyManager.LOCATION_LOCKSCREEN])
+@IntDef(prefix = ["LOCATION_"], value = [
+    MediaHierarchyManager.LOCATION_QS,
+    MediaHierarchyManager.LOCATION_QQS,
+    MediaHierarchyManager.LOCATION_LOCKSCREEN,
+    MediaHierarchyManager.LOCATION_DREAM_OVERLAY])
 @Retention(AnnotationRetention.SOURCE)
 annotation class MediaLocation
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
index d08b6f82..de2b5c9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
@@ -68,7 +68,8 @@
             oldKey: String?,
             data: MediaData,
             immediately: Boolean,
-            receivedSmartspaceCardLatency: Int
+            receivedSmartspaceCardLatency: Int,
+            isSsReactivated: Boolean
         ) {
             if (immediately) {
                 updateViewVisibility()
@@ -78,8 +79,7 @@
         override fun onSmartspaceMediaDataLoaded(
             key: String,
             data: SmartspaceMediaData,
-            shouldPrioritize: Boolean,
-            isSsReactivated: Boolean
+            shouldPrioritize: Boolean
         ) {
             updateViewVisibility()
         }
@@ -169,9 +169,9 @@
 
     private fun updateViewVisibility() {
         state.visible = if (showsOnlyActiveMedia) {
-            mediaDataManager.hasActiveMedia()
+            mediaDataManager.hasActiveMediaOrRecommendation()
         } else {
-            mediaDataManager.hasAnyMedia()
+            mediaDataManager.hasAnyMediaOrRecommendation()
         }
         val newVisibility = if (visible) View.VISIBLE else View.GONE
         if (newVisibility != hostView.visibility) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
index 35f95dd..61d0b41 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
@@ -184,7 +184,8 @@
         oldKey: String?,
         data: MediaData,
         immediately: Boolean,
-        receivedSmartspaceCardLatency: Int
+        receivedSmartspaceCardLatency: Int,
+        isSsReactivated: Boolean
     ) {
         if (useMediaResumption) {
             // If this had been started from a resume state, disconnect now that it's live
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaSessionBasedFilter.kt b/packages/SystemUI/src/com/android/systemui/media/MediaSessionBasedFilter.kt
index 1c448a2..3179296 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaSessionBasedFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaSessionBasedFilter.kt
@@ -96,7 +96,8 @@
         oldKey: String?,
         data: MediaData,
         immediately: Boolean,
-        receivedSmartspaceCardLatency: Int
+        receivedSmartspaceCardLatency: Int,
+        isSsReactivated: Boolean
     ) {
         backgroundExecutor.execute {
             data.token?.let {
@@ -143,8 +144,7 @@
     override fun onSmartspaceMediaDataLoaded(
         key: String,
         data: SmartspaceMediaData,
-        shouldPrioritize: Boolean,
-        isSsReactivated: Boolean
+        shouldPrioritize: Boolean
     ) {
         backgroundExecutor.execute {
             dispatchSmartspaceMediaDataLoaded(key, data)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
index 9581a63..51755065 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
@@ -63,7 +63,8 @@
         oldKey: String?,
         data: MediaData,
         immediately: Boolean,
-        receivedSmartspaceCardLatency: Int
+        receivedSmartspaceCardLatency: Int,
+        isSsReactivated: Boolean
     ) {
         var reusedListener: PlaybackStateListener? = null
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaUiEventLogger.kt b/packages/SystemUI/src/com/android/systemui/media/MediaUiEventLogger.kt
index 862b279..3eba3b5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaUiEventLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaUiEventLogger.kt
@@ -134,6 +134,23 @@
     fun logOpenOutputSwitcher(uid: Int, packageName: String, instanceId: InstanceId) {
         logger.logWithInstanceId(MediaUiEvent.OPEN_OUTPUT_SWITCHER, uid, packageName, instanceId)
     }
+
+    fun logTapContentView(uid: Int, packageName: String, instanceId: InstanceId) {
+        logger.logWithInstanceId(MediaUiEvent.MEDIA_TAP_CONTENT_VIEW, uid, packageName, instanceId)
+    }
+
+    fun logCarouselPosition(@MediaLocation location: Int) {
+        val event = when (location) {
+            MediaHierarchyManager.LOCATION_QQS -> MediaUiEvent.MEDIA_CAROUSEL_LOCATION_QQS
+            MediaHierarchyManager.LOCATION_QS -> MediaUiEvent.MEDIA_CAROUSEL_LOCATION_QS
+            MediaHierarchyManager.LOCATION_LOCKSCREEN ->
+                MediaUiEvent.MEDIA_CAROUSEL_LOCATION_LOCKSCREEN
+            MediaHierarchyManager.LOCATION_DREAM_OVERLAY ->
+                MediaUiEvent.MEDIA_CAROUSEL_LOCATION_DREAM
+            else -> throw IllegalArgumentException("Unknown media carousel location $location")
+        }
+        logger.log(event)
+    }
 }
 
 enum class MediaUiEvent(val metricId: Int) : UiEventLogger.UiEventEnum {
@@ -161,7 +178,7 @@
     @UiEvent(doc = "An existing active media control was converted into resumable media")
     ACTIVE_TO_RESUME(1014),
 
-    @UiEvent(doc = "Media timed out")
+    @UiEvent(doc = "A media control timed out")
     MEDIA_TIMEOUT(1015),
 
     @UiEvent(doc = "A media control was removed from the carousel")
@@ -173,35 +190,50 @@
     @UiEvent(doc = "The user swiped away the media carousel")
     DISMISS_SWIPE(1018),
 
-    @UiEvent(doc = "The user opened the long press menu")
+    @UiEvent(doc = "The user long pressed on a media control")
     OPEN_LONG_PRESS(1019),
 
-    @UiEvent(doc = "The user dismissed via long press menu")
+    @UiEvent(doc = "The user dismissed a media control via its long press menu")
     DISMISS_LONG_PRESS(1020),
 
-    @UiEvent(doc = "The user opened settings from long press menu")
+    @UiEvent(doc = "The user opened media settings from a media control's long press menu")
     OPEN_SETTINGS_LONG_PRESS(1021),
 
-    @UiEvent(doc = "The user opened settings from the carousel")
+    @UiEvent(doc = "The user opened media settings from the media carousel")
     OPEN_SETTINGS_CAROUSEL(1022),
 
-    @UiEvent(doc = "The play/pause button was tapped")
+    @UiEvent(doc = "The play/pause button on a media control was tapped")
     TAP_ACTION_PLAY_PAUSE(1023),
 
-    @UiEvent(doc = "The previous button was tapped")
+    @UiEvent(doc = "The previous button on a media control was tapped")
     TAP_ACTION_PREV(1024),
 
-    @UiEvent(doc = "The next button was tapped")
+    @UiEvent(doc = "The next button on a media control was tapped")
     TAP_ACTION_NEXT(1025),
 
-    @UiEvent(doc = "A custom or generic action button was tapped")
+    @UiEvent(doc = "A custom or generic action button on a media control was tapped")
     TAP_ACTION_OTHER(1026),
 
-    @UiEvent(doc = "The user seeked using the seekbar")
+    @UiEvent(doc = "The user seeked on a media control using the seekbar")
     ACTION_SEEK(1027),
 
     @UiEvent(doc = "The user opened the output switcher from a media control")
-    OPEN_OUTPUT_SWITCHER(1028);
+    OPEN_OUTPUT_SWITCHER(1028),
+
+    @UiEvent(doc = "The user tapped on a media control view")
+    MEDIA_TAP_CONTENT_VIEW(1036),
+
+    @UiEvent(doc = "The media carousel moved to QQS")
+    MEDIA_CAROUSEL_LOCATION_QQS(1037),
+
+    @UiEvent(doc = "THe media carousel moved to QS")
+    MEDIA_CAROUSEL_LOCATION_QS(1038),
+
+    @UiEvent(doc = "The media carousel moved to the lockscreen")
+    MEDIA_CAROUSEL_LOCATION_LOCKSCREEN(1039),
+
+    @UiEvent(doc = "The media carousel moved to the dream state")
+    MEDIA_CAROUSEL_LOCATION_DREAM(1040);
 
     override fun getId() = metricId
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index 807f0f1..ec2a950 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -721,7 +721,7 @@
 
     boolean isVolumeControlEnabled(@NonNull MediaDevice device) {
         return isPlayBackInfoLocal()
-                || mLocalMediaManager.isMediaSessionAvailableForVolumeControl();
+                || device.getDeviceType() != MediaDevice.MediaDeviceType.TYPE_CAST_GROUP_DEVICE;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java
index 8934cd10..e077fed 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java
@@ -56,13 +56,13 @@
 
         @Override
         public void onSmartspaceMediaDataLoaded(@NonNull String key,
-                @NonNull SmartspaceMediaData data, boolean shouldPrioritize,
-                boolean isSsReactivated) {
+                @NonNull SmartspaceMediaData data, boolean shouldPrioritize) {
         }
 
         @Override
         public void onMediaDataLoaded(@NonNull String key, @Nullable String oldKey,
-                @NonNull MediaData data, boolean immediately, int receivedSmartspaceCardLatency) {
+                @NonNull MediaData data, boolean immediately, int receivedSmartspaceCardLatency,
+                boolean isSsReactivated) {
             if (mAdded) {
                 return;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManager.kt b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManager.kt
index 22bc557..2783532 100644
--- a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManager.kt
@@ -52,7 +52,9 @@
                 // There should only be one device that's mutedUntilConnection at a time, so we can
                 // safely override any previous value.
                 currentMutedDevice = device
-                localMediaManager.dispatchAboutToConnectDeviceChanged(device.name, device.getIcon())
+                localMediaManager.dispatchAboutToConnectDeviceAdded(
+                    device.address, device.name, device.getIcon()
+                )
             }
         }
 
@@ -63,7 +65,7 @@
         ) {
             if (currentMutedDevice == device && USAGE_MEDIA in mutedUsages) {
                 currentMutedDevice = null
-                localMediaManager.dispatchAboutToConnectDeviceChanged(null, null)
+                localMediaManager.dispatchAboutToConnectDeviceRemoved()
             }
         }
     }
@@ -76,8 +78,8 @@
         val currentDevice = audioManager.mutingExpectedDevice
         if (currentDevice != null) {
             currentMutedDevice = currentDevice
-            localMediaManager.dispatchAboutToConnectDeviceChanged(
-                currentDevice.name, currentDevice.getIcon()
+            localMediaManager.dispatchAboutToConnectDeviceAdded(
+                currentDevice.address, currentDevice.name, currentDevice.getIcon()
             )
         }
     }
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 3c6805b..cd86fff 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
@@ -19,6 +19,7 @@
 import android.app.StatusBarManager
 import android.content.Context
 import android.media.MediaRoute2Info
+import android.util.Log
 import android.view.View
 import androidx.annotation.StringRes
 import com.android.internal.logging.UiEventLogger
@@ -221,7 +222,12 @@
          */
         fun getSenderStateFromId(
             @StatusBarManager.MediaTransferSenderState displayState: Int,
-        ): ChipStateSender = values().first { it.stateInt == displayState }
+        ): ChipStateSender? = try {
+            values().first { it.stateInt == displayState }
+        } catch (e: NoSuchElementException) {
+            Log.e(TAG, "Could not find requested state $displayState", e)
+            null
+        }
 
         /**
          * Returns the state int from [StatusBarManager] associated with the given sender state
@@ -238,3 +244,5 @@
 // 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
+
+private const val TAG = "ChipStateSender"
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index f5abe28..40265a3 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -143,6 +143,7 @@
     private boolean mDeadZoneConsuming = false;
     private final NavigationBarTransitions mBarTransitions;
     private final OverviewProxyService mOverviewProxyService;
+    @Nullable
     private AutoHideController mAutoHideController;
 
     // performs manual animation in sync with layout transitions
@@ -316,7 +317,7 @@
             };
 
     private final Consumer<Boolean> mNavbarOverlayVisibilityChangeCallback = (visible) -> {
-        if (visible) {
+        if (visible && mAutoHideController != null) {
             mAutoHideController.touchAutoHide();
         }
         notifyActiveTouchRegions();
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 0365241..e5dc0ec 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
@@ -360,9 +360,7 @@
                 .getDimension(R.dimen.navigation_edge_action_drag_threshold);
         mSwipeProgressThreshold = context.getResources()
                 .getDimension(R.dimen.navigation_edge_action_progress_threshold);
-        if (mBackAnimation != null) {
-            mBackAnimation.setSwipeThresholds(mSwipeTriggerThreshold, mSwipeProgressThreshold);
-        }
+        initializeBackAnimation();
 
         setVisibility(GONE);
         Executor backgroundExecutor = Dependency.get(Dependency.BACKGROUND_EXECUTOR);
@@ -391,6 +389,13 @@
 
     public void setBackAnimation(BackAnimation backAnimation) {
         mBackAnimation = backAnimation;
+        initializeBackAnimation();
+    }
+
+    private void initializeBackAnimation() {
+        if (mBackAnimation != null) {
+            mBackAnimation.setSwipeThresholds(mSwipeTriggerThreshold, mSwipeProgressThreshold);
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
index c01d6dc..c6c9aca 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
@@ -466,7 +466,7 @@
                 }
             }
         } catch (SQLException e) {
-            Log.e(TAG, "Failed to query contact: " + e);
+            Log.e(TAG, "Failed to query contact", e);
         } finally {
             if (cursor != null) {
                 cursor.close();
@@ -527,7 +527,7 @@
                 lookupKeysWithBirthdaysToday.add(lookupKey);
             }
         } catch (SQLException e) {
-            Log.e(TAG, "Failed to query birthdays: " + e);
+            Log.e(TAG, "Failed to query birthdays", e);
         } finally {
             if (cursor != null) {
                 cursor.close();
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
index f6e1cd4..1a7bd8c 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
@@ -275,7 +275,7 @@
                 updateSingleConversationWidgets(widgetIds);
             }
         } catch (Exception e) {
-            Log.e(TAG, "Exception: " + e);
+            Log.e(TAG, "failed to update widgets", e);
         }
     }
 
@@ -348,7 +348,7 @@
         try {
             return getTileForExistingWidgetThrowing(appWidgetId);
         } catch (Exception e) {
-            Log.e(TAG, "Failed to retrieve conversation for tile: " + e);
+            Log.e(TAG, "failed to retrieve tile for widget ID " + appWidgetId, e);
             return null;
         }
     }
@@ -423,7 +423,7 @@
             // Add current state.
             return getTileWithCurrentState(storedTile.build(), ACTION_BOOT_COMPLETED);
         } catch (RemoteException e) {
-            Log.e(TAG, "Could not retrieve data: " + e);
+            Log.e(TAG, "getTileFromPersistentStorage failing", e);
             return null;
         }
     }
@@ -441,12 +441,16 @@
                 Log.d(TAG, "Notification removed, key: " + sbn.getKey());
             }
         }
+        if (DEBUG) Log.d(TAG, "Fetching notifications");
+        Collection<NotificationEntry> notifications = mNotifCollection.getAllNotifs();
         mBgExecutor.execute(
-                () -> updateWidgetsWithNotificationChangedInBackground(sbn, notificationAction));
+                () -> updateWidgetsWithNotificationChangedInBackground(
+                        sbn, notificationAction, notifications));
     }
 
     private void updateWidgetsWithNotificationChangedInBackground(StatusBarNotification sbn,
-            PeopleSpaceUtils.NotificationAction action) {
+            PeopleSpaceUtils.NotificationAction action,
+            Collection<NotificationEntry> notifications) {
         try {
             PeopleTileKey key = new PeopleTileKey(
                     sbn.getShortcutId(), sbn.getUser().getIdentifier(), sbn.getPackageName());
@@ -469,23 +473,23 @@
                     Log.d(TAG, "Widgets by URI to be updated:" + tilesUpdatedByUri.toString());
                 }
                 tilesUpdated.addAll(tilesUpdatedByUri);
-                updateWidgetIdsBasedOnNotifications(tilesUpdated);
+                updateWidgetIdsBasedOnNotifications(tilesUpdated, notifications);
             }
         } catch (Exception e) {
-            Log.e(TAG, "Throwing exception: " + e);
+            Log.e(TAG, "updateWidgetsWithNotificationChangedInBackground failing", e);
         }
     }
 
     /** Updates {@code widgetIdsToUpdate} with {@code action}. */
-    private void updateWidgetIdsBasedOnNotifications(Set<String> widgetIdsToUpdate) {
+    private void updateWidgetIdsBasedOnNotifications(Set<String> widgetIdsToUpdate,
+            Collection<NotificationEntry> ungroupedNotifications) {
         if (widgetIdsToUpdate.isEmpty()) {
             if (DEBUG) Log.d(TAG, "No widgets to update, returning.");
             return;
         }
         try {
-            if (DEBUG) Log.d(TAG, "Fetching grouped notifications");
             Map<PeopleTileKey, Set<NotificationEntry>> groupedNotifications =
-                    getGroupedConversationNotifications();
+                    groupConversationNotifications(ungroupedNotifications);
 
             widgetIdsToUpdate
                     .stream()
@@ -495,7 +499,7 @@
                             id -> getAugmentedTileForExistingWidget(id, groupedNotifications)))
                     .forEach((id, tile) -> updateAppWidgetOptionsAndViewOptional(id, tile));
         } catch (Exception e) {
-            Log.e(TAG, "Exception updating widgets: " + e);
+            Log.e(TAG, "updateWidgetIdsBasedOnNotifications failing", e);
         }
     }
 
@@ -510,7 +514,7 @@
                     "Augmenting tile from NotificationEntryManager widget: " + key.toString());
         }
         Map<PeopleTileKey, Set<NotificationEntry>> notifications =
-                getGroupedConversationNotifications();
+                groupConversationNotifications(mNotifCollection.getAllNotifs());
         String contactUri = null;
         if (tile.getContactUri() != null) {
             contactUri = tile.getContactUri().toString();
@@ -518,9 +522,10 @@
         return augmentTileFromNotifications(tile, key, contactUri, notifications, appWidgetId);
     }
 
-    /** Returns active and pending notifications grouped by {@link PeopleTileKey}. */
-    public Map<PeopleTileKey, Set<NotificationEntry>> getGroupedConversationNotifications() {
-        Collection<NotificationEntry> notifications = mNotifCollection.getAllNotifs();
+    /** Groups active and pending notifications grouped by {@link PeopleTileKey}. */
+    public Map<PeopleTileKey, Set<NotificationEntry>> groupConversationNotifications(
+            Collection<NotificationEntry> notifications
+    ) {
         if (DEBUG) Log.d(TAG, "Number of total notifications: " + notifications.size());
         Map<PeopleTileKey, Set<NotificationEntry>> groupedNotifications =
                 notifications
@@ -846,7 +851,7 @@
                     Collections.singletonList(tile.getId()),
                     tile.getUserHandle(), LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS);
         } catch (Exception e) {
-            Log.w(TAG, "Exception caching shortcut:" + e);
+            Log.w(TAG, "failed to cache shortcut", e);
         }
         PeopleSpaceTile finalTile = tile;
         mBgExecutor.execute(
@@ -954,7 +959,7 @@
                     UserHandle.of(key.getUserId()),
                     LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS);
         } catch (Exception e) {
-            Log.d(TAG, "Exception uncaching shortcut:" + e);
+            Log.d(TAG, "failed to uncache shortcut", e);
         }
     }
 
@@ -1037,7 +1042,7 @@
                     packageName, userHandle.getIdentifier(), shortcutId);
             tile = PeopleSpaceUtils.getTile(channel, mLauncherApps);
         } catch (Exception e) {
-            Log.w(TAG, "Exception getting tiles: " + e);
+            Log.w(TAG, "failed to get conversation or tile", e);
             return null;
         }
         if (tile == null) {
@@ -1086,7 +1091,7 @@
                 }
             } catch (PackageManager.NameNotFoundException e) {
                 // Delete data for uninstalled widgets.
-                Log.e(TAG, "Package no longer found for tile: " + e);
+                Log.e(TAG, "package no longer found for tile", e);
                 JobScheduler jobScheduler = mContext.getSystemService(JobScheduler.class);
                 if (jobScheduler != null
                         && jobScheduler.getPendingJob(PeopleBackupFollowUpJob.JOB_ID) != null) {
@@ -1296,7 +1301,7 @@
                     try {
                         editor.putString(newId, (String) entry.getValue());
                     } catch (Exception e) {
-                        Log.e(TAG, "Malformed entry value: " + entry.getValue());
+                        Log.e(TAG, "malformed entry value: " + entry.getValue(), e);
                     }
                     editor.remove(key);
                     break;
@@ -1306,7 +1311,7 @@
                     try {
                         oldWidgetIds = (Set<String>) entry.getValue();
                     } catch (Exception e) {
-                        Log.e(TAG, "Malformed entry value: " + entry.getValue());
+                        Log.e(TAG, "malformed entry value: " + entry.getValue(), e);
                         editor.remove(key);
                         break;
                     }
@@ -1337,7 +1342,7 @@
             try {
                 oldWidgetIds = (Set<String>) entry.getValue();
             } catch (Exception e) {
-                Log.e(TAG, "Malformed entry value: " + entry.getValue());
+                Log.e(TAG, "malformed entry value: " + entry.getValue(), e);
                 followUpEditor.remove(key);
                 continue;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index 2435497..3e00a5f 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -18,6 +18,7 @@
 
 import static android.app.PendingIntent.FLAG_IMMUTABLE;
 
+import android.app.Dialog;
 import android.app.KeyguardManager;
 import android.app.Notification;
 import android.app.NotificationManager;
@@ -60,20 +61,25 @@
 import com.android.settingslib.utils.PowerUtil;
 import com.android.systemui.R;
 import com.android.systemui.SystemUIApplication;
+import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.broadcast.BroadcastSender;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
+import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.util.NotificationChannels;
 import com.android.systemui.volume.Events;
 
 import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
 import java.text.NumberFormat;
 import java.util.Locale;
 import java.util.Objects;
 
 import javax.inject.Inject;
 
+import dagger.Lazy;
+
 /**
  */
 @SysUISingleton
@@ -164,11 +170,15 @@
     private ActivityStarter mActivityStarter;
     private final BroadcastSender mBroadcastSender;
 
+    private final Lazy<BatteryController> mBatteryControllerLazy;
+    private final DialogLaunchAnimator mDialogLaunchAnimator;
+
     /**
      */
     @Inject
     public PowerNotificationWarnings(Context context, ActivityStarter activityStarter,
-            BroadcastSender broadcastSender) {
+            BroadcastSender broadcastSender, Lazy<BatteryController> batteryControllerLazy,
+            DialogLaunchAnimator dialogLaunchAnimator) {
         mContext = context;
         mNoMan = mContext.getSystemService(NotificationManager.class);
         mPowerMan = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
@@ -176,6 +186,8 @@
         mReceiver.init();
         mActivityStarter = activityStarter;
         mBroadcastSender = broadcastSender;
+        mBatteryControllerLazy = batteryControllerLazy;
+        mDialogLaunchAnimator = dialogLaunchAnimator;
         mUseSevereDialog = mContext.getResources().getBoolean(R.bool.config_severe_battery_dialog);
     }
 
@@ -685,8 +697,19 @@
         }
         d.setShowForAllUsers(true);
         d.setOnDismissListener((dialog) -> mSaverConfirmation = null);
-        d.show();
+        WeakReference<View> ref = mBatteryControllerLazy.get().getLastPowerSaverStartView();
+        if (ref != null && ref.get() != null && ref.get().isAggregatedVisible()) {
+            mDialogLaunchAnimator.showFromView(d, ref.get());
+        } else {
+            d.show();
+        }
         mSaverConfirmation = d;
+        mBatteryControllerLazy.get().clearLastPowerSaverStartView();
+    }
+
+    @VisibleForTesting
+    Dialog getSaverConfirmationDialog() {
+        return mSaverConfirmation;
     }
 
     private boolean isEnglishLocale() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
index e0d158c..2ffbf54 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
@@ -50,6 +50,7 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.shared.system.SysUiStatsLog
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.util.DeviceConfigProxy
 import com.android.systemui.util.indentIfPossible
@@ -283,6 +284,7 @@
             runningApps[it] = RunningApp(it.userId, it.packageName,
                     runningServiceTokens[it]!!.startTime, it.uiControl,
                     ai.loadLabel(packageManager), ai.loadIcon(packageManager))
+            logEvent(stopped = false, it.packageName, it.userId, runningApps[it]!!.timeStarted)
         }
 
         removedPackages.forEach { pkg ->
@@ -301,10 +303,25 @@
         }
     }
 
-    private fun stopPackage(userId: Int, packageName: String) {
+    private fun stopPackage(userId: Int, packageName: String, timeStarted: Long) {
+        logEvent(stopped = true, packageName, userId, timeStarted)
         activityManager.stopAppForUser(packageName, userId)
     }
 
+    private fun logEvent(stopped: Boolean, packageName: String, userId: Int, timeStarted: Long) {
+        val timeLogged = systemClock.elapsedRealtime()
+        val event = if (stopped) {
+            SysUiStatsLog.TASK_MANAGER_EVENT_REPORTED__EVENT__STOPPED
+        } else {
+            SysUiStatsLog.TASK_MANAGER_EVENT_REPORTED__EVENT__VIEWED
+        }
+        backgroundExecutor.execute {
+            val uid = packageManager.getPackageUidAsUser(packageName, userId)
+            SysUiStatsLog.write(SysUiStatsLog.TASK_MANAGER_EVENT_REPORTED, uid, event,
+                    timeLogged - timeStarted)
+        }
+    }
+
     private inner class AppListAdapter : RecyclerView.Adapter<AppItemViewHolder>() {
         private val lock = Any()
 
@@ -329,7 +346,7 @@
                         DateUtils.LENGTH_MEDIUM)
                 stopButton.setOnClickListener {
                     stopButton.setText(R.string.fgs_manager_app_item_stop_button_stopped_label)
-                    stopPackage(runningApp.userId, runningApp.packageName)
+                    stopPackage(runningApp.userId, runningApp.packageName, runningApp.timeStarted)
                 }
                 if (runningApp.uiControl == UIControl.HIDE_BUTTON) {
                     stopButton.visibility = View.INVISIBLE
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
index 842a1b9..3f394e7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
@@ -23,7 +23,6 @@
 import android.provider.Settings.Global.USER_SWITCHER_ENABLED
 import android.view.View
 import android.view.ViewGroup
-import android.widget.LinearLayout
 import androidx.annotation.VisibleForTesting
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.logging.MetricsLogger
@@ -32,8 +31,6 @@
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.R
 import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.globalactions.GlobalActionsDialogLite
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.FalsingManager
@@ -45,7 +42,6 @@
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.statusbar.policy.UserInfoController
 import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener
-import com.android.systemui.util.DualHeightHorizontalLinearLayout
 import com.android.systemui.util.ViewController
 import com.android.systemui.util.settings.GlobalSettings
 import javax.inject.Inject
@@ -74,8 +70,7 @@
     private val uiEventLogger: UiEventLogger,
     @Named(PM_LITE_ENABLED) private val showPMLiteButton: Boolean,
     private val globalSetting: GlobalSettings,
-    private val handler: Handler,
-    private val featureFlags: FeatureFlags
+    private val handler: Handler
 ) : ViewController<FooterActionsView>(view) {
 
     private var globalActionsDialog: GlobalActionsDialogLite? = null
@@ -100,7 +95,9 @@
         view.findViewById(R.id.security_footers_container)
     private val powerMenuLite: View = view.findViewById(R.id.pm_lite)
     private val multiUserSwitchController = multiUserSwitchControllerFactory.create(view)
-    private val securityFootersSeparator = View(context).apply {
+
+    @VisibleForTesting
+    internal val securityFootersSeparator = View(context).apply {
         visibility = View.GONE
     }
 
@@ -140,6 +137,7 @@
 
     override fun onInit() {
         multiUserSwitchController.init()
+        securityFooterController.init()
         fgsManagerFooterController.init()
     }
 
@@ -170,48 +168,30 @@
         }
         settingsButton.setOnClickListener(onClickListener)
         multiUserSetting.isListening = true
-        if (featureFlags.isEnabled(Flags.NEW_FOOTER)) {
-            val securityFooter = securityFooterController.view as DualHeightHorizontalLinearLayout
-            securityFootersContainer?.addView(securityFooter)
-            val separatorWidth = resources.getDimensionPixelSize(R.dimen.new_qs_footer_action_inset)
-            securityFootersContainer?.addView(securityFootersSeparator, separatorWidth, 1)
-            reformatForNewFooter(securityFooter)
-            val fgsFooter = fgsManagerFooterController.view
-            securityFootersContainer?.addView(fgsFooter)
-            (fgsFooter.layoutParams as LinearLayout.LayoutParams).apply {
-                width = 0
-                weight = 1f
-            }
 
-            val visibilityListener =
-                VisibilityChangedDispatcher.OnVisibilityChangedListener { visibility ->
-                    if (visibility == View.GONE) {
-                        securityFootersSeparator.visibility = View.GONE
-                    } else if (securityFooter.visibility == View.VISIBLE &&
-                        fgsFooter.visibility == View.VISIBLE) {
-                        securityFootersSeparator.visibility = View.VISIBLE
-                    } else {
-                        securityFootersSeparator.visibility = View.GONE
-                    }
-                    fgsManagerFooterController
-                        .setCollapsed(securityFooter.visibility == View.VISIBLE)
+        val securityFooter = securityFooterController.view
+        securityFootersContainer?.addView(securityFooter)
+        val separatorWidth = resources.getDimensionPixelSize(R.dimen.qs_footer_action_inset)
+        securityFootersContainer?.addView(securityFootersSeparator, separatorWidth, 1)
+
+        val fgsFooter = fgsManagerFooterController.view
+        securityFootersContainer?.addView(fgsFooter)
+
+        val visibilityListener =
+            VisibilityChangedDispatcher.OnVisibilityChangedListener { visibility ->
+                if (securityFooter.visibility == View.VISIBLE &&
+                    fgsFooter.visibility == View.VISIBLE) {
+                    securityFootersSeparator.visibility = View.VISIBLE
+                } else {
+                    securityFootersSeparator.visibility = View.GONE
                 }
-            securityFooterController.setOnVisibilityChangedListener(visibilityListener)
-            fgsManagerFooterController.setOnVisibilityChangedListener(visibilityListener)
-        }
-        updateView()
-    }
+                fgsManagerFooterController
+                    .setCollapsed(securityFooter.visibility == View.VISIBLE)
+            }
+        securityFooterController.setOnVisibilityChangedListener(visibilityListener)
+        fgsManagerFooterController.setOnVisibilityChangedListener(visibilityListener)
 
-    private fun reformatForNewFooter(view: DualHeightHorizontalLinearLayout) {
-        // This is only necessary while things are flagged as the view could be attached in two
-        // different locations.
-        (view.layoutParams as LinearLayout.LayoutParams).apply {
-            bottomMargin = 0
-            width = 0
-            weight = 1f
-            marginEnd = resources.getDimensionPixelSize(R.dimen.new_qs_footer_action_inset)
-        }
-        view.alwaysSingleLine = true
+        updateView()
     }
 
     private fun updateView() {
@@ -236,10 +216,9 @@
         } else {
             userInfoController.removeCallback(onUserInfoChangedListener)
         }
-        if (featureFlags.isEnabled(Flags.NEW_FOOTER)) {
-            fgsManagerFooterController.setListening(listening)
-            securityFooterController.setListening(listening)
-        }
+
+        fgsManagerFooterController.setListening(listening)
+        securityFooterController.setListening(listening)
     }
 
     fun disable(state2: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index d20141b..34f771c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -15,6 +15,7 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.animation.Interpolator;
 import android.view.animation.OvershootInterpolator;
 import android.widget.Scroller;
@@ -552,6 +553,51 @@
         postInvalidateOnAnimation();
     }
 
+    private int sanitizePageAction(int action) {
+        int pageLeftId = AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_LEFT.getId();
+        int pageRightId = AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_RIGHT.getId();
+        if (action == pageLeftId || action == pageRightId) {
+            if (!isLayoutRtl()) {
+                if (action == pageLeftId) {
+                    return AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD;
+                } else {
+                    return AccessibilityNodeInfo.ACTION_SCROLL_FORWARD;
+                }
+            } else {
+                if (action == pageLeftId) {
+                    return AccessibilityNodeInfo.ACTION_SCROLL_FORWARD;
+                } else {
+                    return AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD;
+                }
+            }
+        }
+        return action;
+    }
+
+    @Override
+    public boolean performAccessibilityAction(int action, Bundle arguments) {
+        action = sanitizePageAction(action);
+        boolean performed = super.performAccessibilityAction(action, arguments);
+        if (performed && (action == AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD
+                || action == AccessibilityNodeInfo.ACTION_SCROLL_FORWARD)) {
+            requestAccessibilityFocus();
+        }
+        return performed;
+    }
+
+    @Override
+    public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+        super.onInitializeAccessibilityNodeInfoInternal(info);
+        // getCurrentItem does not respect RTL, so it works well together with page actions that
+        // use left/right positioning.
+        if (getCurrentItem() != 0) {
+            info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_LEFT);
+        }
+        if (getCurrentItem() != mPages.size() - 1) {
+            info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_RIGHT);
+        }
+    }
+
     private static Animator setupBounceAnimator(View view, int ordinal) {
         view.setAlpha(0f);
         view.setScaleX(0f);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index 4640205..56298fa 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -81,8 +81,6 @@
     private final QSPanelController mQsPanelController;
     private final QuickQSPanelController mQuickQSPanelController;
     private final QuickStatusBarHeader mQuickStatusBarHeader;
-    private final QSFgsManagerFooter mFgsManagerFooter;
-    private final QSSecurityFooter mSecurityFooter;
     private final QS mQs;
 
     @Nullable
@@ -105,7 +103,7 @@
     private TouchAnimator mNonfirstPageAlphaAnimator;
     // TranslatesY the QS Tile layout using QS.getHeightDiff()
     private TouchAnimator mQSTileLayoutTranslatorAnimator;
-    // This animates fading of SecurityFooter and media divider
+    // This animates fading of media player
     private TouchAnimator mAllPagesDelayedAnimator;
     // Animator for brightness slider(s)
     @Nullable
@@ -146,7 +144,6 @@
     public QSAnimator(QS qs, QuickQSPanel quickPanel, QuickStatusBarHeader quickStatusBarHeader,
             QSPanelController qsPanelController,
             QuickQSPanelController quickQSPanelController, QSTileHost qsTileHost,
-            QSFgsManagerFooter fgsManagerFooter, QSSecurityFooter securityFooter,
             @Main Executor executor, TunerService tunerService,
             QSExpansionPathInterpolator qsExpansionPathInterpolator) {
         mQs = qs;
@@ -154,8 +151,6 @@
         mQsPanelController = qsPanelController;
         mQuickQSPanelController = quickQSPanelController;
         mQuickStatusBarHeader = quickStatusBarHeader;
-        mFgsManagerFooter = fgsManagerFooter;
-        mSecurityFooter = securityFooter;
         mHost = qsTileHost;
         mExecutor = executor;
         mTunerService = tunerService;
@@ -472,10 +467,8 @@
                     .setListener(this)
                     .build();
 
-            // Fade in the security footer and the divider as we reach the final position
+            // Fade in the media player as we reach the final position
             Builder builder = new Builder().setStartDelay(EXPANDED_TILE_DELAY);
-            builder.addFloat(mFgsManagerFooter.getView(), "alpha", 0, 1);
-            builder.addFloat(mSecurityFooter.getView(), "alpha", 0, 1);
             if (mQsPanelController.shouldUseHorizontalLayout()
                     && mQsPanelController.mMediaHost.hostView != null) {
                 builder.addFloat(mQsPanelController.mMediaHost.hostView, "alpha", 0, 1);
@@ -484,8 +477,6 @@
                 mQsPanelController.mMediaHost.hostView.setAlpha(1.0f);
             }
             mAllPagesDelayedAnimator = builder.build();
-            mAllViews.add(mFgsManagerFooter.getView());
-            mAllViews.add(mSecurityFooter.getView());
             translationYBuilder.setInterpolator(mQSExpansionPathInterpolator.getYInterpolator());
             qqsTranslationYBuilder.setInterpolator(mQSExpansionPathInterpolator.getYInterpolator());
             translationXBuilder.setInterpolator(mQSExpansionPathInterpolator.getXInterpolator());
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index 519ed5c..6658441 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -138,12 +138,8 @@
     }
 
     void updateResources(QSPanelController qsPanelController,
-            QuickStatusBarHeaderController quickStatusBarHeaderController,
-            boolean newFooter) {
-        int bottomPadding = 0;
-        if (newFooter) {
-            bottomPadding = getResources().getDimensionPixelSize(R.dimen.qs_panel_padding_bottom);
-        }
+            QuickStatusBarHeaderController quickStatusBarHeaderController) {
+        int bottomPadding = getResources().getDimensionPixelSize(R.dimen.qs_panel_padding_bottom);
         mQSPanelContainer.setPaddingRelative(
                 mQSPanelContainer.getPaddingStart(),
                 QSUtils.getQsHeaderSystemIconsAreaHeight(mContext),
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
index 61da182..7d61991 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
@@ -18,8 +18,6 @@
 
 import android.content.res.Configuration;
 
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.qs.dagger.QSScope;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.util.ViewController;
@@ -32,26 +30,23 @@
     private final QSPanelController mQsPanelController;
     private final QuickStatusBarHeaderController mQuickStatusBarHeaderController;
     private final ConfigurationController mConfigurationController;
-    private final boolean mNewFooter;
 
     private final ConfigurationController.ConfigurationListener mConfigurationListener =
             new ConfigurationController.ConfigurationListener() {
         @Override
         public void onConfigChanged(Configuration newConfig) {
-            mView.updateResources(mQsPanelController, mQuickStatusBarHeaderController, mNewFooter);
+            mView.updateResources(mQsPanelController, mQuickStatusBarHeaderController);
         }
     };
 
     @Inject
     QSContainerImplController(QSContainerImpl view, QSPanelController qsPanelController,
             QuickStatusBarHeaderController quickStatusBarHeaderController,
-            ConfigurationController configurationController,
-            FeatureFlags featureFlags) {
+            ConfigurationController configurationController) {
         super(view);
         mQsPanelController = qsPanelController;
         mQuickStatusBarHeaderController = quickStatusBarHeaderController;
         mConfigurationController = configurationController;
-        mNewFooter = featureFlags.isEnabled(Flags.NEW_FOOTER);
     }
 
     @Override
@@ -65,7 +60,7 @@
 
     @Override
     protected void onViewAttached() {
-        mView.updateResources(mQsPanelController, mQuickStatusBarHeaderController, mNewFooter);
+        mView.updateResources(mQsPanelController, mQuickStatusBarHeaderController);
         mConfigurationController.addCallback(mConfigurationListener);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
index aac5672..bcf60d1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
@@ -48,10 +48,5 @@
      */
     void setKeyguardShowing(boolean keyguardShowing);
 
-    /**
-     * Sets the {@link android.view.View.OnClickListener to be used on elements that expend QS.
-     */
-    void setExpandClickListener(View.OnClickListener onClickListener);
-
     default void disable(int state1, int state2, boolean animate) {}
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java
index 6c0ca49..61905ae 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java
@@ -23,13 +23,11 @@
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Build;
-import android.os.Bundle;
 import android.os.Handler;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.AttributeSet;
 import android.view.View;
-import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.FrameLayout;
 import android.widget.TextView;
 
@@ -168,23 +166,6 @@
         super.onDetachedFromWindow();
     }
 
-    @Override
-    public boolean performAccessibilityAction(int action, Bundle arguments) {
-        if (action == AccessibilityNodeInfo.ACTION_EXPAND) {
-            if (mExpandClickListener != null) {
-                mExpandClickListener.onClick(null);
-                return true;
-            }
-        }
-        return super.performAccessibilityAction(action, arguments);
-    }
-
-    @Override
-    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
-        super.onInitializeAccessibilityNodeInfo(info);
-        info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND);
-    }
-
     void disable(int state2) {
         final boolean disabled = (state2 & DISABLE2_QUICK_SETTINGS) != 0;
         if (disabled == mQsDisabled) return;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
index bef4f43..0d29a1a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
@@ -114,12 +114,6 @@
         mView.setKeyguardShowing();
     }
 
-    /** */
-    @Override
-    public void setExpandClickListener(View.OnClickListener onClickListener) {
-        mView.setExpandClickListener(onClickListener);
-    }
-
     @Override
     public void disable(int state1, int state2, boolean animate) {
         mView.disable(state2);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index b5e1c5e..4fa05c8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -31,7 +31,6 @@
 import android.view.ContextThemeWrapper;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.view.View.OnClickListener;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
 
@@ -212,7 +211,6 @@
                     }
         });
         mHeader = view.findViewById(R.id.header);
-        mQSPanelController.setHeaderContainer(view.findViewById(R.id.header_text_container));
         mFooter = qsFragmentComponent.getQSFooter();
 
         mQSContainerImplController = qsFragmentComponent.getQSContainerImplController();
@@ -717,8 +715,9 @@
     }
 
     @Override
-    public void setExpandClickListener(OnClickListener onClickListener) {
-        mFooter.setExpandClickListener(onClickListener);
+    public void setCollapseExpandAction(Runnable action) {
+        mQSPanelController.setCollapseExpandAction(action);
+        mQuickQSPanelController.setCollapseExpandAction(action);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 11a36ad..9fbdd3c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -34,6 +34,7 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.LinearLayout;
 
 import androidx.annotation.VisibleForTesting;
@@ -62,6 +63,8 @@
     private final int mMediaTopMargin;
     private final int mMediaTotalBottomMargin;
 
+    private Runnable mCollapseExpandAction;
+
     /**
      * The index where the content starts that needs to be moved between parents
      */
@@ -72,7 +75,6 @@
     @Nullable
     protected BrightnessSliderController mToggleSliderController;
 
-    private final H mHandler = new H();
     /** Whether or not the QS media player feature is enabled. */
     protected boolean mUsingMediaPlayer;
 
@@ -84,16 +86,9 @@
             new ArrayList<>();
 
     @Nullable
-    protected View mFgsManagerFooter;
-    @Nullable
-    protected View mSecurityFooter;
-
-    @Nullable
     protected View mFooter;
 
     @Nullable
-    private ViewGroup mHeaderContainer;
-    @Nullable
     private PageIndicator mFooterPageIndicator;
     private int mContentMarginStart;
     private int mContentMarginEnd;
@@ -109,7 +104,6 @@
     private float mSquishinessFraction = 1f;
     private final ArrayMap<View, Integer> mChildrenLayoutTop = new ArrayMap<>();
     private final Rect mClippingRect = new Rect();
-    private boolean mUseNewFooter = false;
     private ViewGroup mMediaHostView;
     private boolean mShouldMoveMediaOnExpansion = true;
 
@@ -153,10 +147,6 @@
         }
     }
 
-    void setUseNewFooter(boolean useNewFooter) {
-        mUseNewFooter = useNewFooter;
-    }
-
     protected void setHorizontalContentContainerClipping() {
         mHorizontalContentContainer.setClipChildren(true);
         mHorizontalContentContainer.setClipToPadding(false);
@@ -441,27 +431,6 @@
         }
     }
 
-    /** Switch the security footer between top and bottom of QS depending on orientation. */
-    public void switchSecurityFooter(boolean shouldUseSplitNotificationShade) {
-        if (mSecurityFooter == null) return;
-
-        if (!shouldUseSplitNotificationShade
-                && mContext.getResources().getConfiguration().orientation
-                == Configuration.ORIENTATION_LANDSCAPE && mHeaderContainer != null) {
-            // Adding the security view to the header, that enables us to avoid scrolling
-            switchToParent(mSecurityFooter, mHeaderContainer, 0);
-        } else {
-            // Add after the footer
-            int index;
-            if (mFgsManagerFooter != null) {
-                index = indexOfChild(mFgsManagerFooter);
-            } else {
-                index = indexOfChild(mFooter);
-            }
-            switchToParent(mSecurityFooter, this, index + 1);
-        }
-    }
-
     private void switchToParent(View child, ViewGroup parent, int index) {
         switchToParent(child, parent, index, getDumpableTag());
     }
@@ -606,38 +575,10 @@
         }
     }
 
-    /**
-     * Set the header container of quick settings.
-     */
-    public void setHeaderContainer(@NonNull ViewGroup headerContainer) {
-        mHeaderContainer = headerContainer;
-    }
-
     public boolean isListening() {
         return mListening;
     }
 
-    /**
-     * Set the security footer view and switch it into the right place
-     * @param view the view in question
-     * @param shouldUseSplitNotificationShade if QS is in split shade mode
-     */
-    public void setSecurityFooter(View view, boolean shouldUseSplitNotificationShade) {
-        mSecurityFooter = view;
-        switchSecurityFooter(shouldUseSplitNotificationShade);
-    }
-
-    /**
-     * Set the fgs manager footer view and switch it into the right place
-     * @param view the view in question
-     */
-    public void setFgsManagerFooter(View view) {
-        mFgsManagerFooter = view;
-        // Add after the footer
-        int index = indexOfChild(mFooter);
-        switchToParent(mFgsManagerFooter, this, index + 1);
-    }
-
     protected void setPageMargin(int pageMargin) {
         if (mTileLayout instanceof PagedTileLayout) {
             ((PagedTileLayout) mTileLayout).setPageMargin(pageMargin);
@@ -678,6 +619,28 @@
         mShouldMoveMediaOnExpansion = shouldMoveMediaOnExpansion;
     }
 
+    @Override
+    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+        super.onInitializeAccessibilityNodeInfo(info);
+        info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_COLLAPSE);
+    }
+
+    @Override
+    public boolean performAccessibilityAction(int action, Bundle arguments) {
+        if (action == AccessibilityNodeInfo.ACTION_EXPAND
+                || action == AccessibilityNodeInfo.ACTION_COLLAPSE) {
+            if (mCollapseExpandAction != null) {
+                mCollapseExpandAction.run();
+                return true;
+            }
+        }
+        return super.performAccessibilityAction(action, arguments);
+    }
+
+    public void setCollapseExpandAction(Runnable action) {
+        mCollapseExpandAction = action;
+    }
+
     private class H extends Handler {
         private static final int ANNOUNCE_FOR_ACCESSIBILITY = 1;
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index 10d7920..5670836 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -21,18 +21,14 @@
 import static com.android.systemui.qs.QSPanel.QS_SHOW_BRIGHTNESS;
 import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_MEDIA_PLAYER;
 
-import android.annotation.NonNull;
 import android.content.res.Configuration;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewGroup;
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.media.MediaHierarchyManager;
 import com.android.systemui.media.MediaHost;
 import com.android.systemui.media.MediaHostState;
@@ -56,8 +52,6 @@
 @QSScope
 public class QSPanelController extends QSPanelControllerBase<QSPanel> {
 
-    private final QSFgsManagerFooter mQSFgsManagerFooter;
-    private final QSSecurityFooter mQsSecurityFooter;
     private final TunerService mTunerService;
     private final QSCustomizerController mQsCustomizerController;
     private final QSTileRevealController.Factory mQsTileRevealControllerFactory;
@@ -65,7 +59,6 @@
     private final BrightnessController mBrightnessController;
     private final BrightnessSliderController mBrightnessSliderController;
     private final BrightnessMirrorHandler mBrightnessMirrorHandler;
-    private final FeatureFlags mFeatureFlags;
     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
 
     private boolean mGridContentVisible = true;
@@ -75,11 +68,9 @@
         @Override
         public void onConfigurationChange(Configuration newConfig) {
             mView.updateResources();
-            mQsSecurityFooter.onConfigurationChanged();
             if (mView.isListening()) {
                 refreshAllTiles();
             }
-            mView.switchSecurityFooter(mShouldUseSplitNotificationShade);
         }
     };
 
@@ -94,8 +85,7 @@
     };
 
     @Inject
-    QSPanelController(QSPanel view, QSFgsManagerFooter qsFgsManagerFooter,
-            QSSecurityFooter qsSecurityFooter, TunerService tunerService,
+    QSPanelController(QSPanel view, TunerService tunerService,
             QSTileHost qstileHost, QSCustomizerController qsCustomizerController,
             @Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer,
             @Named(QS_PANEL) MediaHost mediaHost,
@@ -103,12 +93,10 @@
             DumpManager dumpManager, MetricsLogger metricsLogger, UiEventLogger uiEventLogger,
             QSLogger qsLogger, BrightnessController.Factory brightnessControllerFactory,
             BrightnessSliderController.Factory brightnessSliderFactory,
-            FalsingManager falsingManager, FeatureFlags featureFlags,
+            FalsingManager falsingManager,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
         super(view, qstileHost, qsCustomizerController, usingMediaPlayer, mediaHost,
                 metricsLogger, uiEventLogger, qsLogger, dumpManager);
-        mQSFgsManagerFooter = qsFgsManagerFooter;
-        mQsSecurityFooter = qsSecurityFooter;
         mTunerService = tunerService;
         mQsCustomizerController = qsCustomizerController;
         mQsTileRevealControllerFactory = qsTileRevealControllerFactory;
@@ -119,9 +107,7 @@
 
         mBrightnessController = brightnessControllerFactory.create(mBrightnessSliderController);
         mBrightnessMirrorHandler = new BrightnessMirrorHandler(mBrightnessController);
-        mFeatureFlags = featureFlags;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
-        view.setUseNewFooter(featureFlags.isEnabled(Flags.NEW_FOOTER));
     }
 
     @Override
@@ -132,7 +118,6 @@
         mMediaHost.init(MediaHierarchyManager.LOCATION_QS);
         mQsCustomizerController.init();
         mBrightnessSliderController.init();
-        mQSFgsManagerFooter.init();
     }
 
     @Override
@@ -147,10 +132,6 @@
             refreshAllTiles();
         }
         mView.addOnConfigurationChangedListener(mOnConfigurationChangedListener);
-        if (!mFeatureFlags.isEnabled(Flags.NEW_FOOTER)) {
-            mView.setSecurityFooter(mQsSecurityFooter.getView(), mShouldUseSplitNotificationShade);
-            mView.setFgsManagerFooter(mQSFgsManagerFooter.getView());
-        }
         switchTileLayout(true);
         mBrightnessMirrorHandler.onQsPanelAttached();
 
@@ -172,13 +153,6 @@
         super.onViewDetached();
     }
 
-    /**
-     * Set the header container of quick settings.
-     */
-    public void setHeaderContainer(@NonNull ViewGroup headerContainer) {
-        mView.setHeaderContainer(headerContainer);
-    }
-
     /** */
     public void setVisibility(int visibility) {
         mView.setVisibility(visibility);
@@ -191,11 +165,6 @@
             refreshAllTiles();
         }
 
-        if (!mFeatureFlags.isEnabled(Flags.NEW_FOOTER)) {
-            mQSFgsManagerFooter.setListening(listening);
-            mQsSecurityFooter.setListening(listening);
-        }
-
         // Set the listening as soon as the QS fragment starts listening regardless of the
         //expansion, so it will update the current brightness before the slider is visible.
         if (listening) {
@@ -214,10 +183,6 @@
         return mHost;
     }
 
-    /** Show the device monitoring dialog. */
-    public void showDeviceMonitoringDialog() {
-        mQsSecurityFooter.showDeviceMonitoringDialog();
-    }
 
     /** Update appearance of QSPanel. */
     public void updateResources() {
@@ -228,8 +193,6 @@
     public void refreshAllTiles() {
         mBrightnessController.checkRestrictionAndSetEnabled();
         super.refreshAllTiles();
-        mQSFgsManagerFooter.refreshState();
-        mQsSecurityFooter.refreshState();
     }
 
     /** Start customizing the Quick Settings. */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index 74d1a3d..9c8fc47 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -424,6 +424,14 @@
         return mView.getBrightnessView();
     }
 
+    /**
+     * Set a listener to collapse/expand QS.
+     * @param action
+     */
+    public void setCollapseExpandAction(Runnable action) {
+        mView.setCollapseExpandAction(action);
+    }
+
     /** Sets whether we are currently on lock screen. */
     public void setIsOnKeyguard(boolean isOnKeyguard) {
         boolean isOnSplitShadeLockscreen = mShouldUseSplitNotificationShade && isOnKeyguard;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
index ea9bc69..7e0410c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
@@ -41,25 +41,26 @@
 import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_WORK_PROFILE_MONITORING;
 import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_WORK_PROFILE_NAMED_VPN;
 import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_WORK_PROFILE_NETWORK;
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
 import static com.android.systemui.qs.dagger.QSFragmentModule.QS_SECURITY_FOOTER_VIEW;
 
 import android.app.AlertDialog;
+import android.app.Dialog;
 import android.app.admin.DeviceAdminInfo;
 import android.app.admin.DevicePolicyEventLogger;
 import android.app.admin.DevicePolicyManager;
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.UserInfo;
-import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
 import android.text.SpannableStringBuilder;
@@ -69,7 +70,6 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.View.OnClickListener;
-import android.view.ViewGroup;
 import android.view.Window;
 import android.widget.ImageView;
 import android.widget.TextView;
@@ -81,15 +81,15 @@
 import com.android.systemui.FontSizeUtils;
 import com.android.systemui.R;
 import com.android.systemui.animation.DialogLaunchAnimator;
+import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.qs.dagger.QSScope;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.statusbar.policy.SecurityController;
+import com.android.systemui.util.ViewController;
 
 import java.util.concurrent.atomic.AtomicBoolean;
 
@@ -97,13 +97,13 @@
 import javax.inject.Named;
 
 @QSScope
-class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListener,
+class QSSecurityFooter extends ViewController<View>
+        implements OnClickListener, DialogInterface.OnClickListener,
         VisibilityChangedDispatcher {
     protected static final String TAG = "QSSecurityFooter";
     protected static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     private static final boolean DEBUG_FORCE_VISIBLE = false;
 
-    private final View mRootView;
     private final TextView mFooterText;
     private final ImageView mPrimaryFooterIcon;
     private final Context mContext;
@@ -114,15 +114,13 @@
     private final Handler mMainHandler;
     private final UserTracker mUserTracker;
     private final DialogLaunchAnimator mDialogLaunchAnimator;
+    private final BroadcastDispatcher mBroadcastDispatcher;
 
     private final AtomicBoolean mShouldUseSettingsButton = new AtomicBoolean(false);
 
     private AlertDialog mDialog;
     protected H mHandler;
 
-    // Does it move between footer and header? Remove this once all the flagging is removed
-    private final boolean mNewQsFooter;
-
     private boolean mIsVisible;
     @Nullable
     private CharSequence mFooterTextContent = null;
@@ -133,15 +131,24 @@
     @Nullable
     private VisibilityChangedDispatcher.OnVisibilityChangedListener mVisibilityChangedListener;
 
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getAction().equals(
+                    DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG)) {
+                showDeviceMonitoringDialog();
+            }
+        }
+    };
+
     @Inject
     QSSecurityFooter(@Named(QS_SECURITY_FOOTER_VIEW) View rootView,
             UserTracker userTracker, @Main Handler mainHandler, ActivityStarter activityStarter,
             SecurityController securityController, DialogLaunchAnimator dialogLaunchAnimator,
-            @Background Looper bgLooper, FeatureFlags featureFlags) {
-        mRootView = rootView;
-        mRootView.setOnClickListener(this);
-        mFooterText = mRootView.findViewById(R.id.footer_text);
-        mPrimaryFooterIcon = mRootView.findViewById(R.id.primary_footer_icon);
+            @Background Looper bgLooper, BroadcastDispatcher broadcastDispatcher) {
+        super(rootView);
+        mFooterText = mView.findViewById(R.id.footer_text);
+        mPrimaryFooterIcon = mView.findViewById(R.id.primary_footer_icon);
         mFooterIconId = R.drawable.ic_info_outline;
         mContext = rootView.getContext();
         mDpm = rootView.getContext().getSystemService(DevicePolicyManager.class);
@@ -151,7 +158,22 @@
         mHandler = new H(bgLooper);
         mUserTracker = userTracker;
         mDialogLaunchAnimator = dialogLaunchAnimator;
-        mNewQsFooter = featureFlags.isEnabled(Flags.NEW_FOOTER);
+        mBroadcastDispatcher = broadcastDispatcher;
+    }
+
+    @Override
+    protected void onViewAttached() {
+        // Use background handler, as it's the same thread that handleClick is called on.
+        mBroadcastDispatcher.registerReceiverWithHandler(mReceiver,
+                new IntentFilter(DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG),
+                mHandler, UserHandle.ALL);
+        mView.setOnClickListener(this);
+    }
+
+    @Override
+    protected void onViewDetached() {
+        mBroadcastDispatcher.unregisterReceiver(mReceiver);
+        mView.setOnClickListener(null);
     }
 
     public void setListening(boolean listening) {
@@ -173,29 +195,17 @@
         FontSizeUtils.updateFontSize(mFooterText, R.dimen.qs_tile_text_size);
         Resources r = mContext.getResources();
 
-        if (!mNewQsFooter) {
-            mFooterText.setMaxLines(r.getInteger(R.integer.qs_security_footer_maxLines));
-
-            int bottomMargin = r.getDimensionPixelSize(R.dimen.qs_footers_margin_bottom);
-            ViewGroup.MarginLayoutParams lp =
-                    (ViewGroup.MarginLayoutParams) mRootView.getLayoutParams();
-            lp.bottomMargin = bottomMargin;
-            lp.width = r.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT
-                    ? MATCH_PARENT : WRAP_CONTENT;
-            mRootView.setLayoutParams(lp);
-        }
-
         int padding = r.getDimensionPixelSize(R.dimen.qs_footer_padding);
-        mRootView.setPaddingRelative(padding, padding, padding, padding);
-        mRootView.setBackground(mContext.getDrawable(R.drawable.qs_security_footer_background));
+        mView.setPaddingRelative(padding, 0, padding, 0);
+        mView.setBackground(mContext.getDrawable(R.drawable.qs_security_footer_background));
     }
 
     public View getView() {
-        return mRootView;
+        return mView;
     }
 
     public boolean hasFooter() {
-        return mRootView.getVisibility() != View.GONE;
+        return mView.getVisibility() != View.GONE;
     }
 
     @Override
@@ -252,11 +262,11 @@
         // b) a specific work policy set but the work profile is turned off.
         if (mIsVisible && isProfileOwnerOfOrganizationOwnedDevice
                 && (!hasDisclosableWorkProfilePolicy || !isWorkProfileOn)) {
-            mRootView.setClickable(false);
-            mRootView.findViewById(R.id.footer_icon).setVisibility(View.GONE);
+            mView.setClickable(false);
+            mView.findViewById(R.id.footer_icon).setVisibility(View.GONE);
         } else {
-            mRootView.setClickable(true);
-            mRootView.findViewById(R.id.footer_icon).setVisibility(View.VISIBLE);
+            mView.setClickable(true);
+            mView.findViewById(R.id.footer_icon).setVisibility(View.VISIBLE);
         }
         // Update the string
         mFooterTextContent = getFooterText(isDeviceManaged, hasWorkProfile,
@@ -493,12 +503,20 @@
                     this);
 
             mDialog.setView(view);
-
-            mDialogLaunchAnimator.showFromView(mDialog, mRootView);
+            if (mView.isAggregatedVisible()) {
+                mDialogLaunchAnimator.showFromView(mDialog, mView);
+            } else {
+                mDialog.show();
+            }
         });
     }
 
     @VisibleForTesting
+    Dialog getDialog() {
+        return mDialog;
+    }
+
+    @VisibleForTesting
     View createDialogView() {
         if (mSecurityController.isParentalControlsEnabled()) {
             return createParentalControlsDialogView();
@@ -805,9 +823,9 @@
             if (mFooterTextContent != null) {
                 mFooterText.setText(mFooterTextContent);
             }
-            mRootView.setVisibility(mIsVisible || DEBUG_FORCE_VISIBLE ? View.VISIBLE : View.GONE);
+            mView.setVisibility(mIsVisible || DEBUG_FORCE_VISIBLE ? View.VISIBLE : View.GONE);
             if (mVisibilityChangedListener != null) {
-                mVisibilityChangedListener.onVisibilityChanged(mRootView.getVisibility());
+                mVisibilityChangedListener.onVisibilityChanged(mView.getVisibility());
             }
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index f5ae019..3c95da8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -20,6 +20,7 @@
 import android.content.res.Configuration;
 import android.util.AttributeSet;
 import android.view.View;
+import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.LinearLayout;
 
 import com.android.internal.logging.UiEventLogger;
@@ -167,6 +168,14 @@
         return QSEvent.QQS_TILE_VISIBLE;
     }
 
+    @Override
+    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+        super.onInitializeAccessibilityNodeInfo(info);
+        // Remove the collapse action from QSPanel
+        info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_COLLAPSE);
+        info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND);
+    }
+
     static class QQSSideLabelTileLayout extends SideLabelTileLayout {
 
         private boolean mLastSelected;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index c5ca285..264edb1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -70,7 +70,6 @@
     private View mDateView;
     // DateView next to clock. Visible on QQS
     private VariableDateView mClockDateView;
-    private View mSecurityHeaderView;
     private View mStatusIconsView;
     private View mContainer;
 
@@ -137,7 +136,6 @@
         mPrivacyChip = findViewById(R.id.privacy_chip);
         mDateView = findViewById(R.id.date);
         mClockDateView = findViewById(R.id.date_clock);
-        mSecurityHeaderView = findViewById(R.id.header_text_container);
         mClockIconsSeparator = findViewById(R.id.separator);
         mRightLayout = findViewById(R.id.rightLayout);
         mDateContainer = findViewById(R.id.date_container);
@@ -152,8 +150,6 @@
         updateResources();
         Configuration config = mContext.getResources().getConfiguration();
         setDatePrivacyContainersWidth(config.orientation == Configuration.ORIENTATION_LANDSCAPE);
-        setSecurityHeaderContainerVisibility(
-                config.orientation == Configuration.ORIENTATION_LANDSCAPE);
 
         // QS will always show the estimate, and BatteryMeterView handles the case where
         // it's unavailable or charging
@@ -207,8 +203,6 @@
         super.onConfigurationChanged(newConfig);
         updateResources();
         setDatePrivacyContainersWidth(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE);
-        setSecurityHeaderContainerVisibility(
-                newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE);
     }
 
     @Override
@@ -229,10 +223,6 @@
         mPrivacyContainer.setLayoutParams(lp);
     }
 
-    private void setSecurityHeaderContainerVisibility(boolean landscape) {
-        mSecurityHeaderView.setVisibility(landscape ? VISIBLE : GONE);
-    }
-
     private void updateBatteryMode() {
         if (mConfigShowBatteryEstimate && !mHasCenterCutout) {
             mBatteryRemainingIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE);
@@ -337,7 +327,6 @@
             return;
         }
         TouchAnimator.Builder builder = new TouchAnimator.Builder()
-                .addFloat(mSecurityHeaderView, "alpha", 0, 1)
                 // These views appear on expanding down
                 .addFloat(mDateView, "alpha", 0, 0, 1)
                 .addFloat(mClockDateView, "alpha", 1, 0, 0)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
index da82d2c..9ef90ec 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
@@ -8,6 +8,7 @@
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityNodeInfo;
 
 import androidx.annotation.Nullable;
 
@@ -240,6 +241,7 @@
             } else {
                 record.tileView.setLeftTopRightBottom(left, top, right, bottom);
             }
+            record.tileView.setPosition(i);
             mLastTileBottom = bottom;
         }
     }
@@ -296,4 +298,11 @@
             }
         }
     }
+
+    @Override
+    public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+        super.onInitializeAccessibilityNodeInfoInternal(info);
+        info.setCollectionInfo(
+                new AccessibilityNodeInfo.CollectionInfo(mRecords.size(), 1, false));
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
index 2780b16..aa505fb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
@@ -22,13 +22,10 @@
 import android.content.Context;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.view.ViewStub;
 
 import com.android.systemui.R;
 import com.android.systemui.battery.BatteryMeterView;
 import com.android.systemui.dagger.qualifiers.RootView;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.privacy.OngoingPrivacyChip;
 import com.android.systemui.qs.FooterActionsView;
@@ -128,15 +125,7 @@
      * This will replace a ViewStub either in {@link QSFooterView} or in {@link QSContainerImpl}.
      */
     @Provides
-    static FooterActionsView providesQSFooterActionsView(@RootView View view,
-            FeatureFlags featureFlags) {
-        ViewStub stub;
-        if (featureFlags.isEnabled(Flags.NEW_FOOTER)) {
-            stub = view.requireViewById(R.id.container_stub);
-        } else {
-            stub = view.requireViewById(R.id.footer_stub);
-        }
-        stub.inflate();
+    static FooterActionsView providesQSFooterActionsView(@RootView View view) {
         return view.findViewById(R.id.qs_footer_actions);
     }
 
@@ -161,9 +150,10 @@
     @Named(QS_SECURITY_FOOTER_VIEW)
     static View providesQSSecurityFooterView(
             @QSThemedContext LayoutInflater layoutInflater,
-            QSPanel qsPanel
+            FooterActionsView footerActionsView
     ) {
-        return layoutInflater.inflate(R.layout.quick_settings_security_footer, qsPanel, false);
+        return layoutInflater.inflate(R.layout.quick_settings_security_footer, footerActionsView,
+                false);
     }
 
     /** */
@@ -200,8 +190,8 @@
     @Named(QS_FGS_MANAGER_FOOTER_VIEW)
     static View providesQSFgsManagerFooterView(
             @QSThemedContext LayoutInflater layoutInflater,
-            QSPanel qsPanel
+            FooterActionsView footerActionsView
     ) {
-        return layoutInflater.inflate(R.layout.fgs_footer, qsPanel, false);
+        return layoutInflater.inflate(R.layout.fgs_footer, footerActionsView, false);
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index a712ce2..72dad06 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -69,6 +69,12 @@
         internal const val TILE_STATE_RES_PREFIX = "tile_states_"
     }
 
+    private var _position: Int = INVALID
+
+    override fun setPosition(position: Int) {
+        _position = position
+    }
+
     override var heightOverride: Int = HeightOverrideable.NO_OVERRIDE
         set(value) {
             if (field == value) return
@@ -404,6 +410,10 @@
                 }
             }
         }
+        if (_position != INVALID) {
+            info.collectionItemInfo =
+                AccessibilityNodeInfo.CollectionItemInfo(_position, 1, 0, 1, false)
+        }
     }
 
     override fun toString(): String {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
index 7d8a28f..1004fca 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
@@ -116,6 +116,11 @@
     public void handleSetListening(boolean listening) {
         super.handleSetListening(listening);
         mSetting.setListening(listening);
+        if (!listening) {
+            // If we stopped listening, it means that the tile is not visible. In that case, we
+            // don't need to save the view anymore
+            mBatteryController.clearLastPowerSaverStartView();
+        }
     }
 
     @Override
@@ -128,7 +133,7 @@
         if (getState().state == Tile.STATE_UNAVAILABLE) {
             return;
         }
-        mBatteryController.setPowerSaveMode(!mPowerSave);
+        mBatteryController.setPowerSaveMode(!mPowerSave, view);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index 5d6bbae..4afd39e3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -202,7 +202,7 @@
                         mActivityStarter
                                 .postStartActivityDismissingKeyguard(getLongClickIntent(), 0,
                                         controller);
-                    }, R.style.Theme_SystemUI_Dialog, false /* showProgressBarWhenEmpty */);
+                    }, R.style.Theme_SystemUI_Dialog_Cast, false /* showProgressBarWhenEmpty */);
             holder.init(dialog);
             SystemUIDialog.setShowForAllUsers(dialog, true);
             SystemUIDialog.registerDismissListener(dialog);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java b/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java
index 4f0455d..f3c87d0 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java
@@ -49,6 +49,7 @@
     private final SwipeDismissHandler mSwipeDismissHandler;
     private final GestureDetector mSwipeDetector;
     private View mActionsContainer;
+    private View mActionsContainerBackground;
     private SwipeDismissCallbacks mCallbacks;
     private final DisplayMetrics mDisplayMetrics;
 
@@ -118,7 +119,8 @@
 
     @Override // View
     protected void onFinishInflate() {
-        mActionsContainer = findViewById(R.id.actions_container_background);
+        mActionsContainer = findViewById(R.id.actions_container);
+        mActionsContainerBackground = findViewById(R.id.actions_container_background);
     }
 
     @Override
@@ -129,10 +131,6 @@
         return mSwipeDetector.onTouchEvent(ev);
     }
 
-    public int getVisibleRight() {
-        return mActionsContainer.getRight();
-    }
-
     /**
      * Cancel current dismissal animation, if any
      */
@@ -331,7 +329,7 @@
             if (startX > 0 || (startX == 0 && layoutDir == LAYOUT_DIRECTION_RTL)) {
                 finalX = mDisplayMetrics.widthPixels;
             } else {
-                finalX = -1 * mActionsContainer.getRight();
+                finalX = -1 * mActionsContainerBackground.getRight();
             }
             float distance = Math.abs(finalX - startX);
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index 04f33c1..50ee1f7 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -237,7 +237,7 @@
             String subjectDate = DateFormat.getDateTimeInstance().format(new Date(mImageTime));
             String subject = String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate);
             Intent sharingIntent = new Intent(Intent.ACTION_SEND);
-            sharingIntent.setType("image/png");
+            sharingIntent.setDataAndType(uri, "image/png");
             sharingIntent.putExtra(Intent.EXTRA_STREAM, uri);
             // Include URI in ClipData also, so that grantPermission picks it up.
             // We don't use setData here because some apps interpret this as "to:".
@@ -246,7 +246,9 @@
                     new ClipData.Item(uri));
             sharingIntent.setClipData(clipdata);
             sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
-            sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+            sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+                    .addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
 
             // Make sure pending intents for the system user are still unique across users
             // by setting the (otherwise unused) request code to the current user id.
@@ -256,6 +258,7 @@
                     .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK)
                     .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
 
+
             // cancel current pending intent (if any) since clipData isn't used for matching
             PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
                     context, 0, sharingChooserIntent,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AbstractLockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/AbstractLockscreenShadeTransitionController.kt
new file mode 100644
index 0000000..189f384
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AbstractLockscreenShadeTransitionController.kt
@@ -0,0 +1,61 @@
+package com.android.systemui.statusbar
+
+import android.content.Context
+import android.content.res.Configuration
+import android.util.IndentingPrintWriter
+import com.android.systemui.Dumpable
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.LargeScreenUtils
+import java.io.FileDescriptor
+import java.io.PrintWriter
+
+/** An abstract implementation of a class that controls the lockscreen to shade transition. */
+abstract class AbstractLockscreenShadeTransitionController(
+    protected val context: Context,
+    configurationController: ConfigurationController,
+    dumpManager: DumpManager
+) : Dumpable {
+
+    protected var useSplitShade = false
+
+    /**
+     * The amount of pixels that the user has dragged down during the shade transition on
+     * lockscreen.
+     */
+    var dragDownAmount = 0f
+        set(value) {
+            if (value == field) {
+                return
+            }
+            field = value
+            onDragDownAmountChanged(value)
+        }
+
+    init {
+        updateResourcesInternal()
+        configurationController.addCallback(
+            object : ConfigurationController.ConfigurationListener {
+                override fun onConfigChanged(newConfig: Configuration?) {
+                    updateResourcesInternal()
+                }
+            })
+        @Suppress("LeakingThis")
+        dumpManager.registerDumpable(this)
+    }
+
+    private fun updateResourcesInternal() {
+        useSplitShade = LargeScreenUtils.shouldUseSplitNotificationShade(context.resources)
+        updateResources()
+    }
+
+    protected abstract fun updateResources()
+
+    protected abstract fun onDragDownAmountChanged(dragDownAmount: Float)
+
+    override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
+        dump(IndentingPrintWriter(pw, /* singleIndent= */ "  "))
+    }
+
+    abstract fun dump(pw: IndentingPrintWriter)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index d9a98b1..5585cde 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -323,7 +323,7 @@
         default void onBiometricError(@Modality int modality, int error, int vendorCode) {
         }
 
-        default void hideAuthenticationDialog() {
+        default void hideAuthenticationDialog(long requestId) {
         }
 
         /**
@@ -999,9 +999,11 @@
     }
 
     @Override
-    public void hideAuthenticationDialog() {
+    public void hideAuthenticationDialog(long requestId) {
         synchronized (mLock) {
-            mHandler.obtainMessage(MSG_BIOMETRIC_HIDE).sendToTarget();
+            final SomeArgs args = SomeArgs.obtain();
+            args.argl1 = requestId;
+            mHandler.obtainMessage(MSG_BIOMETRIC_HIDE, args).sendToTarget();
         }
     }
 
@@ -1508,11 +1510,14 @@
                     someArgs.recycle();
                     break;
                 }
-                case MSG_BIOMETRIC_HIDE:
+                case MSG_BIOMETRIC_HIDE: {
+                    final SomeArgs someArgs = (SomeArgs) msg.obj;
                     for (int i = 0; i < mCallbacks.size(); i++) {
-                        mCallbacks.get(i).hideAuthenticationDialog();
+                        mCallbacks.get(i).hideAuthenticationDialog(someArgs.argl1 /* requestId */);
                     }
+                    someArgs.recycle();
                     break;
+                }
                 case MSG_SET_BIOMETRICS_LISTENER:
                     for (int i = 0; i < mCallbacks.size(); i++) {
                         mCallbacks.get(i).setBiometicContextListener(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 55ca8f3..b07a898 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -887,7 +887,10 @@
             if (mKeyguardUpdateMonitor.isUdfpsSupported()
                     && mKeyguardUpdateMonitor.getUserCanSkipBouncer(
                     KeyguardUpdateMonitor.getCurrentUser())) {
-                showBiometricMessage(mContext.getString(R.string.keyguard_unlock_press));
+                final int stringId = mKeyguardUpdateMonitor.getIsFaceAuthenticated()
+                        ? R.string.keyguard_face_successful_unlock_press
+                        : R.string.keyguard_unlock_press;
+                showBiometricMessage(mContext.getString(stringId));
             } else {
                 showBiometricMessage(mContext.getString(R.string.keyguard_unlock));
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionController.kt
new file mode 100644
index 0000000..01eb444
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionController.kt
@@ -0,0 +1,120 @@
+package com.android.systemui.statusbar
+
+import android.content.Context
+import android.util.IndentingPrintWriter
+import android.util.MathUtils
+import com.android.systemui.R
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.media.MediaHierarchyManager
+import com.android.systemui.statusbar.phone.NotificationPanelViewController
+import com.android.systemui.statusbar.policy.ConfigurationController
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+/** Controls the lockscreen to shade transition for the keyguard elements. */
+class LockscreenShadeKeyguardTransitionController
+@AssistedInject
+constructor(
+    private val mediaHierarchyManager: MediaHierarchyManager,
+    @Assisted private val notificationPanelController: NotificationPanelViewController,
+    context: Context,
+    configurationController: ConfigurationController,
+    dumpManager: DumpManager
+) : AbstractLockscreenShadeTransitionController(context, configurationController, dumpManager) {
+
+    /**
+     * Distance that the full shade transition takes in order for the keyguard content on
+     * NotificationPanelViewController to fully fade (e.g. Clock & Smartspace).
+     */
+    private var alphaTransitionDistance = 0
+
+    /**
+     * Distance that the full shade transition takes in order for the keyguard elements to fully
+     * translate into their final position
+     */
+    private var keyguardTransitionDistance = 0
+
+    /** The amount of vertical offset for the keyguard during the full shade transition. */
+    private var keyguardTransitionOffset = 0
+
+    /** The amount of alpha that was last set on the keyguard elements. */
+    private var alpha = 0f
+
+    /** The latest progress [0,1] of the alpha transition. */
+    private var alphaProgress = 0f
+
+    /** The amount of alpha that was last set on the keyguard status bar. */
+    private var statusBarAlpha = 0f
+
+    /** The amount of translationY that was last set on the keyguard elements. */
+    private var translationY = 0
+
+    /** The latest progress [0,1] of the translationY progress. */
+    private var translationYProgress = 0f
+
+    override fun updateResources() {
+        alphaTransitionDistance =
+            context.resources.getDimensionPixelSize(
+                R.dimen.lockscreen_shade_npvc_keyguard_content_alpha_transition_distance)
+        keyguardTransitionDistance =
+            context.resources.getDimensionPixelSize(
+                R.dimen.lockscreen_shade_keyguard_transition_distance)
+        keyguardTransitionOffset =
+            context.resources.getDimensionPixelSize(
+                R.dimen.lockscreen_shade_keyguard_transition_vertical_offset)
+    }
+
+    override fun onDragDownAmountChanged(dragDownAmount: Float) {
+        alphaProgress = MathUtils.saturate(dragDownAmount / alphaTransitionDistance)
+        alpha = 1f - alphaProgress
+        translationY = calculateKeyguardTranslationY(dragDownAmount)
+        notificationPanelController.setKeyguardTransitionProgress(alpha, translationY)
+
+        statusBarAlpha = if (useSplitShade) alpha else -1f
+        notificationPanelController.setKeyguardStatusBarAlpha(statusBarAlpha)
+    }
+
+    private fun calculateKeyguardTranslationY(dragDownAmount: Float): Int {
+        if (!useSplitShade) {
+            return 0
+        }
+        // On split-shade, the translationY of the keyguard should stay in sync with the
+        // translation of media.
+        if (mediaHierarchyManager.isCurrentlyInGuidedTransformation()) {
+            return mediaHierarchyManager.getGuidedTransformationTranslationY()
+        }
+        // When media is not showing, apply the default distance
+        translationYProgress = MathUtils.saturate(dragDownAmount / keyguardTransitionDistance)
+        val translationY = translationYProgress * keyguardTransitionOffset
+        return translationY.toInt()
+    }
+
+    override fun dump(indentingPrintWriter: IndentingPrintWriter) {
+        indentingPrintWriter.let {
+            it.println("LockscreenShadeKeyguardTransitionController:")
+            it.increaseIndent()
+            it.println("Resources:")
+            it.increaseIndent()
+            it.println("alphaTransitionDistance: $alphaTransitionDistance")
+            it.println("keyguardTransitionDistance: $keyguardTransitionDistance")
+            it.println("keyguardTransitionOffset: $keyguardTransitionOffset")
+            it.decreaseIndent()
+            it.println("State:")
+            it.increaseIndent()
+            it.println("dragDownAmount: $dragDownAmount")
+            it.println("alpha: $alpha")
+            it.println("alphaProgress: $alphaProgress")
+            it.println("statusBarAlpha: $statusBarAlpha")
+            it.println("translationProgress: $translationYProgress")
+            it.println("translationY: $translationY")
+        }
+    }
+
+    @AssistedFactory
+    fun interface Factory {
+        fun create(
+            notificationPanelController: NotificationPanelViewController
+        ): LockscreenShadeKeyguardTransitionController
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeScrimTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeScrimTransitionController.kt
new file mode 100644
index 0000000..00d3701
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeScrimTransitionController.kt
@@ -0,0 +1,86 @@
+package com.android.systemui.statusbar
+
+import android.content.Context
+import android.util.IndentingPrintWriter
+import android.util.MathUtils
+import com.android.systemui.R
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.phone.ScrimController
+import com.android.systemui.statusbar.policy.ConfigurationController
+import javax.inject.Inject
+
+/** Controls the lockscreen to shade transition for scrims. */
+class LockscreenShadeScrimTransitionController
+@Inject
+constructor(
+    private val scrimController: ScrimController,
+    context: Context,
+    configurationController: ConfigurationController,
+    dumpManager: DumpManager
+) : AbstractLockscreenShadeTransitionController(context, configurationController, dumpManager) {
+
+    /**
+     * Distance that the full shade transition takes in order for scrim to fully transition to the
+     * shade (in alpha)
+     */
+    private var scrimTransitionDistance = 0
+
+    /** Distance it takes in order for the notifications scrim fade in to start. */
+    private var notificationsScrimTransitionDelay = 0
+
+    /** Distance it takes for the notifications scrim to fully fade if after it started. */
+    private var notificationsScrimTransitionDistance = 0
+
+    /** The latest progress [0,1] the scrims transition. */
+    var scrimProgress = 0f
+
+    /** The latest progress [0,1] specifically of the notifications scrim transition. */
+    var notificationsScrimProgress = 0f
+
+    /**
+     * The last drag amount specifically for the notifications scrim. It is different to the normal
+     * [dragDownAmount] as the notifications scrim transition starts relative to the other scrims'
+     * progress.
+     */
+    var notificationsScrimDragAmount = 0f
+
+    override fun updateResources() {
+        scrimTransitionDistance =
+            context.resources.getDimensionPixelSize(
+                R.dimen.lockscreen_shade_scrim_transition_distance)
+        notificationsScrimTransitionDelay =
+            context.resources.getDimensionPixelSize(
+                R.dimen.lockscreen_shade_notifications_scrim_transition_delay)
+        notificationsScrimTransitionDistance =
+            context.resources.getDimensionPixelSize(
+                R.dimen.lockscreen_shade_notifications_scrim_transition_distance)
+    }
+
+    override fun onDragDownAmountChanged(dragDownAmount: Float) {
+        scrimProgress = MathUtils.saturate(dragDownAmount / scrimTransitionDistance)
+        notificationsScrimDragAmount = dragDownAmount - notificationsScrimTransitionDelay
+        notificationsScrimProgress =
+            MathUtils.saturate(notificationsScrimDragAmount / notificationsScrimTransitionDistance)
+        scrimController.setTransitionToFullShadeProgress(scrimProgress, notificationsScrimProgress)
+    }
+
+    override fun dump(indentingPrintWriter: IndentingPrintWriter) {
+        indentingPrintWriter.let {
+            it.println("LockscreenShadeScrimTransitionController:")
+            it.increaseIndent()
+            it.println("Resources:")
+            it.increaseIndent()
+            it.println("scrimTransitionDistance: $scrimTransitionDistance")
+            it.println("notificationsScrimTransitionDelay: $notificationsScrimTransitionDelay")
+            it.println(
+                "notificationsScrimTransitionDistance: $notificationsScrimTransitionDistance")
+            it.decreaseIndent()
+            it.println("State")
+            it.increaseIndent()
+            it.println("dragDownAmount: $dragDownAmount")
+            it.println("scrimProgress: $scrimProgress")
+            it.println("notificationsScrimProgress: $notificationsScrimProgress")
+            it.println("notificationsScrimDragAmount: $notificationsScrimDragAmount")
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index 63ab3de..b7d82c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -38,7 +38,6 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.phone.LSShadeTransitionLogger
 import com.android.systemui.statusbar.phone.NotificationPanelViewController
-import com.android.systemui.statusbar.phone.ScrimController
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.LargeScreenUtils
 import java.io.FileDescriptor
@@ -61,7 +60,9 @@
     private val falsingCollector: FalsingCollector,
     private val ambientState: AmbientState,
     private val mediaHierarchyManager: MediaHierarchyManager,
-    private val scrimController: ScrimController,
+    private val scrimTransitionController: LockscreenShadeScrimTransitionController,
+    private val keyguardTransitionControllerFactory:
+        LockscreenShadeKeyguardTransitionController.Factory,
     private val depthController: NotificationShadeDepthController,
     private val context: Context,
     private val splitShadeOverScrollerFactory: SplitShadeLockScreenOverScroller.Factory,
@@ -112,22 +113,6 @@
     private var fullTransitionDistanceByTap = 0
 
     /**
-     * Distance that the full shade transition takes in order for scrim to fully transition to the
-     * shade (in alpha)
-     */
-    private var scrimTransitionDistance = 0
-
-    /**
-     * Distance that it takes in order for the notifications scrim fade in to start.
-     */
-    private var notificationsScrimTransitionDelay = 0
-
-    /**
-     * Distance that it takes for the notifications scrim to fully fade if after it started.
-     */
-    private var notificationsScrimTransitionDistance = 0
-
-    /**
      * Distance that the full shade transition takes in order for the notification shelf to fully
      * expand.
      */
@@ -140,12 +125,6 @@
     private var qsTransitionDistance = 0
 
     /**
-     * Distance that the full shade transition takes in order for the keyguard content on
-     * NotificationPanelViewController to fully fade (e.g. Clock & Smartspace).
-     */
-    private var npvcKeyguardContentAlphaTransitionDistance = 0
-
-    /**
      * Distance that the full shade transition takes in order for depth of the wallpaper to fully
      * change.
      */
@@ -164,17 +143,6 @@
     private var statusBarTransitionDistance = 0
 
     /**
-     * Distance that the full shade transition takes in order for the keyguard elements to fully
-     * translate into their final position
-     */
-    private var keyguardTransitionDistance = 0
-
-    /**
-     * The amount of vertical offset for the keyguard during the full shade transition.
-     */
-    private var keyguardTransitionOffset = 0
-
-    /**
      * Flag to make sure that the dragDownAmount is applied to the listeners even when in the
      * locked down shade.
      */
@@ -215,6 +183,10 @@
         singleShadeOverScrollerFactory.create(nsslController)
     }
 
+    private val keyguardTransitionController by lazy {
+        keyguardTransitionControllerFactory.create(notificationPanelController)
+    }
+
     /**
      * [LockScreenShadeOverScroller] property that delegates to either
      * [SingleShadeLockScreenOverScroller] or [SplitShadeLockScreenOverScroller].
@@ -267,18 +239,10 @@
                 R.dimen.lockscreen_shade_full_transition_distance)
         fullTransitionDistanceByTap = context.resources.getDimensionPixelSize(
             R.dimen.lockscreen_shade_transition_by_tap_distance)
-        scrimTransitionDistance = context.resources.getDimensionPixelSize(
-                R.dimen.lockscreen_shade_scrim_transition_distance)
-        notificationsScrimTransitionDelay = context.resources.getDimensionPixelSize(
-                R.dimen.lockscreen_shade_notifications_scrim_transition_delay)
-        notificationsScrimTransitionDistance = context.resources.getDimensionPixelSize(
-                R.dimen.lockscreen_shade_notifications_scrim_transition_distance)
         notificationShelfTransitionDistance = context.resources.getDimensionPixelSize(
                 R.dimen.lockscreen_shade_notif_shelf_transition_distance)
         qsTransitionDistance = context.resources.getDimensionPixelSize(
                 R.dimen.lockscreen_shade_qs_transition_distance)
-        npvcKeyguardContentAlphaTransitionDistance = context.resources.getDimensionPixelSize(
-                R.dimen.lockscreen_shade_npvc_keyguard_content_alpha_transition_distance)
         depthControllerTransitionDistance = context.resources.getDimensionPixelSize(
                 R.dimen.lockscreen_shade_depth_controller_transition_distance)
         udfpsTransitionDistance = context.resources.getDimensionPixelSize(
@@ -286,10 +250,6 @@
         statusBarTransitionDistance = context.resources.getDimensionPixelSize(
                 R.dimen.lockscreen_shade_status_bar_transition_distance)
         useSplitShade = LargeScreenUtils.shouldUseSplitNotificationShade(context.resources)
-        keyguardTransitionDistance = context.resources.getDimensionPixelSize(
-            R.dimen.lockscreen_shade_keyguard_transition_distance)
-        keyguardTransitionOffset = context.resources.getDimensionPixelSize(
-            R.dimen.lockscreen_shade_keyguard_transition_vertical_offset)
     }
 
     fun setStackScroller(nsslController: NotificationStackScrollLayoutController) {
@@ -457,9 +417,9 @@
                             false /* animate */, 0 /* delay */)
 
                     mediaHierarchyManager.setTransitionToFullShadeAmount(field)
-                    transitionToShadeAmountScrim(field)
+                    scrimTransitionController.dragDownAmount = value
                     transitionToShadeAmountCommon(field)
-                    transitionToShadeAmountKeyguard(field)
+                    keyguardTransitionController.dragDownAmount = value
                     shadeOverScroller.expansionDragDownAmount = dragDownAmount
                 }
             }
@@ -471,14 +431,6 @@
     var qSDragProgress = 0f
         private set
 
-    private fun transitionToShadeAmountScrim(dragDownAmount: Float) {
-        val scrimProgress = MathUtils.saturate(dragDownAmount / scrimTransitionDistance)
-        val notificationsScrimDragAmount = dragDownAmount - notificationsScrimTransitionDelay
-        val notificationsScrimProgress = MathUtils.saturate(
-                notificationsScrimDragAmount / notificationsScrimTransitionDistance)
-        scrimController.setTransitionToFullShadeProgress(scrimProgress, notificationsScrimProgress)
-    }
-
     private fun transitionToShadeAmountCommon(dragDownAmount: Float) {
         if (depthControllerTransitionDistance == 0) { // split shade
             depthController.transitionToFullShadeProgress = 0f
@@ -495,34 +447,6 @@
         centralSurfaces.setTransitionToFullShadeProgress(statusBarProgress)
     }
 
-    private fun transitionToShadeAmountKeyguard(dragDownAmount: Float) {
-        // Fade out all content only visible on the lockscreen
-        val keyguardAlphaProgress =
-            MathUtils.saturate(dragDownAmount / npvcKeyguardContentAlphaTransitionDistance)
-        val keyguardAlpha = 1f - keyguardAlphaProgress
-        val keyguardTranslationY = calculateKeyguardTranslationY(dragDownAmount)
-        notificationPanelController
-            .setKeyguardTransitionProgress(keyguardAlpha, keyguardTranslationY)
-
-        val statusBarAlpha = if (useSplitShade) keyguardAlpha else -1f
-        notificationPanelController.setKeyguardStatusBarAlpha(statusBarAlpha)
-    }
-
-    private fun calculateKeyguardTranslationY(dragDownAmount: Float): Int {
-        if (!useSplitShade) {
-            return 0
-        }
-        // On split-shade, the translationY of the keyguard should stay in sync with the
-        // translation of media.
-        if (mediaHierarchyManager.isCurrentlyInGuidedTransformation()) {
-            return mediaHierarchyManager.getGuidedTransformationTranslationY()
-        }
-        // When media is not showing, apply the default distance
-        val translationProgress = MathUtils.saturate(dragDownAmount / keyguardTransitionDistance)
-        val translationY = translationProgress * keyguardTransitionOffset
-        return translationY.toInt()
-    }
-
     private fun setDragDownAmountAnimated(
         target: Float,
         delay: Long = 0,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index d785059..27586b4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -60,6 +60,7 @@
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.settings.SecureSettings;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -84,6 +85,7 @@
 
     private final DeviceProvisionedController mDeviceProvisionedController;
     private final KeyguardStateController mKeyguardStateController;
+    private final SecureSettings mSecureSettings;
     private final Object mLock = new Object();
 
     // Lazy
@@ -187,6 +189,7 @@
     protected NotificationPresenter mPresenter;
     protected ContentObserver mLockscreenSettingsObserver;
     protected ContentObserver mSettingsObserver;
+    private boolean mHideSilentNotificationsOnLockscreen;
 
     private NotificationEntryManager getEntryManager() {
         if (mEntryManager == null) {
@@ -208,6 +211,7 @@
             @Main Handler mainHandler,
             DeviceProvisionedController deviceProvisionedController,
             KeyguardStateController keyguardStateController,
+            SecureSettings secureSettings,
             DumpManager dumpManager) {
         mContext = context;
         mMainHandler = mainHandler;
@@ -222,6 +226,7 @@
         mKeyguardManager = keyguardManager;
         mBroadcastDispatcher = broadcastDispatcher;
         mDeviceProvisionedController = deviceProvisionedController;
+        mSecureSettings = secureSettings;
         mKeyguardStateController = keyguardStateController;
 
         dumpManager.registerDumpable(this);
@@ -256,12 +261,18 @@
         };
 
         mContext.getContentResolver().registerContentObserver(
-                Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS), false,
+                mSecureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS), false,
                 mLockscreenSettingsObserver,
                 UserHandle.USER_ALL);
 
         mContext.getContentResolver().registerContentObserver(
-                Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS),
+                mSecureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS),
+                true,
+                mLockscreenSettingsObserver,
+                UserHandle.USER_ALL);
+
+        mContext.getContentResolver().registerContentObserver(
+                mSecureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS),
                 true,
                 mLockscreenSettingsObserver,
                 UserHandle.USER_ALL);
@@ -272,7 +283,7 @@
 
         if (ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT) {
             mContext.getContentResolver().registerContentObserver(
-                    Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT),
+                    mSecureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT),
                     false,
                     mSettingsObserver,
                     UserHandle.USER_ALL);
@@ -366,7 +377,7 @@
             }
         }
         boolean exceedsPriorityThreshold;
-        if (hideSilentNotificationsOnLockscreen()) {
+        if (mHideSilentNotificationsOnLockscreen) {
             exceedsPriorityThreshold =
                     entry.getBucket() == BUCKET_MEDIA_CONTROLS
                             || (entry.getBucket() != BUCKET_SILENT
@@ -377,11 +388,6 @@
         return mShowLockscreenNotifications && exceedsPriorityThreshold;
     }
 
-    private boolean hideSilentNotificationsOnLockscreen() {
-        return whitelistIpcs(() -> Settings.Secure.getInt(mContext.getContentResolver(),
-                Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, 1) == 0);
-    }
-
     private void setShowLockscreenNotifications(boolean show) {
         mShowLockscreenNotifications = show;
     }
@@ -391,7 +397,7 @@
     }
 
     protected void updateLockscreenNotificationSetting() {
-        final boolean show = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+        final boolean show = mSecureSettings.getIntForUser(
                 Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
                 1,
                 mCurrentUserId) != 0;
@@ -400,10 +406,13 @@
         final boolean allowedByDpm = (dpmFlags
                 & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0;
 
+        mHideSilentNotificationsOnLockscreen = mSecureSettings.getIntForUser(
+                Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, 1, mCurrentUserId) == 0;
+
         setShowLockscreenNotifications(show && allowedByDpm);
 
         if (ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT) {
-            final boolean remoteInput = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+            final boolean remoteInput = mSecureSettings.getIntForUser(
                     Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT,
                     0,
                     mCurrentUserId) != 0;
@@ -426,8 +435,7 @@
         }
 
         if (mUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
-            final boolean allowedByUser = 0 != Settings.Secure.getIntForUser(
-                    mContext.getContentResolver(),
+            final boolean allowedByUser = 0 != mSecureSettings.getIntForUser(
                     Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, userHandle);
             final boolean allowedByDpm = adminAllowsKeyguardFeature(userHandle,
                     DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
@@ -492,8 +500,7 @@
         }
 
         if (mUsersAllowingNotifications.indexOfKey(userHandle) < 0) {
-            final boolean allowedByUser = 0 != Settings.Secure.getIntForUser(
-                    mContext.getContentResolver(),
+            final boolean allowedByUser = 0 != mSecureSettings.getIntForUser(
                     Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, userHandle);
             final boolean allowedByDpm = adminAllowsKeyguardFeature(userHandle,
                     DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 052c57e..1df4091 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -102,12 +102,14 @@
             KeyguardStateController.class);
     private final KeyguardBypassController mKeyguardBypassController;
     private static final HashSet<Integer> PAUSED_MEDIA_STATES = new HashSet<>();
+    private static final HashSet<Integer> CONNECTING_MEDIA_STATES = new HashSet<>();
     static {
         PAUSED_MEDIA_STATES.add(PlaybackState.STATE_NONE);
         PAUSED_MEDIA_STATES.add(PlaybackState.STATE_STOPPED);
         PAUSED_MEDIA_STATES.add(PlaybackState.STATE_PAUSED);
         PAUSED_MEDIA_STATES.add(PlaybackState.STATE_ERROR);
-        PAUSED_MEDIA_STATES.add(PlaybackState.STATE_CONNECTING);
+        CONNECTING_MEDIA_STATES.add(PlaybackState.STATE_CONNECTING);
+        CONNECTING_MEDIA_STATES.add(PlaybackState.STATE_BUFFERING);
     }
 
     private final NotificationVisibilityProvider mVisibilityProvider;
@@ -245,13 +247,12 @@
             @Override
             public void onMediaDataLoaded(@NonNull String key,
                     @Nullable String oldKey, @NonNull MediaData data, boolean immediately,
-                    int receivedSmartspaceCardLatency) {
+                    int receivedSmartspaceCardLatency, boolean isSsReactivated) {
             }
 
             @Override
             public void onSmartspaceMediaDataLoaded(@NonNull String key,
-                    @NonNull SmartspaceMediaData data, boolean shouldPrioritize,
-                    boolean isSsReactivated) {
+                    @NonNull SmartspaceMediaData data, boolean shouldPrioritize) {
             }
 
             @Override
@@ -320,13 +321,12 @@
             @Override
             public void onMediaDataLoaded(@NonNull String key,
                     @Nullable String oldKey, @NonNull MediaData data, boolean immediately,
-                    int receivedSmartspaceCardLatency) {
+                    int receivedSmartspaceCardLatency, boolean isSsReactivated) {
             }
 
             @Override
             public void onSmartspaceMediaDataLoaded(@NonNull String key,
-                    @NonNull SmartspaceMediaData data, boolean shouldPrioritize,
-                    boolean isSsReactivated) {
+                    @NonNull SmartspaceMediaData data, boolean shouldPrioritize) {
 
             }
 
@@ -365,7 +365,17 @@
      * @return true if playing
      */
     public static boolean isPlayingState(int state) {
-        return !PAUSED_MEDIA_STATES.contains(state);
+        return !PAUSED_MEDIA_STATES.contains(state)
+            && !CONNECTING_MEDIA_STATES.contains(state);
+    }
+
+    /**
+     * Check if a state should be considered as connecting
+     * @param state a PlaybackState
+     * @return true if connecting or buffering
+     */
+    public static boolean isConnectingState(int state) {
+        return CONNECTING_MEDIA_STATES.contains(state);
     }
 
     public void setUpWithPresenter(NotificationPresenter presenter) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index eaa66bb..633786f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -594,16 +594,13 @@
         } else {
             shouldClipOwnTop = view.showingPulsing();
         }
-        if (viewEnd > notificationClipEnd && !shouldClipOwnTop
-                && (mAmbientState.isShadeExpanded() || !isPinned)) {
-            int clipBottomAmount = (int) (viewEnd - notificationClipEnd);
-            if (isPinned) {
-                clipBottomAmount = Math.min(view.getIntrinsicHeight() - view.getCollapsedHeight(),
-                        clipBottomAmount);
+        if (!isPinned) {
+            if (viewEnd > notificationClipEnd && !shouldClipOwnTop) {
+                int clipBottomAmount = (int) (viewEnd - notificationClipEnd);
+                view.setClipBottomAmount(clipBottomAmount);
+            } else {
+                view.setClipBottomAmount(0);
             }
-            view.setClipBottomAmount(clipBottomAmount);
-        } else {
-            view.setClipBottomAmount(0);
         }
         if (shouldClipOwnTop) {
             return (int) (viewEnd - getTranslationY());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
index 4a6d7e1..8d7fc98 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
@@ -21,8 +21,6 @@
 
 import com.android.settingslib.WirelessUtils;
 
-import java.util.List;
-
 /** Shows the operator name */
 public class OperatorNameView extends TextView {
     private boolean mDemoMode;
@@ -43,8 +41,10 @@
         mDemoMode = demoMode;
     }
 
-    void update(boolean showOperatorName, boolean hasMobile,
-            List<OperatorNameViewController.SubInfo> subs) {
+    void update(boolean showOperatorName,
+            boolean hasMobile,
+            OperatorNameViewController.SubInfo sub
+    ) {
         setVisibility(showOperatorName ? VISIBLE : GONE);
 
         boolean airplaneMode = WirelessUtils.isAirplaneModeOn(mContext);
@@ -55,24 +55,21 @@
         }
 
         if (!mDemoMode) {
-            updateText(subs);
+            updateText(sub);
         }
     }
 
-    void updateText(List<OperatorNameViewController.SubInfo> subs) {
+    void updateText(OperatorNameViewController.SubInfo subInfo) {
+        CharSequence carrierName = null;
         CharSequence displayText = null;
-        final int N = subs.size();
-        for (int i = 0; i < N; i++) {
-            OperatorNameViewController.SubInfo subInfo = subs.get(i);
-            CharSequence carrierName = subs.get(i).getCarrierName();
-            if (!TextUtils.isEmpty(carrierName) && subInfo.simReady()) {
-                if (subInfo.stateInService()) {
-                    displayText = subInfo.getCarrierName();
-                    break;
-                }
+        if (subInfo != null) {
+            carrierName = subInfo.getCarrierName();
+        }
+        if (!TextUtils.isEmpty(carrierName) && subInfo.simReady()) {
+            if (subInfo.stateInService()) {
+                displayText = carrierName;
             }
         }
-
         setText(displayText);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameViewController.java
index 8a4c4b5..8afc72f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameViewController.java
@@ -20,6 +20,7 @@
 import android.os.Bundle;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.view.View;
 
@@ -30,12 +31,11 @@
 import com.android.systemui.statusbar.connectivity.IconState;
 import com.android.systemui.statusbar.connectivity.NetworkController;
 import com.android.systemui.statusbar.connectivity.SignalCallback;
+import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
 import com.android.systemui.tuner.TunerService;
+import com.android.systemui.util.CarrierConfigTracker;
 import com.android.systemui.util.ViewController;
 
-import java.util.ArrayList;
-import java.util.List;
-
 import javax.inject.Inject;
 
 /** Controller for {@link OperatorNameView}. */
@@ -47,19 +47,22 @@
     private final TunerService mTunerService;
     private final TelephonyManager mTelephonyManager;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    private final CarrierConfigTracker mCarrierConfigTracker;
 
     private OperatorNameViewController(OperatorNameView view,
             DarkIconDispatcher darkIconDispatcher,
             NetworkController networkController,
             TunerService tunerService,
             TelephonyManager telephonyManager,
-            KeyguardUpdateMonitor keyguardUpdateMonitor) {
+            KeyguardUpdateMonitor keyguardUpdateMonitor,
+            CarrierConfigTracker carrierConfigTracker) {
         super(view);
         mDarkIconDispatcher = darkIconDispatcher;
         mNetworkController = networkController;
         mTunerService = tunerService;
         mTelephonyManager = telephonyManager;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+        mCarrierConfigTracker = carrierConfigTracker;
     }
 
     @Override
@@ -79,24 +82,22 @@
     }
 
     private void update() {
-        mView.update(mTunerService.getValue(KEY_SHOW_OPERATOR_NAME, 1) != 0,
-                mTelephonyManager.isDataCapable(), getSubInfos());
+        SubInfo defaultSubInfo = getDefaultSubInfo();
+        boolean showOperatorName =
+                mCarrierConfigTracker
+                        .getShowOperatorNameInStatusBarConfig(defaultSubInfo.getSubId())
+                        && (mTunerService.getValue(KEY_SHOW_OPERATOR_NAME, 1) != 0);
+        mView.update(showOperatorName, mTelephonyManager.isDataCapable(), getDefaultSubInfo());
     }
 
-    private List<SubInfo> getSubInfos() {
-        List<SubInfo> result = new ArrayList<>();
-        List<SubscriptionInfo> subscritionInfos =
-                mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(false);
-
-        for (SubscriptionInfo subscriptionInfo : subscritionInfos) {
-            int subId = subscriptionInfo.getSubscriptionId();
-            result.add(new SubInfo(
-                    subscriptionInfo.getCarrierName(),
-                    mKeyguardUpdateMonitor.getSimState(subId),
-                    mKeyguardUpdateMonitor.getServiceState(subId)));
-        }
-
-        return result;
+    private SubInfo getDefaultSubInfo() {
+        int defaultSubId = SubscriptionManager.getDefaultDataSubscriptionId();
+        SubscriptionInfo sI = mKeyguardUpdateMonitor.getSubscriptionInfoForSubId(defaultSubId);
+        return new SubInfo(
+                sI.getSubscriptionId(),
+                sI.getCarrierName(),
+                mKeyguardUpdateMonitor.getSimState(defaultSubId),
+                mKeyguardUpdateMonitor.getServiceState(defaultSubId));
     }
 
     /** Factory for constructing an {@link OperatorNameViewController}. */
@@ -106,22 +107,32 @@
         private final TunerService mTunerService;
         private final TelephonyManager mTelephonyManager;
         private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+        private final CarrierConfigTracker mCarrierConfigTracker;
 
         @Inject
-        public Factory(DarkIconDispatcher darkIconDispatcher, NetworkController networkController,
-                TunerService tunerService, TelephonyManager telephonyManager,
-                KeyguardUpdateMonitor keyguardUpdateMonitor) {
+        public Factory(DarkIconDispatcher darkIconDispatcher,
+                NetworkController networkController,
+                TunerService tunerService,
+                TelephonyManager telephonyManager,
+                KeyguardUpdateMonitor keyguardUpdateMonitor,
+                CarrierConfigTracker carrierConfigTracker) {
             mDarkIconDispatcher = darkIconDispatcher;
             mNetworkController = networkController;
             mTunerService = tunerService;
             mTelephonyManager = telephonyManager;
             mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+            mCarrierConfigTracker = carrierConfigTracker;
         }
 
         /** Create an {@link OperatorNameViewController}. */
         public OperatorNameViewController create(OperatorNameView view) {
-            return new OperatorNameViewController(view, mDarkIconDispatcher, mNetworkController,
-                    mTunerService, mTelephonyManager, mKeyguardUpdateMonitor);
+            return new OperatorNameViewController(view,
+                    mDarkIconDispatcher,
+                    mNetworkController,
+                    mTunerService,
+                    mTelephonyManager,
+                    mKeyguardUpdateMonitor,
+                    mCarrierConfigTracker);
         }
     }
 
@@ -152,7 +163,7 @@
             new KeyguardUpdateMonitorCallback() {
         @Override
         public void onRefreshCarrierInfo() {
-            mView.updateText(getSubInfos());
+            mView.updateText(getDefaultSubInfo());
         }
     };
 
@@ -176,17 +187,26 @@
     };
 
     static class SubInfo {
+        private final int mSubId;
         private final CharSequence mCarrierName;
         private final int mSimState;
         private final ServiceState mServiceState;
 
-        private SubInfo(CharSequence carrierName,
-                int simState, ServiceState serviceState) {
+        private SubInfo(
+                int subId,
+                CharSequence carrierName,
+                int simState,
+                ServiceState serviceState) {
+            mSubId = subId;
             mCarrierName = carrierName;
             mSimState = simState;
             mServiceState = serviceState;
         }
 
+        int getSubId() {
+            return mSubId;
+        }
+
         boolean simReady() {
             return mSimState == TelephonyManager.SIM_STATE_READY;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index 93103e2..e026c19 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -205,7 +205,7 @@
             }
             mLastState = mState;
             mState = state;
-            mUpcomingState = state;
+            updateUpcomingState(mState);
             mUiEventLogger.log(StatusBarStateEvent.fromState(mState));
             Trace.instantForTrack(Trace.TRACE_TAG_APP, "UI Events", "StatusBarState " + tag);
             for (RankedListener rl : new ArrayList<>(mListeners)) {
@@ -223,8 +223,18 @@
 
     @Override
     public void setUpcomingState(int nextState) {
-        mUpcomingState = nextState;
-        recordHistoricalState(mUpcomingState /* newState */, mState /* lastState */, true);
+        recordHistoricalState(nextState /* newState */, mState /* lastState */, true);
+        updateUpcomingState(nextState);
+
+    }
+
+    private void updateUpcomingState(int upcomingState) {
+        if (mUpcomingState != upcomingState) {
+            mUpcomingState = upcomingState;
+            for (RankedListener rl : new ArrayList<>(mListeners)) {
+                rl.mListener.onUpcomingStateChanged(mUpcomingState);
+            }
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityState.kt
index 9c3c10c..b66e175 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityState.kt
@@ -46,6 +46,34 @@
         }
     }
 
+    protected open fun tableColumns(): List<String> {
+        return listOf(
+            "connected",
+            "enabled",
+            "activityIn",
+            "activityOut",
+            "level",
+            "iconGroup",
+            "inetCondition",
+            "rssi",
+            "time")
+    }
+
+    protected open fun tableData(): List<String> {
+        return listOf(
+            connected,
+            enabled,
+            activityIn,
+            activityOut,
+            level,
+            iconGroup,
+            inetCondition,
+            rssi,
+            sSDF.format(time)).map {
+                it.toString()
+        }
+    }
+
     protected open fun copyFrom(other: ConnectivityState) {
         connected = other.connected
         enabled = other.enabled
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java
index 41d2b65..9d8667a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java
@@ -828,6 +828,8 @@
                     + (mMobileStatusHistoryIndex + STATUS_HISTORY_SIZE - i) + "): "
                     + mMobileStatusHistory[i & (STATUS_HISTORY_SIZE - 1)]);
         }
+
+        dumpTableData(pw);
     }
 
     /** Box for QS icon info */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt
index 8a3b006..f20d206 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt
@@ -162,6 +162,52 @@
         builder.append("displayInfo=$telephonyDisplayInfo")
     }
 
+    override fun tableColumns(): List<String> {
+        val columns = listOf("dataSim",
+            "networkName",
+            "networkNameData",
+            "dataConnected",
+            "roaming",
+            "isDefault",
+            "isEmergency",
+            "airplaneMode",
+            "carrierNetworkChangeMode",
+            "userSetup",
+            "dataState",
+            "defaultDataOff",
+            "showQuickSettingsRatIcon",
+            "voiceServiceState",
+            "isInService",
+            "serviceState",
+            "signalStrength",
+            "displayInfo")
+
+        return super.tableColumns() + columns
+    }
+
+    override fun tableData(): List<String> {
+        val columns = listOf(dataSim,
+                networkName,
+                networkNameData,
+                dataConnected,
+                roaming,
+                isDefault,
+                isEmergency,
+                airplaneMode,
+                carrierNetworkChangeMode,
+                userSetup,
+                dataState,
+                defaultDataOff,
+                showQuickSettingsRatIcon(),
+                getVoiceServiceState(),
+                isInService(),
+                serviceState?.minLog() ?: "(null)",
+                signalStrength?.minLog() ?: "(null)",
+                telephonyDisplayInfo).map { it.toString() }
+
+        return super.tableData() + columns
+    }
+
     override fun equals(other: Any?): Boolean {
         if (this === other) return true
         if (javaClass != other?.javaClass) return false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/SignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/SignalController.java
index cd20068..e2806a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/SignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/SignalController.java
@@ -22,9 +22,12 @@
 import android.util.Log;
 
 import com.android.settingslib.SignalIcon.IconGroup;
+import com.android.systemui.dump.DumpsysTableLogger;
 
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.BitSet;
+import java.util.List;
 
 
 /**
@@ -193,20 +196,68 @@
         pw.println("  - " + mTag + " -----");
         pw.println("  Current State: " + mCurrentState);
         if (RECORD_HISTORY) {
-            // Count up the states that actually contain time stamps, and only display those.
-            int size = 0;
-            for (int i = 0; i < HISTORY_SIZE; i++) {
-                if (mHistory[i].time != 0) size++;
-            }
-            // Print out the previous states in ordered number.
-            for (int i = mHistoryIndex + HISTORY_SIZE - 1;
-                    i >= mHistoryIndex + HISTORY_SIZE - size; i--) {
-                pw.println("  Previous State(" + (mHistoryIndex + HISTORY_SIZE - i) + "): "
-                        + mHistory[i & (HISTORY_SIZE - 1)]);
+            List<ConnectivityState> history = getOrderedHistoryExcludingCurrentState();
+            for (int i = 0; i < history.size(); i++) {
+                pw.println("  Previous State(" + (i + 1) + "): " + mHistory[i]);
             }
         }
     }
 
+    /**
+     * mHistory is a ring, so use this method to get the time-ordered (from youngest to oldest)
+     * list of historical states. Filters out any state whose `time` is `0`.
+     *
+     * For ease of compatibility, this list returns JUST the historical states, not the current
+     * state which has yet to be copied into the history
+     *
+     * @see #getOrderedHistory()
+     * @return historical states, ordered from newest to oldest
+     */
+    List<ConnectivityState> getOrderedHistoryExcludingCurrentState() {
+        ArrayList<ConnectivityState> history = new ArrayList<>();
+
+        // Count up the states that actually contain time stamps, and only display those.
+        int size = 0;
+        for (int i = 0; i < HISTORY_SIZE; i++) {
+            if (mHistory[i].time != 0) size++;
+        }
+        // Print out the previous states in ordered number.
+        for (int i = mHistoryIndex + HISTORY_SIZE - 1;
+                i >= mHistoryIndex + HISTORY_SIZE - size; i--) {
+            history.add(mHistory[i & (HISTORY_SIZE - 1)]);
+        }
+
+        return history;
+    }
+
+    /**
+     * Get the ordered history states, including the current yet-to-be-copied state. Useful for
+     * logging
+     *
+     * @see #getOrderedHistoryExcludingCurrentState()
+     * @return [currentState, historicalState...] array
+     */
+    List<ConnectivityState> getOrderedHistory() {
+        ArrayList<ConnectivityState> history = new ArrayList<>();
+        // Start with the current state
+        history.add(mCurrentState);
+        history.addAll(getOrderedHistoryExcludingCurrentState());
+        return history;
+    }
+
+    void dumpTableData(PrintWriter pw) {
+        List<List<String>> tableData = new ArrayList<List<String>>();
+        List<ConnectivityState> history = getOrderedHistory();
+        for (int i = 0; i < history.size(); i++) {
+            tableData.add(history.get(i).tableData());
+        }
+
+        DumpsysTableLogger logger =
+                new DumpsysTableLogger(mTag, mCurrentState.tableColumns(), tableData);
+
+        logger.printTableData(pw);
+    }
+
     final void notifyListeners() {
         notifyListeners(mCallbackHandler);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java
index b80df4a..a4589c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java
@@ -241,6 +241,7 @@
     public void dump(PrintWriter pw) {
         super.dump(pw);
         mWifiTracker.dump(pw);
+        dumpTableData(pw);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiState.kt
index ac15f78..d32e349 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiState.kt
@@ -48,6 +48,30 @@
                 .append(",subId=").append(subId)
     }
 
+    override fun tableColumns(): List<String> {
+        val columns = listOf("ssid",
+                "isTransient",
+                "isDefault",
+                "statusLabel",
+                "isCarrierMerged",
+                "subId")
+
+        return super.tableColumns() + columns
+    }
+
+    override fun tableData(): List<String> {
+        val data = listOf(ssid,
+        isTransient,
+        isDefault,
+        statusLabel,
+        isCarrierMerged,
+        subId).map {
+            it.toString()
+        }
+
+        return super.tableData() + data
+    }
+
     override fun equals(other: Any?): Boolean {
         if (this === other) return true
         if (javaClass != other?.javaClass) return false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
index b74140d..7800b4c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.R
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
 import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.util.animation.AnimationUtil.Companion.frames
 import javax.inject.Inject
 import kotlin.math.roundToInt
 
@@ -109,14 +110,14 @@
         initializeAnimRect()
 
         val alphaIn = ValueAnimator.ofFloat(0f, 1f).apply {
-            startDelay = 117
-            duration = 83
+            startDelay = 7.frames
+            duration = 5.frames
             interpolator = null
             addUpdateListener { currentAnimatedView?.view?.alpha = animatedValue as Float }
         }
         val moveIn = ValueAnimator.ofInt(chipMinWidth, chipWidth).apply {
-            startDelay = 117
-            duration = 383
+            startDelay = 7.frames
+            duration = 23.frames
             interpolator = STATUS_BAR_X_MOVE_IN
             addUpdateListener {
                 updateAnimatedViewBoundsWidth(animatedValue as Int)
@@ -146,7 +147,7 @@
 
     private fun createMoveOutAnimationForDot(): Animator {
         val width1 = ValueAnimator.ofInt(chipWidth, chipMinWidth).apply {
-            duration = 150
+            duration = 9.frames
             interpolator = STATUS_CHIP_WIDTH_TO_DOT_KEYFRAME_1
             addUpdateListener {
                 updateAnimatedViewBoundsWidth(it.animatedValue as Int)
@@ -154,8 +155,8 @@
         }
 
         val width2 = ValueAnimator.ofInt(chipMinWidth, dotSize).apply {
-            startDelay = 150
-            duration = 333
+            startDelay = 9.frames
+            duration = 20.frames
             interpolator = STATUS_CHIP_WIDTH_TO_DOT_KEYFRAME_2
             addUpdateListener {
                 updateAnimatedViewBoundsWidth(it.animatedValue as Int)
@@ -167,8 +168,8 @@
         val chipVerticalCenter = v.top + v.measuredHeight / 2
         val height1 = ValueAnimator.ofInt(
                 currentAnimatedView!!.view.measuredHeight, keyFrame1Height).apply {
-            startDelay = 133
-            duration = 100
+            startDelay = 8.frames
+            duration = 6.frames
             interpolator = STATUS_CHIP_HEIGHT_TO_DOT_KEYFRAME_1
             addUpdateListener {
                 updateAnimatedViewBoundsHeight(it.animatedValue as Int, chipVerticalCenter)
@@ -176,8 +177,8 @@
         }
 
         val height2 = ValueAnimator.ofInt(keyFrame1Height, dotSize).apply {
-            startDelay = 233
-            duration = 250
+            startDelay = 14.frames
+            duration = 15.frames
             interpolator = STATUS_CHIP_HEIGHT_TO_DOT_KEYFRAME_2
             addUpdateListener {
                 updateAnimatedViewBoundsHeight(it.animatedValue as Int, chipVerticalCenter)
@@ -187,8 +188,8 @@
         // Move the chip view to overlap exactly with the privacy dot. The chip displays by default
         // exactly adjacent to the dot, so we can just move over by the diameter of the dot itself
         val moveOut = ValueAnimator.ofInt(0, dotSize).apply {
-            startDelay = 50
-            duration = 183
+            startDelay = 3.frames
+            duration = 11.frames
             interpolator = STATUS_CHIP_MOVE_TO_DOT
             addUpdateListener {
                 // If RTL, we can just invert the move
@@ -208,7 +209,7 @@
 
     private fun createMoveOutAnimationDefault(): Animator {
         val moveOut = ValueAnimator.ofInt(chipWidth, chipMinWidth).apply {
-            duration = 383
+            duration = 23.frames
             addUpdateListener {
                 currentAnimatedView?.apply {
                     updateAnimatedViewBoundsWidth(it.animatedValue as Int)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index 3b22f2a..2f1022a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -83,7 +83,6 @@
     private Runnable mOnPulseHeightChangedListener;
     private ExpandableNotificationRow mTrackedHeadsUpRow;
     private float mAppearFraction;
-    private boolean mIsShadeOpening;
     private float mOverExpansion;
     private int mStackTopMargin;
 
@@ -219,14 +218,6 @@
         mBaseZHeight = getBaseHeight(mZDistanceBetweenElements);
     }
 
-    public void setIsShadeOpening(boolean isOpening) {
-        mIsShadeOpening = isOpening;
-    }
-
-    public boolean isShadeOpening() {
-        return mIsShadeOpening;
-    }
-
     void setOverExpansion(float overExpansion) {
         mOverExpansion = overExpansion;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
index b95d153..7f3381c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
@@ -34,10 +34,13 @@
 
     private static final int TAG_ANIMATOR_HEIGHT = R.id.height_animator_tag;
     private static final int TAG_ANIMATOR_TOP_INSET = R.id.top_inset_animator_tag;
+    private static final int TAG_ANIMATOR_BOTTOM_INSET = R.id.bottom_inset_animator_tag;
     private static final int TAG_END_HEIGHT = R.id.height_animator_end_value_tag;
     private static final int TAG_END_TOP_INSET = R.id.top_inset_animator_end_value_tag;
+    private static final int TAG_END_BOTTOM_INSET = R.id.bottom_inset_animator_end_value_tag;
     private static final int TAG_START_HEIGHT = R.id.height_animator_start_value_tag;
     private static final int TAG_START_TOP_INSET = R.id.top_inset_animator_start_value_tag;
+    private static final int TAG_START_BOTTOM_INSET = R.id.bottom_inset_animator_start_value_tag;
 
     // These are flags such that we can create masks for filtering.
 
@@ -96,12 +99,17 @@
     public boolean headsUpIsVisible;
 
     /**
-     * How much the child overlaps with the previous child on top. This is used to
-     * show the background properly when the child on top is translating away.
+     * How much the child overlaps on top with the child above.
      */
     public int clipTopAmount;
 
     /**
+     * How much the child overlaps on bottom with the child above. This is used to
+     * show the background properly when the child on top is translating away.
+     */
+    public int clipBottomAmount;
+
+    /**
      * The index of the view, only accounting for views not equal to GONE
      */
     public int notGoneIndex;
@@ -138,8 +146,8 @@
         if (view instanceof ExpandableView) {
             ExpandableView expandableView = (ExpandableView) view;
 
-            int height = expandableView.getActualHeight();
-            int newHeight = this.height;
+            final int height = expandableView.getActualHeight();
+            final int newHeight = this.height;
 
             // apply height
             if (height != newHeight) {
@@ -157,10 +165,14 @@
             expandableView.setBelowSpeedBump(this.belowSpeedBump);
 
             // apply clipping
-            float oldClipTopAmount = expandableView.getClipTopAmount();
+            final float oldClipTopAmount = expandableView.getClipTopAmount();
             if (oldClipTopAmount != this.clipTopAmount) {
                 expandableView.setClipTopAmount(this.clipTopAmount);
             }
+            final float oldClipBottomAmount = expandableView.getClipBottomAmount();
+            if (oldClipBottomAmount != this.clipBottomAmount) {
+                expandableView.setClipBottomAmount(this.clipBottomAmount);
+            }
 
             expandableView.setTransformingInShelf(false);
             expandableView.setInShelf(inShelf);
@@ -187,13 +199,20 @@
             abortAnimation(child, TAG_ANIMATOR_HEIGHT);
         }
 
-        // start top inset animation
+        // start clip top animation
         if (this.clipTopAmount != expandableView.getClipTopAmount()) {
-            startInsetAnimation(expandableView, properties);
+            startClipAnimation(expandableView, properties, /* clipTop */true);
         } else {
             abortAnimation(child, TAG_ANIMATOR_TOP_INSET);
         }
 
+        // start clip bottom animation
+        if (this.clipBottomAmount != expandableView.getClipBottomAmount()) {
+            startClipAnimation(expandableView, properties, /* clipTop */ false);
+        } else {
+            abortAnimation(child, TAG_ANIMATOR_BOTTOM_INSET);
+        }
+
         // start dimmed animation
         expandableView.setDimmed(this.dimmed, animationFilter.animateDimmed);
 
@@ -301,16 +320,20 @@
         child.setActualHeightAnimating(true);
     }
 
-    private void startInsetAnimation(final ExpandableView child, AnimationProperties properties) {
-        Integer previousStartValue = getChildTag(child, TAG_START_TOP_INSET);
-        Integer previousEndValue = getChildTag(child, TAG_END_TOP_INSET);
-        int newEndValue = this.clipTopAmount;
+    private void startClipAnimation(final ExpandableView child, AnimationProperties properties,
+            boolean clipTop) {
+        Integer previousStartValue = getChildTag(child,
+                clipTop ? TAG_START_TOP_INSET : TAG_START_BOTTOM_INSET);
+        Integer previousEndValue = getChildTag(child,
+                clipTop ? TAG_END_TOP_INSET : TAG_END_BOTTOM_INSET);
+        int newEndValue = clipTop ? this.clipTopAmount : this.clipBottomAmount;
         if (previousEndValue != null && previousEndValue == newEndValue) {
             return;
         }
-        ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TOP_INSET);
+        ValueAnimator previousAnimator = getChildTag(child,
+                clipTop ? TAG_ANIMATOR_TOP_INSET : TAG_ANIMATOR_BOTTOM_INSET);
         AnimationFilter filter = properties.getAnimationFilter();
-        if (!filter.animateTopInset) {
+        if (clipTop && !filter.animateTopInset || !clipTop) {
             // just a local update was performed
             if (previousAnimator != null) {
                 // we need to increase all animation keyframes of the previous animator by the
@@ -319,22 +342,28 @@
                 int relativeDiff = newEndValue - previousEndValue;
                 int newStartValue = previousStartValue + relativeDiff;
                 values[0].setIntValues(newStartValue, newEndValue);
-                child.setTag(TAG_START_TOP_INSET, newStartValue);
-                child.setTag(TAG_END_TOP_INSET, newEndValue);
+                child.setTag(clipTop ? TAG_START_TOP_INSET : TAG_START_BOTTOM_INSET, newStartValue);
+                child.setTag(clipTop ? TAG_END_TOP_INSET : TAG_END_BOTTOM_INSET, newEndValue);
                 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
                 return;
             } else {
                 // no new animation needed, let's just apply the value
-                child.setClipTopAmount(newEndValue);
+                if (clipTop) {
+                    child.setClipTopAmount(newEndValue);
+                } else {
+                    child.setClipBottomAmount(newEndValue);
+                }
                 return;
             }
         }
 
-        ValueAnimator animator = ValueAnimator.ofInt(child.getClipTopAmount(), newEndValue);
-        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
+        ValueAnimator animator = ValueAnimator.ofInt(
+                clipTop ? child.getClipTopAmount() : child.getClipBottomAmount(), newEndValue);
+        animator.addUpdateListener(animation -> {
+            if (clipTop) {
                 child.setClipTopAmount((int) animation.getAnimatedValue());
+            } else {
+                child.setClipBottomAmount((int) animation.getAnimatedValue());
             }
         });
         animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
@@ -353,15 +382,16 @@
         animator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
-                child.setTag(TAG_ANIMATOR_TOP_INSET, null);
-                child.setTag(TAG_START_TOP_INSET, null);
-                child.setTag(TAG_END_TOP_INSET, null);
+                child.setTag(clipTop ? TAG_ANIMATOR_TOP_INSET : TAG_ANIMATOR_BOTTOM_INSET, null);
+                child.setTag(clipTop ? TAG_START_TOP_INSET : TAG_START_BOTTOM_INSET, null);
+                child.setTag(clipTop ? TAG_END_TOP_INSET : TAG_END_BOTTOM_INSET, null);
             }
         });
         startAnimator(animator, listener);
-        child.setTag(TAG_ANIMATOR_TOP_INSET, animator);
-        child.setTag(TAG_START_TOP_INSET, child.getClipTopAmount());
-        child.setTag(TAG_END_TOP_INSET, newEndValue);
+        child.setTag(clipTop ? TAG_ANIMATOR_TOP_INSET:TAG_ANIMATOR_BOTTOM_INSET, animator);
+        child.setTag(clipTop ? TAG_START_TOP_INSET: TAG_START_BOTTOM_INSET,
+                clipTop ? child.getClipTopAmount() : child.getClipBottomAmount());
+        child.setTag(clipTop ? TAG_END_TOP_INSET: TAG_END_BOTTOM_INSET, newEndValue);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index c0553b5..8271a88 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -427,6 +427,7 @@
     private boolean mInHeadsUpPinnedMode;
     private boolean mHeadsUpAnimatingAway;
     private int mStatusBarState;
+    private int mUpcomingStatusBarState;
     private int mCachedBackgroundColor;
     private boolean mHeadsUpGoingAwayAnimationsAllowed = true;
     private Runnable mReflingAndAnimateScroll = () -> {
@@ -690,7 +691,8 @@
                 mController.hasActiveClearableNotifications(ROWS_ALL);
         boolean showFooterView = (showDismissView || mController.getVisibleNotificationCount() > 0)
                 && mIsCurrentUserSetup  // see: b/193149550
-                && mStatusBarState != StatusBarState.KEYGUARD
+                && !onKeyguard()
+                && mUpcomingStatusBarState != StatusBarState.KEYGUARD
                 // quick settings don't affect notifications when not in full screen
                 && (mQsExpansionFraction != 1 || !mQsFullScreen)
                 && !mScreenOffAnimationController.shouldHideNotificationsFooter()
@@ -1317,14 +1319,15 @@
         if (mOnStackYChanged != null) {
             mOnStackYChanged.accept(listenerNeedsAnimation);
         }
-        if ((mQsExpansionFraction <= 0 || !mQsFullScreen) && !shouldSkipHeightUpdate()) {
-            final float endHeight = updateStackEndHeight();
+        if (mQsExpansionFraction <= 0 && !shouldSkipHeightUpdate()) {
+            final float endHeight = updateStackEndHeight(
+                    getHeight(), getEmptyBottomMargin(), mTopPadding);
             updateStackHeight(endHeight, fraction);
         }
     }
 
-    private float updateStackEndHeight() {
-        final float stackEndHeight = Math.max(0f, mIntrinsicContentHeight);
+    private float updateStackEndHeight(float height, float bottomMargin, float topPadding) {
+        final float stackEndHeight = Math.max(0f, height - bottomMargin - topPadding);
         mAmbientState.setStackEndHeight(stackEndHeight);
         return stackEndHeight;
     }
@@ -4959,6 +4962,13 @@
         updateDismissBehavior();
     }
 
+    void setUpcomingStatusBarState(int upcomingStatusBarState) {
+        mUpcomingStatusBarState = upcomingStatusBarState;
+        if (mUpcomingStatusBarState != mStatusBarState) {
+            updateFooter();
+        }
+    }
+
     void onStatePostChange(boolean fromShadeLocked) {
         boolean onKeyguard = onKeyguard();
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 3e630cd..6d7c95f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -310,6 +310,11 @@
                 }
 
                 @Override
+                public void onUpcomingStateChanged(int newState) {
+                    mView.setUpcomingStatusBarState(newState);
+                }
+
+                @Override
                 public void onStatePostChange() {
                     mView.updateSensitiveness(mStatusBarStateController.goingToFullShade(),
                             mLockscreenUserManager.isAnyProfilePublicMode());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
index 6c6ed85..d68f371 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
@@ -139,6 +139,8 @@
                 height += spaceNeeded
                 count += 1
             } else {
+                val gapBeforeFirstViewInShelf = current.calculateGapHeight(stack, previous, count)
+                height += gapBeforeFirstViewInShelf
                 height += shelfHeight
                 log { "returning height with shelf -> $height" }
                 return height
@@ -178,7 +180,9 @@
         if (visibleIndex != 0) {
             size += notificationPadding
         }
-        size += calculateGapHeight(stack, previousView, visibleIndex)
+        val gapHeight = calculateGapHeight(stack, previousView, visibleIndex)
+        log { "\ti=$visibleIndex gapHeight=$gapHeight"}
+        size += gapHeight
         return size
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index e1f8c35..c097133 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -192,6 +192,7 @@
         float clipStart = 0;
         int childCount = algorithmState.visibleChildren.size();
         boolean firstHeadsUp = true;
+        float firstHeadsUpEnd = 0;
         for (int i = 0; i < childCount; i++) {
             ExpandableView child = algorithmState.visibleChildren.get(i);
             ExpandableViewState state = child.getViewState();
@@ -203,14 +204,18 @@
             float newNotificationEnd = newYTranslation + newHeight;
             boolean isHeadsUp = (child instanceof ExpandableNotificationRow) && child.isPinned();
             if (mClipNotificationScrollToTop
-                    && (!state.inShelf || (isHeadsUp && !firstHeadsUp))
-                    && newYTranslation < clipStart
+                    && ((isHeadsUp && !firstHeadsUp) || child.isHeadsUpAnimatingAway())
+                    && newNotificationEnd > firstHeadsUpEnd
                     && !ambientState.isShadeExpanded()) {
-                // The previous view is overlapping on top, clip!
-                float overlapAmount = clipStart - newYTranslation;
-                state.clipTopAmount = (int) overlapAmount;
+                // The bottom of this view is peeking out from under the previous view.
+                // Clip the part that is peeking out.
+                float overlapAmount = newNotificationEnd - firstHeadsUpEnd;
+                state.clipBottomAmount = (int) overlapAmount;
             } else {
-                state.clipTopAmount = 0;
+                state.clipBottomAmount = 0;
+            }
+            if (firstHeadsUp) {
+                firstHeadsUpEnd = newNotificationEnd;
             }
             if (isHeadsUp) {
                 firstHeadsUp = false;
@@ -635,8 +640,6 @@
                     // Ensure that a headsUp doesn't vertically extend further than the heads-up at
                     // the top most z-position
                     childState.height = row.getIntrinsicHeight();
-                    childState.yTranslation = Math.min(topState.yTranslation + topState.height
-                            - childState.height, childState.yTranslation);
                 }
 
                 // heads up notification show and this row is the top entry of heads up
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index aab5ff8..f95093e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -166,7 +166,6 @@
     private final NotificationShadeWindowController mNotificationShadeWindowController;
     private final SessionTracker mSessionTracker;
     private final int mConsecutiveFpFailureThreshold;
-    private final int mWakeUpDelay;
     private int mMode;
     private BiometricSourceType mBiometricType;
     private KeyguardViewController mKeyguardViewController;
@@ -266,6 +265,7 @@
     }
 
     private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
+    private final ScreenOffAnimationController mScreenOffAnimationController;
 
     @Inject
     public BiometricUnlockController(DozeScrimController dozeScrimController,
@@ -285,7 +285,8 @@
             StatusBarStateController statusBarStateController,
             KeyguardUnlockAnimationController keyguardUnlockAnimationController,
             SessionTracker sessionTracker,
-            LatencyTracker latencyTracker) {
+            LatencyTracker latencyTracker,
+            ScreenOffAnimationController screenOffAnimationController) {
         mPowerManager = powerManager;
         mShadeController = shadeController;
         mUpdateMonitor = keyguardUpdateMonitor;
@@ -302,7 +303,6 @@
         mScrimController = scrimController;
         mKeyguardStateController = keyguardStateController;
         mHandler = handler;
-        mWakeUpDelay = resources.getInteger(com.android.internal.R.integer.config_wakeUpDelayDoze);
         mConsecutiveFpFailureThreshold = resources.getInteger(
                 R.integer.fp_consecutive_failure_time_ms);
         mKeyguardBypassController = keyguardBypassController;
@@ -312,6 +312,7 @@
         mStatusBarStateController = statusBarStateController;
         mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
         mSessionTracker = sessionTracker;
+        mScreenOffAnimationController = screenOffAnimationController;
         dumpManager.registerDumpable(getClass().getName(), this);
     }
 
@@ -433,7 +434,6 @@
         // During wake and unlock, we need to draw black before waking up to avoid abrupt
         // brightness changes due to display state transitions.
         boolean alwaysOnEnabled = mDozeParameters.getAlwaysOn();
-        boolean delayWakeUp = mode == MODE_WAKE_AND_UNLOCK && alwaysOnEnabled && mWakeUpDelay > 0;
         Runnable wakeUp = ()-> {
             if (!wasDeviceInteractive) {
                 if (DEBUG_BIO_WAKELOCK) {
@@ -442,15 +442,12 @@
                 mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
                         "android.policy:BIOMETRIC");
             }
-            if (delayWakeUp) {
-                mKeyguardViewMediator.onWakeAndUnlocking();
-            }
             Trace.beginSection("release wake-and-unlock");
             releaseBiometricWakeLock();
             Trace.endSection();
         };
 
-        if (!delayWakeUp && mMode != MODE_NONE) {
+        if (mMode != MODE_NONE) {
             wakeUp.run();
         }
         switch (mMode) {
@@ -504,11 +501,7 @@
                     mUpdateMonitor.awakenFromDream();
                 }
                 mNotificationShadeWindowController.setNotificationShadeFocusable(false);
-                if (delayWakeUp) {
-                    mHandler.postDelayed(wakeUp, mWakeUpDelay);
-                } else {
-                    mKeyguardViewMediator.onWakeAndUnlocking();
-                }
+                mKeyguardViewMediator.onWakeAndUnlocking();
                 Trace.endSection();
                 break;
             case MODE_ONLY_WAKE:
@@ -564,7 +557,8 @@
         boolean deviceDreaming = mUpdateMonitor.isDreaming();
 
         if (!mUpdateMonitor.isDeviceInteractive()) {
-            if (!mKeyguardViewController.isShowing()) {
+            if (!mKeyguardViewController.isShowing()
+                    && !mScreenOffAnimationController.isKeyguardShowDelayed()) {
                 return MODE_ONLY_WAKE;
             } else if (mDozeScrimController.isPulsing() && unlockingAllowed) {
                 return MODE_WAKE_AND_UNLOCK_PULSING;
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 ffe4d4f..1625d9b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -1388,8 +1388,11 @@
      * keyguard.
      */
     private void dispatchPanelExpansionForKeyguardDismiss(float fraction, boolean trackingTouch) {
-        // Things that mean we're not dismissing the keyguard, and should ignore this expansion:
+        // Things that mean we're not swiping to dismiss the keyguard, and should ignore this
+        // expansion:
         // - Keyguard isn't even visible.
+        // - Keyguard is occluded. Expansion changes here are the shade being expanded over the
+        //   occluding activity.
         // - Keyguard is visible, but can't be dismissed (swiping up will show PIN/password prompt).
         // - The SIM is locked, you can't swipe to unlock. If the SIM is locked but there is no
         //   device lock set, canDismissLockScreen returns true even though you should not be able
@@ -1397,6 +1400,7 @@
         // - QS is expanded and we're swiping - swiping up now will hide QS, not dismiss the
         //   keyguard.
         if (!isKeyguardShowing()
+                || isOccluded()
                 || !mKeyguardStateController.canDismissLockScreen()
                 || mKeyguardViewMediator.isAnySimPinSecure()
                 || (mNotificationPanelViewController.isQsExpanded() && trackingTouch)) {
@@ -1438,7 +1442,6 @@
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
         filter.addAction(Intent.ACTION_SCREEN_OFF);
-        filter.addAction(DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG);
         mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter, null, UserHandle.ALL);
     }
 
@@ -2628,8 +2631,6 @@
                 }
                 finishBarAnimations();
                 resetUserExpandedStates();
-            } else if (DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG.equals(action)) {
-                mQSPanelController.showDeviceMonitoringDialog();
             }
             Trace.endSection();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
index 64b0b4e..280e75c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
@@ -77,7 +77,7 @@
     }
 
     private val keyguardUpdateMonitorCallback = object : KeyguardUpdateMonitorCallback() {
-        override fun onKeyguardBouncerChanged(bouncer: Boolean) {
+        override fun onKeyguardBouncerFullyShowingChanged(bouncer: Boolean) {
             bouncerVisible = bouncer
             updateListeningState()
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 11628cb..6007323 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -239,7 +239,7 @@
 
     private final DozeParameters mDozeParameters;
     private final OnHeightChangedListener mOnHeightChangedListener = new OnHeightChangedListener();
-    private final OnClickListener mOnClickListener = new OnClickListener();
+    private final Runnable mCollapseExpandAction = new CollapseExpandAction();
     private final OnOverscrollTopChangedListener
             mOnOverscrollTopChangedListener =
             new OnOverscrollTopChangedListener();
@@ -1355,9 +1355,11 @@
         int userSwitcherPreferredY = mStatusBarHeaderHeightKeyguard;
         boolean bypassEnabled = mKeyguardBypassController.getBypassEnabled();
         final boolean hasVisibleNotifications = mNotificationStackScrollLayoutController
-                .getVisibleNotificationCount() != 0 || mMediaDataManager.hasActiveMedia();
+                .getVisibleNotificationCount() != 0
+                || mMediaDataManager.hasActiveMediaOrRecommendation();
         boolean splitShadeWithActiveMedia =
-                mShouldUseSplitNotificationShade && mMediaDataManager.hasActiveMedia();
+                mShouldUseSplitNotificationShade
+                        && mMediaDataManager.hasActiveMediaOrRecommendation();
         boolean shouldAnimateClockChange = mScreenOffAnimationController.shouldAnimateClockChange();
         if ((hasVisibleNotifications && !mShouldUseSplitNotificationShade)
                 || (splitShadeWithActiveMedia && !mDozing)) {
@@ -1424,7 +1426,8 @@
 
     private void updateKeyguardStatusViewAlignment(boolean animate) {
         boolean hasVisibleNotifications = mNotificationStackScrollLayoutController
-                .getVisibleNotificationCount() != 0 || mMediaDataManager.hasActiveMedia();
+                .getVisibleNotificationCount() != 0
+                || mMediaDataManager.hasActiveMediaOrRecommendation();
         boolean shouldBeCentered = !mShouldUseSplitNotificationShade || !hasVisibleNotifications
                 || mDozing;
         if (mStatusViewCentered != shouldBeCentered) {
@@ -2622,7 +2625,7 @@
         float endPosition = 0;
         if (pxAmount > 0.0f) {
             if (mNotificationStackScrollLayoutController.getVisibleNotificationCount() == 0
-                    && !mMediaDataManager.hasActiveMedia()) {
+                    && !mMediaDataManager.hasActiveMediaOrRecommendation()) {
                 // No notifications are visible, let's animate to the height of qs instead
                 if (mQs != null) {
                     // Let's interpolate to the header height instead of the top padding,
@@ -3570,7 +3573,7 @@
         public void onFragmentViewCreated(String tag, Fragment fragment) {
             mQs = (QS) fragment;
             mQs.setPanelView(mHeightListener);
-            mQs.setExpandClickListener(mOnClickListener);
+            mQs.setCollapseExpandAction(mCollapseExpandAction);
             mQs.setHeaderClickable(isQsExpansionEnabled());
             mQs.setOverscrolling(mStackScrollerOverscrolling);
             mQs.setInSplitShade(mShouldUseSplitNotificationShade);
@@ -4213,9 +4216,9 @@
         }
     }
 
-    private class OnClickListener implements View.OnClickListener {
+    private class CollapseExpandAction implements Runnable {
         @Override
-        public void onClick(View v) {
+        public void run() {
             onQsExpansionStarted();
             if (mQsExpanded) {
                 flingSettings(0 /* vel */, FLING_COLLAPSE, null /* onFinishRunnable */,
@@ -4900,7 +4903,6 @@
     private int mCurrentPanelState = STATE_CLOSED;
 
     private void onPanelStateChanged(@PanelState int state) {
-        mAmbientState.setIsShadeOpening(state == STATE_OPENING);
         updateQSExpansionEnabledAmbient();
 
         if (state == STATE_OPEN && mCurrentPanelState != state) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
index ffbf282..67b4dbb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
@@ -30,6 +30,7 @@
 import android.graphics.PixelFormat;
 import android.graphics.Region;
 import android.os.Binder;
+import android.os.Build;
 import android.os.RemoteException;
 import android.os.Trace;
 import android.util.Log;
@@ -327,6 +328,16 @@
             Trace.setCounter("display_max_refresh_rate",
                     (long) mLpChanged.preferredMaxDisplayRefreshRate);
         }
+
+        if (state.mBouncerShowing && !isDebuggable()) {
+            mLpChanged.flags |= LayoutParams.FLAG_SECURE;
+        } else {
+            mLpChanged.flags &= ~LayoutParams.FLAG_SECURE;
+        }
+    }
+
+    protected boolean isDebuggable() {
+        return Build.IS_DEBUGGABLE;
     }
 
     private void adjustScreenOrientation(State state) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt
index 745228e..491e9fd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt
@@ -168,14 +168,7 @@
     private fun updateBottomSpacing() {
         val (containerPadding, notificationsMargin) = calculateBottomSpacing()
         var qsScrollPaddingBottom = 0
-        val newFooter = featureFlags.isEnabled(Flags.NEW_FOOTER)
-        if (!newFooter && !(splitShadeEnabled || isQSCustomizing || isQSDetailShowing ||
-                        isGestureNavigation || taskbarVisible)) {
-            // no taskbar, portrait, navigation buttons enabled:
-            // padding is needed so QS can scroll up over bottom insets - to reach the point when
-            // the whole QS is above bottom insets
-            qsScrollPaddingBottom = bottomStableInsets
-        } else if (newFooter && !(isQSCustomizing || isQSDetailShowing)) {
+        if (!(isQSCustomizing || isQSDetailShowing)) {
             // With the new footer, we also want this padding in the bottom in these cases
             qsScrollPaddingBottom = if (splitShadeEnabled) {
                 notificationsMargin - scrimShadeBottomMargin
@@ -185,11 +178,7 @@
         }
         mView.setPadding(0, 0, 0, containerPadding)
         mView.setNotificationsMarginBottom(notificationsMargin)
-        if (newFooter) {
-            mView.setQSContainerPaddingBottom(qsScrollPaddingBottom)
-        } else {
-            mView.setQSScrollPaddingBottom(qsScrollPaddingBottom)
-        }
+        mView.setQSContainerPaddingBottom(qsScrollPaddingBottom)
     }
 
     private fun calculateBottomSpacing(): Pair<Int, Int> {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
index 7caea06..2446cf7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
@@ -103,16 +103,6 @@
         mStackScroller.setLayoutParams(params);
     }
 
-    public void setQSScrollPaddingBottom(int paddingBottom) {
-        if (mQSScrollView != null) {
-            mQSScrollView.setPaddingRelative(
-                    mQSScrollView.getPaddingLeft(),
-                    mQSScrollView.getPaddingTop(),
-                    mQSScrollView.getPaddingRight(),
-                    paddingBottom);
-        }
-    }
-
     public void setQSContainerPaddingBottom(int paddingBottom) {
         if (mQSContainer != null) {
             mQSContainer.setPadding(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 0b95458..28f2e61 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -1021,15 +1021,24 @@
         boolean aodWallpaperTimeout = (mState == ScrimState.AOD || mState == ScrimState.PULSING)
                 && mWallpaperVisibilityTimedOut;
         // We also want to hide FLAG_SHOW_WHEN_LOCKED activities under the scrim.
-        boolean occludedKeyguard = (mState == ScrimState.PULSING || mState == ScrimState.AOD)
+        boolean hideFlagShowWhenLockedActivities =
+                (mState == ScrimState.PULSING || mState == ScrimState.AOD)
                 && mKeyguardOccluded;
-        if (aodWallpaperTimeout || occludedKeyguard) {
+        if (aodWallpaperTimeout || hideFlagShowWhenLockedActivities) {
             mBehindAlpha = 1;
         }
         // Prevent notification scrim flicker when transitioning away from keyguard.
         if (mKeyguardStateController.isKeyguardGoingAway()) {
             mNotificationsAlpha = 0;
         }
+
+        // Prevent flickering for activities above keyguard and quick settings in keyguard.
+        if (mKeyguardOccluded
+                && (mState == ScrimState.KEYGUARD || mState == ScrimState.SHADE_LOCKED)) {
+            mBehindAlpha = 0;
+            mNotificationsAlpha = 0;
+        }
+
         setScrimAlpha(mScrimInFront, mInFrontAlpha);
         setScrimAlpha(mScrimBehind, mBehindAlpha);
         setScrimAlpha(mNotificationsScrim, mNotificationsAlpha);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index a1cbba4..ce1289e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -355,6 +355,12 @@
 
     @Override
     public void onPanelExpansionChanged(float fraction, boolean expanded, boolean tracking) {
+        // Avoid having the shade and the bouncer open at the same time over a dream.
+        final boolean hideBouncerOverDream =
+                mDreamOverlayStateController.isOverlayActive()
+                        && (mNotificationPanelViewController.isExpanded()
+                        || mNotificationPanelViewController.isExpanding());
+
         // We don't want to translate the bounce when:
         // • Keyguard is occluded, because we're in a FLAG_SHOW_WHEN_LOCKED activity and need to
         //   conserve the original animation.
@@ -371,7 +377,7 @@
             return;
         } else if (bouncerNeedsScrimming()) {
             mBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
-        } else if (mShowing) {
+        } else if (mShowing && !hideBouncerOverDream) {
             if (!isWakeAndUnlocking()
                     && !mCentralSurfaces.isInLaunchTransition()
                     && !isUnlockCollapsing()) {
@@ -1055,8 +1061,10 @@
         if ((showing && !occluded) != (mLastShowing && !mLastOccluded) || mFirstUpdate) {
             mKeyguardUpdateManager.onKeyguardVisibilityChanged(showing && !occluded);
         }
-        if (bouncerIsOrWillBeShowing != mLastBouncerIsOrWillBeShowing || mFirstUpdate) {
-            mKeyguardUpdateManager.sendKeyguardBouncerChanged(bouncerIsOrWillBeShowing);
+        if (bouncerIsOrWillBeShowing != mLastBouncerIsOrWillBeShowing || mFirstUpdate
+                || bouncerShowing != mLastBouncerShowing) {
+            mKeyguardUpdateManager.sendKeyguardBouncerChanged(bouncerIsOrWillBeShowing,
+                    bouncerShowing);
         }
 
         mFirstUpdate = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 6fe92fa..87ca942 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -489,7 +489,7 @@
             ExpandableNotificationRow row,
             boolean animate,
             boolean isActivityIntent) {
-        mLogger.logStartNotificationIntent(entry.getKey(), intent);
+        mLogger.logStartNotificationIntent(entry.getKey());
         try {
             Runnable onFinishAnimationCallback = animate
                     ? () -> mLaunchEventsEmitter.notifyFinishLaunchNotifActivity(entry)
@@ -513,8 +513,10 @@
                                 mKeyguardStateController.isShowing(),
                                 eventTime)
                                 : getActivityOptions(mCentralSurfaces.getDisplayId(), adapter);
-                        return intent.sendAndReturnResult(mContext, 0, fillInIntent, null,
+                        int result = intent.sendAndReturnResult(mContext, 0, fillInIntent, null,
                                 null, null, options);
+                        mLogger.logSendPendingIntent(entry.getKey(), intent, result);
+                        return result;
                     });
         } catch (PendingIntent.CanceledException e) {
             // the stack trace isn't very helpful here.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
index d118747..2fbe520 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
@@ -32,7 +32,7 @@
         buffer.log(TAG, DEBUG, {
             str1 = key
         }, {
-            "(1/4) onNotificationClicked: $str1"
+            "(1/5) onNotificationClicked: $str1"
         })
     }
 
@@ -40,7 +40,7 @@
         buffer.log(TAG, DEBUG, {
             str1 = key
         }, {
-            "(2/4) handleNotificationClickAfterKeyguardDismissed: $str1"
+            "(2/5) handleNotificationClickAfterKeyguardDismissed: $str1"
         })
     }
 
@@ -48,16 +48,25 @@
         buffer.log(TAG, DEBUG, {
             str1 = key
         }, {
-            "(3/4) handleNotificationClickAfterPanelCollapsed: $str1"
+            "(3/5) handleNotificationClickAfterPanelCollapsed: $str1"
         })
     }
 
-    fun logStartNotificationIntent(key: String, pendingIntent: PendingIntent) {
+    fun logStartNotificationIntent(key: String) {
+        buffer.log(TAG, INFO, {
+            str1 = key
+        }, {
+            "(4/5) startNotificationIntent: $str1"
+        })
+    }
+
+    fun logSendPendingIntent(key: String, pendingIntent: PendingIntent, result: Int) {
         buffer.log(TAG, INFO, {
             str1 = key
             str2 = pendingIntent.intent.toString()
+            int1 = result
         }, {
-            "(4/4) Starting $str2 for notification $str1"
+            "(5/5) Started intent $str2 for notification $str1 with result code $int1"
         })
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
index 6c6ec19..06532c4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
@@ -61,6 +61,7 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.tuner.TunerService;
+import com.android.systemui.util.CarrierConfigTracker;
 import com.android.systemui.util.settings.SecureSettings;
 
 import java.util.concurrent.Executor;
@@ -263,6 +264,7 @@
             NetworkController networkController,
             StatusBarStateController statusBarStateController,
             CommandQueue commandQueue,
+            CarrierConfigTracker carrierConfigTracker,
             CollapsedStatusBarFragmentLogger collapsedStatusBarFragmentLogger,
             OperatorNameViewController.Factory operatorNameViewControllerFactory,
             SecureSettings secureSettings,
@@ -282,6 +284,7 @@
                 networkController,
                 statusBarStateController,
                 commandQueue,
+                carrierConfigTracker,
                 collapsedStatusBarFragmentLogger,
                 operatorNameViewControllerFactory,
                 secureSettings,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 8194957..9e48b76 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -32,6 +32,7 @@
 import android.os.Bundle;
 import android.os.Parcelable;
 import android.provider.Settings;
+import android.telephony.SubscriptionManager;
 import android.util.SparseArray;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -69,6 +70,9 @@
 import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
 import com.android.systemui.statusbar.policy.EncryptionHelper;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.CarrierConfigTracker;
+import com.android.systemui.util.CarrierConfigTracker.CarrierConfigChangedListener;
+import com.android.systemui.util.CarrierConfigTracker.DefaultDataSubscriptionChangedListener;
 import com.android.systemui.util.settings.SecureSettings;
 
 import java.util.ArrayList;
@@ -115,6 +119,7 @@
     private final NotificationIconAreaController mNotificationIconAreaController;
     private final PanelExpansionStateManager mPanelExpansionStateManager;
     private final StatusBarIconController mStatusBarIconController;
+    private final CarrierConfigTracker mCarrierConfigTracker;
     private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
     private final SecureSettings mSecureSettings;
     private final Executor mMainExecutor;
@@ -137,6 +142,28 @@
     private OperatorNameViewController mOperatorNameViewController;
     private StatusBarSystemEventAnimator mSystemEventAnimator;
 
+    private final CarrierConfigChangedListener mCarrierConfigCallback =
+            new CarrierConfigChangedListener() {
+                @Override
+                public void onCarrierConfigChanged() {
+                    if (mOperatorNameViewController == null) {
+                        initOperatorName();
+                    } else {
+                        // Already initialized, KeyguardUpdateMonitorCallback will handle the update
+                    }
+                }
+            };
+
+    private final DefaultDataSubscriptionChangedListener mDefaultDataListener =
+            new DefaultDataSubscriptionChangedListener() {
+                @Override
+                public void onDefaultSubscriptionChanged(int subId) {
+                    if (mOperatorNameViewController == null) {
+                        initOperatorName();
+                    }
+                }
+            };
+
     @SuppressLint("ValidFragment")
     public CollapsedStatusBarFragment(
             StatusBarFragmentComponent.Factory statusBarFragmentComponentFactory,
@@ -153,6 +180,7 @@
             NetworkController networkController,
             StatusBarStateController statusBarStateController,
             CommandQueue commandQueue,
+            CarrierConfigTracker carrierConfigTracker,
             CollapsedStatusBarFragmentLogger collapsedStatusBarFragmentLogger,
             OperatorNameViewController.Factory operatorNameViewControllerFactory,
             SecureSettings secureSettings,
@@ -172,6 +200,7 @@
         mNetworkController = networkController;
         mStatusBarStateController = statusBarStateController;
         mCommandQueue = commandQueue;
+        mCarrierConfigTracker = carrierConfigTracker;
         mCollapsedStatusBarFragmentLogger = collapsedStatusBarFragmentLogger;
         mOperatorNameViewControllerFactory = operatorNameViewControllerFactory;
         mSecureSettings = secureSettings;
@@ -212,6 +241,8 @@
         initNotificationIconArea();
         mSystemEventAnimator =
                 new StatusBarSystemEventAnimator(mSystemIconArea, getResources());
+        mCarrierConfigTracker.addCallback(mCarrierConfigCallback);
+        mCarrierConfigTracker.addDefaultDataSubscriptionChangedListener(mDefaultDataListener);
     }
 
     @VisibleForTesting
@@ -283,6 +314,8 @@
         if (mNetworkController.hasEmergencyCryptKeeperText()) {
             mNetworkController.removeCallback(mSignalCallback);
         }
+        mCarrierConfigTracker.removeCallback(mCarrierConfigCallback);
+        mCarrierConfigTracker.removeDataSubscriptionChangedListener(mDefaultDataListener);
     }
 
     /** Initializes views related to the notification icon area. */
@@ -569,11 +602,16 @@
     }
 
     private void initOperatorName() {
-        if (getResources().getBoolean(R.bool.config_showOperatorNameInStatusBar)) {
+        int subId = SubscriptionManager.getDefaultDataSubscriptionId();
+        if (mCarrierConfigTracker.getShowOperatorNameInStatusBarConfig(subId)) {
             ViewStub stub = mStatusBar.findViewById(R.id.operator_name);
             mOperatorNameViewController =
                     mOperatorNameViewControllerFactory.create((OperatorNameView) stub.inflate());
             mOperatorNameViewController.init();
+            // This view should not be visible on lock-screen
+            if (mKeyguardStateController.isShowing()) {
+                hideOperatorName(false);
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarSystemEventAnimator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarSystemEventAnimator.kt
index f530ec8..fe69f75 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarSystemEventAnimator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarSystemEventAnimator.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.statusbar.events.STATUS_BAR_X_MOVE_IN
 import com.android.systemui.statusbar.events.STATUS_BAR_X_MOVE_OUT
 import com.android.systemui.statusbar.events.SystemStatusAnimationCallback
+import com.android.systemui.util.animation.AnimationUtil.Companion.frames
 
 /**
  * Tied directly to [SystemStatusAnimationScheduler]. Any StatusBar-like thing (keyguard, collapsed
@@ -45,12 +46,12 @@
             R.dimen.ongoing_appops_chip_animation_out_status_bar_translation_x)
 
     override fun onSystemEventAnimationBegin(): Animator {
-        val moveOut = ValueAnimator.ofFloat(0f, 1f).setDuration(383)
+        val moveOut = ValueAnimator.ofFloat(0f, 1f).setDuration(23.frames)
         moveOut.interpolator = STATUS_BAR_X_MOVE_OUT
         moveOut.addUpdateListener { animation: ValueAnimator ->
             animatedView.translationX = -(translationXIn * animation.animatedValue as Float)
         }
-        val alphaOut = ValueAnimator.ofFloat(1f, 0f).setDuration(133)
+        val alphaOut = ValueAnimator.ofFloat(1f, 0f).setDuration(8.frames)
         alphaOut.interpolator = null
         alphaOut.addUpdateListener { animation: ValueAnimator ->
             animatedView.alpha = animation.animatedValue as Float
@@ -63,14 +64,14 @@
 
     override fun onSystemEventAnimationFinish(hasPersistentDot: Boolean): Animator {
         animatedView.translationX = translationXOut.toFloat()
-        val moveIn = ValueAnimator.ofFloat(1f, 0f).setDuration(467)
-        moveIn.startDelay = 33
+        val moveIn = ValueAnimator.ofFloat(1f, 0f).setDuration(28.frames)
+        moveIn.startDelay = 2.frames
         moveIn.interpolator = STATUS_BAR_X_MOVE_IN
         moveIn.addUpdateListener { animation: ValueAnimator ->
             animatedView.translationX = translationXOut * animation.animatedValue as Float
         }
-        val alphaIn = ValueAnimator.ofFloat(0f, 1f).setDuration(167)
-        alphaIn.startDelay = 67
+        val alphaIn = ValueAnimator.ofFloat(0f, 1f).setDuration(10.frames)
+        alphaIn.startDelay = 4.frames
         alphaIn.interpolator = null
         alphaIn.addUpdateListener { animation: ValueAnimator ->
             animatedView.alpha = animation.animatedValue as Float
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
index 95a7316..ecaa28b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.policy;
 
 import android.annotation.Nullable;
+import android.view.View;
 
 import com.android.systemui.Dumpable;
 import com.android.systemui.demomode.DemoMode;
@@ -24,6 +25,7 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
 
 public interface BatteryController extends DemoMode, Dumpable,
         CallbackController<BatteryStateChangeCallback> {
@@ -35,7 +37,32 @@
     /**
      * Sets if the current device is in power save mode.
      */
-    void setPowerSaveMode(boolean powerSave);
+    default void setPowerSaveMode(boolean powerSave) {
+        setPowerSaveMode(powerSave, null);
+    }
+
+    /**
+     * Sets if the current device is in power save mode.
+     *
+     * Can pass the view that triggered the request.
+     */
+    void setPowerSaveMode(boolean powerSave, @Nullable View view);
+
+    /**
+     * Gets a reference to the last view used when called {@link #setPowerSaveMode}.
+     */
+    @Nullable
+    default WeakReference<View> getLastPowerSaverStartView() {
+        return null;
+    }
+
+    /**
+     * Clears the last view used when called {@link #setPowerSaveMode}.
+     *
+     * Immediately after calling this, a call to {@link #getLastPowerSaverStartView()} should return
+     * {@code null}.
+     */
+    default void clearLastPowerSaverStartView() {}
 
     /**
      * Returns {@code true} if the device is currently plugged in.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index 9e2c478..1e71dea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -28,6 +28,7 @@
 import android.os.PowerManager;
 import android.os.PowerSaveState;
 import android.util.Log;
+import android.view.View;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -45,8 +46,10 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * Default implementation of a {@link BatteryController}. This controller monitors for battery
@@ -85,6 +88,11 @@
     private Estimate mEstimate;
     private boolean mFetchingEstimate = false;
 
+    // Use AtomicReference because we may request it from a different thread
+    // Use WeakReference because we are keeping a reference to a View that's not as long lived
+    // as this controller.
+    private AtomicReference<WeakReference<View>> mPowerSaverStartView = new AtomicReference<>();
+
     @VisibleForTesting
     public BatteryControllerImpl(
             Context context,
@@ -141,11 +149,22 @@
     }
 
     @Override
-    public void setPowerSaveMode(boolean powerSave) {
+    public void setPowerSaveMode(boolean powerSave, View view) {
+        if (powerSave) mPowerSaverStartView.set(new WeakReference<>(view));
         BatterySaverUtils.setPowerSaveMode(mContext, powerSave, /*needFirstTimeWarning*/ true);
     }
 
     @Override
+    public WeakReference<View> getLastPowerSaverStartView() {
+        return mPowerSaverStartView.get();
+    }
+
+    @Override
+    public void clearLastPowerSaverStartView() {
+        mPowerSaverStartView.set(null);
+    }
+
+    @Override
     public void addCallback(@NonNull BatteryController.BatteryStateChangeCallback cb) {
         synchronized (mChangeCallbacks) {
             mChangeCallbacks.add(cb);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index fe08fb0..217a613 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -1215,7 +1215,8 @@
                 }
                 // Use broadcast instead of ShadeController, as this dialog may have started in
                 // another process and normal dagger bindings are not available
-                mBroadcastSender.closeSystemDialogs();
+                mBroadcastSender.sendBroadcastAsUser(
+                        new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), UserHandle.CURRENT);
                 getContext().startActivityAsUser(
                         CreateUserActivity.createIntentForStart(getContext()),
                         mUserTracker.getUserHandle());
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
index cbdf87e..8f2a432 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
@@ -155,21 +155,22 @@
             newRoot.relayout(params) { transaction ->
                 val vsyncId = Choreographer.getSfInstance().vsyncId
 
-                backgroundExecutor.execute {
-                    // Apply the transaction that contains the first frame of the overlay
-                    // synchronously and apply another empty transaction with
-                    // 'vsyncId + 1' to make sure that it is actually displayed on
-                    // the screen. The second transaction is necessary to remove the screen blocker
-                    // (turn on the brightness) only when the content is actually visible as it
-                    // might be presented only in the next frame.
-                    // See b/197538198
-                    transaction.setFrameTimelineVsync(vsyncId).apply(/* sync */ true)
+                // Apply the transaction that contains the first frame of the overlay and apply
+                // another empty transaction with 'vsyncId + 1' to make sure that it is actually
+                // displayed on the screen. The second transaction is necessary to remove the screen
+                // blocker (turn on the brightness) only when the content is actually visible as it
+                // might be presented only in the next frame.
+                // See b/197538198
+                transaction
+                    .setFrameTimelineVsync(vsyncId)
+                    .apply()
 
-                    transaction.setFrameTimelineVsync(vsyncId + 1).apply(/* sync */ true)
-
-                    Trace.endAsyncSection("UnfoldLightRevealOverlayAnimation#relayout", 0)
-                    callback.run()
-                }
+                transaction.setFrameTimelineVsync(vsyncId + 1)
+                    .addTransactionCommittedListener(backgroundExecutor) {
+                        Trace.endAsyncSection("UnfoldLightRevealOverlayAnimation#relayout", 0)
+                        callback.run()
+                    }
+                    .apply()
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java b/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java
index 14190fa..5f7d745 100644
--- a/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java
@@ -23,43 +23,73 @@
 import android.os.PersistableBundle;
 import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionManager;
+import android.util.ArraySet;
 import android.util.SparseBooleanArray;
 
+import androidx.annotation.NonNull;
+
+import com.android.internal.telephony.TelephonyIntents;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.statusbar.policy.CallbackController;
+
+import java.util.Set;
 
 import javax.inject.Inject;
 
 /**
- * Tracks the Carrier Config values.
+ * Tracks CarrierConfigs for each subId, as well as the default configuration. CarrierConfigurations
+ * do not trigger a device configuration event, so any UI that relies on carrier configurations must
+ * register with the tracker to get proper updates.
+ *
+ * The tracker also listens for `TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED`
+ *
+ * @see CarrierConfigChangedListener to listen for updates
  */
 @SysUISingleton
-public class CarrierConfigTracker extends BroadcastReceiver {
+public class CarrierConfigTracker
+        extends BroadcastReceiver
+        implements CallbackController<CarrierConfigTracker.CarrierConfigChangedListener> {
     private final SparseBooleanArray mCallStrengthConfigs = new SparseBooleanArray();
     private final SparseBooleanArray mNoCallingConfigs = new SparseBooleanArray();
     private final SparseBooleanArray mCarrierProvisionsWifiMergedNetworks =
             new SparseBooleanArray();
+    private final SparseBooleanArray mShowOperatorNameConfigs = new SparseBooleanArray();
     private final CarrierConfigManager mCarrierConfigManager;
+    private final Set<CarrierConfigChangedListener> mListeners = new ArraySet<>();
+    private final Set<DefaultDataSubscriptionChangedListener> mDataListeners =
+            new ArraySet<>();
     private boolean mDefaultCallStrengthConfigLoaded;
     private boolean mDefaultCallStrengthConfig;
     private boolean mDefaultNoCallingConfigLoaded;
     private boolean mDefaultNoCallingConfig;
     private boolean mDefaultCarrierProvisionsWifiMergedNetworksLoaded;
     private boolean mDefaultCarrierProvisionsWifiMergedNetworks;
+    private boolean mDefaultShowOperatorNameConfigLoaded;
+    private boolean mDefaultShowOperatorNameConfig;
 
     @Inject
-    public CarrierConfigTracker(Context context, BroadcastDispatcher broadcastDispatcher) {
-        mCarrierConfigManager = context.getSystemService(CarrierConfigManager.class);
-        broadcastDispatcher.registerReceiver(
-                this, new IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
+    public CarrierConfigTracker(
+            CarrierConfigManager carrierConfigManager,
+            BroadcastDispatcher broadcastDispatcher) {
+        mCarrierConfigManager = carrierConfigManager;
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+        filter.addAction(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
+        broadcastDispatcher.registerReceiver(this, filter);
     }
 
     @Override
     public void onReceive(Context context, Intent intent) {
-        if (!CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(intent.getAction())) {
-            return;
+        String action = intent.getAction();
+        if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(action)) {
+            updateFromNewCarrierConfig(intent);
+        } else if (TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED.equals(action)) {
+            updateDefaultDataSubscription(intent);
         }
+    }
 
+    private void updateFromNewCarrierConfig(Intent intent) {
         final int subId = intent.getIntExtra(
                 CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX,
                 SubscriptionManager.INVALID_SUBSCRIPTION_ID);
@@ -84,6 +114,29 @@
             mCarrierProvisionsWifiMergedNetworks.put(subId, config.getBoolean(
                     CarrierConfigManager.KEY_CARRIER_PROVISIONS_WIFI_MERGED_NETWORKS_BOOL));
         }
+        synchronized (mShowOperatorNameConfigs) {
+            mShowOperatorNameConfigs.put(subId, config.getBoolean(
+                    CarrierConfigManager.KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL));
+        }
+
+        notifyCarrierConfigChanged();
+    }
+
+    private void updateDefaultDataSubscription(Intent intent) {
+        int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, -1);
+        notifyDefaultDataSubscriptionChanged(subId);
+    }
+
+    private void notifyCarrierConfigChanged() {
+        for (CarrierConfigChangedListener l : mListeners) {
+            l.onCarrierConfigChanged();
+        }
+    }
+
+    private void notifyDefaultDataSubscriptionChanged(int subId) {
+        for (DefaultDataSubscriptionChangedListener l : mDataListeners) {
+            l.onDefaultSubscriptionChanged(subId);
+        }
     }
 
     /**
@@ -139,4 +192,73 @@
         }
         return mDefaultCarrierProvisionsWifiMergedNetworks;
     }
+
+    /**
+     * Returns the KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL value for the default config
+     */
+    public boolean getShowOperatorNameInStatusBarConfigDefault() {
+        if (!mDefaultShowOperatorNameConfigLoaded) {
+            mDefaultShowOperatorNameConfig = CarrierConfigManager.getDefaultConfig().getBoolean(
+                    CarrierConfigManager.KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL);
+            mDefaultShowOperatorNameConfigLoaded = true;
+        }
+
+        return mDefaultShowOperatorNameConfig;
+    }
+
+    /**
+     * Returns the KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL value for the given subId, or the
+     * default value if no override exists
+     *
+     * @param subId the subscription id for which to query the config
+     */
+    public boolean getShowOperatorNameInStatusBarConfig(int subId) {
+        if (mShowOperatorNameConfigs.indexOfKey(subId) >= 0) {
+            return mShowOperatorNameConfigs.get(subId);
+        } else {
+            return getShowOperatorNameInStatusBarConfigDefault();
+        }
+    }
+
+    @Override
+    public void addCallback(@NonNull CarrierConfigChangedListener listener) {
+        mListeners.add(listener);
+    }
+
+    @Override
+    public void removeCallback(@NonNull CarrierConfigChangedListener listener) {
+        mListeners.remove(listener);
+    }
+
+    /** */
+    public void addDefaultDataSubscriptionChangedListener(
+            @NonNull DefaultDataSubscriptionChangedListener listener) {
+        mDataListeners.add(listener);
+    }
+
+    /** */
+    public void removeDataSubscriptionChangedListener(
+            DefaultDataSubscriptionChangedListener listener) {
+        mDataListeners.remove(listener);
+    }
+
+    /**
+     * Called when carrier config changes
+     */
+    public interface CarrierConfigChangedListener {
+        /** */
+        void onCarrierConfigChanged();
+    }
+
+    /**
+     * Called when the default data subscription changes. Listeners may want to query
+     * subId-dependent configuration values when this event happens
+     */
+    public interface DefaultDataSubscriptionChangedListener {
+        /**
+         * @param subId the new default data subscription id per
+         * {@link SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX}
+         */
+        void onDefaultSubscriptionChanged(int subId);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/DualHeightHorizontalLinearLayout.kt b/packages/SystemUI/src/com/android/systemui/util/DualHeightHorizontalLinearLayout.kt
deleted file mode 100644
index cfceefa..0000000
--- a/packages/SystemUI/src/com/android/systemui/util/DualHeightHorizontalLinearLayout.kt
+++ /dev/null
@@ -1,183 +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.util
-
-import android.content.Context
-import android.content.res.Configuration
-import android.util.AttributeSet
-import android.util.DisplayMetrics
-import android.util.TypedValue
-import android.widget.LinearLayout
-import android.widget.TextView
-import com.android.systemui.R
-
-/**
- * Horizontal [LinearLayout] to contain some text.
- *
- * The height of this container can alternate between two different heights, depending on whether
- * the text takes one line or more.
- *
- * When the text takes multiple lines, it will use the values in the regular attributes (`padding`,
- * `layout_height`). The single line behavior must be set in XML.
- *
- * XML attributes for single line behavior:
- * * `systemui:textViewId`: set the id for the [TextView] that determines the height of the
- *   container
- * * `systemui:singleLineHeight`: sets the height of the view when the text takes up only one line.
- *   By default, it will use [getMinimumHeight].
- * * `systemui:singleLineVerticalPadding`: sets the padding (top and bottom) when then text takes up
- * only one line. By default, it is 0.
- *
- * All dimensions are updated when configuration changes.
- */
-class DualHeightHorizontalLinearLayout @JvmOverloads constructor(
-    context: Context,
-    attrs: AttributeSet? = null,
-    defStyleAttrs: Int = 0,
-    defStyleRes: Int = 0
-) : LinearLayout(context, attrs, defStyleAttrs, defStyleRes) {
-
-    private val singleLineHeightValue: TypedValue?
-    private var singleLineHeightPx = 0
-
-    private val singleLineVerticalPaddingValue: TypedValue?
-    private var singleLineVerticalPaddingPx = 0
-
-    private val textViewId: Int
-    private var textView: TextView? = null
-
-    private val displayMetrics: DisplayMetrics
-        get() = context.resources.displayMetrics
-
-    private var initialPadding = mPaddingTop // All vertical padding is the same
-
-    private var originalMaxLines = 1
-    var alwaysSingleLine: Boolean = false
-        set(value) {
-            field = value
-            if (field) {
-                textView?.setSingleLine()
-            } else {
-                textView?.maxLines = originalMaxLines
-            }
-        }
-
-    init {
-        if (orientation != HORIZONTAL) {
-            throw IllegalStateException("This view should always have horizontal orientation")
-        }
-
-        val ta = context.obtainStyledAttributes(
-                attrs,
-                R.styleable.DualHeightHorizontalLinearLayout, defStyleAttrs, defStyleRes
-        )
-
-        val tempHeight = TypedValue()
-        singleLineHeightValue = if (
-                ta.hasValue(R.styleable.DualHeightHorizontalLinearLayout_singleLineHeight)
-        ) {
-            ta.getValue(R.styleable.DualHeightHorizontalLinearLayout_singleLineHeight, tempHeight)
-            tempHeight
-        } else {
-            null
-        }
-
-        val tempPadding = TypedValue()
-        singleLineVerticalPaddingValue = if (
-                ta.hasValue(R.styleable.DualHeightHorizontalLinearLayout_singleLineVerticalPadding)
-        ) {
-            ta.getValue(
-                    R.styleable.DualHeightHorizontalLinearLayout_singleLineVerticalPadding,
-                    tempPadding
-            )
-            tempPadding
-        } else {
-            null
-        }
-
-        textViewId = ta.getResourceId(R.styleable.DualHeightHorizontalLinearLayout_textViewId, 0)
-
-        ta.recycle()
-    }
-
-    init {
-        updateResources()
-    }
-
-    override fun setPadding(left: Int, top: Int, right: Int, bottom: Int) {
-        super.setPadding(left, top, right, bottom)
-        initialPadding = top
-    }
-
-    override fun setPaddingRelative(start: Int, top: Int, end: Int, bottom: Int) {
-        super.setPaddingRelative(start, top, end, bottom)
-        initialPadding = top
-    }
-
-    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
-        textView?.let { tv ->
-            if (tv.lineCount < 2 || alwaysSingleLine) {
-                setMeasuredDimension(measuredWidth, singleLineHeightPx)
-                mPaddingBottom = 0
-                mPaddingTop = 0
-            } else {
-                mPaddingBottom = initialPadding
-                mPaddingTop = initialPadding
-            }
-        }
-    }
-
-    override fun onFinishInflate() {
-        super.onFinishInflate()
-        textView = findViewById<TextView>(textViewId)?.also {
-            originalMaxLines = it.maxLines
-        }
-    }
-
-    override fun onConfigurationChanged(newConfig: Configuration?) {
-        super.onConfigurationChanged(newConfig)
-        updateResources()
-    }
-
-    override fun setOrientation(orientation: Int) {
-        if (orientation == VERTICAL) {
-            throw IllegalStateException("This view should always have horizontal orientation")
-        }
-        super.setOrientation(orientation)
-    }
-
-    private fun updateResources() {
-        updateDimensionValue(singleLineHeightValue, minimumHeight, ::singleLineHeightPx::set)
-        updateDimensionValue(singleLineVerticalPaddingValue, 0, ::singleLineVerticalPaddingPx::set)
-    }
-
-    private inline fun updateDimensionValue(
-        tv: TypedValue?,
-        defaultValue: Int,
-        propertySetter: (Int) -> Unit
-    ) {
-        val value = tv?.let {
-            if (it.resourceId != 0) {
-                context.resources.getDimensionPixelSize(it.resourceId)
-            } else {
-                it.getDimension(displayMetrics).toInt()
-            }
-        } ?: defaultValue
-        propertySetter(value)
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/AnimationUtil.kt b/packages/SystemUI/src/com/android/systemui/util/animation/AnimationUtil.kt
index c0538c1..4958228 100644
--- a/packages/SystemUI/src/com/android/systemui/util/animation/AnimationUtil.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/AnimationUtil.kt
@@ -37,5 +37,11 @@
             }
             return (numFrames * 1000f / 60f).roundToLong()
         }
+
+        /**
+         * Convenience extension function for [getMsForFrames], so that we can write `23.frames`
+         */
+        val Int.frames: Long
+            get() = getMsForFrames(this)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index 4d34aa3..b70220d 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -576,7 +576,9 @@
             @Override
             public void onEntryRemoved(NotificationEntry entry,
                     @NotifCollection.CancellationReason int reason) {
-                BubblesManager.this.onEntryRemoved(entry);
+                if (reason == REASON_APP_CANCEL || reason == REASON_APP_CANCEL_ALL) {
+                    BubblesManager.this.onEntryRemoved(entry);
+                }
             }
 
             @Override
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
index 4bdab76..cc606de 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
@@ -58,7 +58,7 @@
     userId = user,
     listening = false,
     biometricEnabledForUser = false,
-    bouncer = false,
+    bouncerIsOrWillShow = false,
     canSkipBouncer = false,
     credentialAttempted = false,
     deviceInteractive = false,
@@ -85,9 +85,10 @@
     authInterruptActive = false,
     becauseCannotSkipBouncer = false,
     biometricSettingEnabledForUser = false,
-    bouncer = false,
+    bouncerFullyShown = false,
     faceAuthenticated = false,
     faceDisabled = false,
+    goingToSleep = false,
     keyguardAwake = false,
     keyguardGoingAway = false,
     listeningForFaceAssistant = false,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 42e15c4..775addd 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -35,10 +35,12 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.Activity;
+import android.app.ActivityManager;
 import android.app.admin.DevicePolicyManager;
 import android.app.trust.IStrongAuthTracker;
 import android.app.trust.TrustManager;
@@ -50,15 +52,20 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.BiometricSourceType;
 import android.hardware.biometrics.ComponentInfoInternal;
 import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
 import android.hardware.face.FaceManager;
 import android.hardware.face.FaceSensorProperties;
 import android.hardware.face.FaceSensorPropertiesInternal;
 import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.FingerprintSensorProperties;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.nfc.NfcAdapter;
 import android.os.Bundle;
+import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.IRemoteCallback;
 import android.os.UserHandle;
@@ -121,6 +128,9 @@
             TEST_CARRIER, TEST_CARRIER_2, NAME_SOURCE_CARRIER_ID, 0xFFFFFF, "",
             DATA_ROAMING_DISABLE, null, null, null, null, false, null, "", true, TEST_GROUP_UUID,
             TEST_CARRIER_ID, 0);
+    private static final int FACE_SENSOR_ID = 0;
+    private static final int FINGERPRINT_SENSOR_ID = 1;
+
     @Mock
     private DumpManager mDumpManager;
     @Mock
@@ -212,6 +222,16 @@
 
         when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
         when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
+        when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(List.of(
+                new FingerprintSensorPropertiesInternal(1 /* sensorId */,
+                        FingerprintSensorProperties.STRENGTH_STRONG,
+                        1 /* maxEnrollmentsPerUser */,
+                        List.of(new ComponentInfoInternal("fingerprintSensor" /* componentId */,
+                                "vendor/model/revision" /* hardwareVersion */,
+                                "1.01" /* firmwareVersion */,
+                                "00000001" /* serialNumber */, "" /* softwareVersion */)),
+                        FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
+                        false /* resetLockoutRequiresHAT */)));
         when(mUserManager.isUserUnlocked(anyInt())).thenReturn(true);
         when(mUserManager.isPrimaryUser()).thenReturn(true);
         when(mStrongAuthTracker.getStub()).thenReturn(mock(IStrongAuthTracker.Stub.class));
@@ -232,9 +252,13 @@
         mSpiedContext.addMockSystemService(TelephonyManager.class, mTelephonyManager);
 
         mMockitoSession = ExtendedMockito.mockitoSession()
-                .spyStatic(SubscriptionManager.class).startMocking();
+                .spyStatic(SubscriptionManager.class)
+                .spyStatic(ActivityManager.class)
+                .startMocking();
         ExtendedMockito.doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
                 .when(SubscriptionManager::getDefaultSubscriptionId);
+        ExtendedMockito.doReturn(KeyguardUpdateMonitor.getCurrentUser())
+                .when(ActivityManager::getCurrentUser);
 
         mTestableLooper = TestableLooper.get(this);
         allowTestableLooperAsMainThread();
@@ -527,6 +551,15 @@
     }
 
     @Test
+    public void testNoStartAuthenticate_whenAboutToShowBouncer() {
+        mKeyguardUpdateMonitor.sendKeyguardBouncerChanged(
+                /* bouncerIsOrWillBeShowing */ true, /* bouncerFullyShown */ false);
+
+        verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
+                anyBoolean());
+    }
+
+    @Test
     public void testTriesToAuthenticate_whenKeyguard() {
         mKeyguardUpdateMonitor.dispatchStartedWakingUp();
         mTestableLooper.processAllMessages();
@@ -649,7 +682,7 @@
         // doesn't matter here
         mKeyguardUpdateMonitor.onFaceAuthenticated(KeyguardUpdateMonitor.getCurrentUser(),
                 true /* isStrongBiometric */);
-        mKeyguardUpdateMonitor.sendKeyguardBouncerChanged(true);
+        setKeyguardBouncerVisibility(true);
         mTestableLooper.processAllMessages();
 
         verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
@@ -757,6 +790,59 @@
     }
 
     @Test
+    public void testMultiUserLockoutChanged_whenUserSwitches() {
+        testMultiUserLockout_whenUserSwitches(BiometricConstants.BIOMETRIC_LOCKOUT_PERMANENT,
+                BiometricConstants.BIOMETRIC_LOCKOUT_PERMANENT);
+    }
+
+    @Test
+    public void testMultiUserLockoutNotChanged_whenUserSwitches() {
+        testMultiUserLockout_whenUserSwitches(BiometricConstants.BIOMETRIC_LOCKOUT_NONE,
+                BiometricConstants.BIOMETRIC_LOCKOUT_NONE);
+    }
+
+    private void testMultiUserLockout_whenUserSwitches(
+            @BiometricConstants.LockoutMode int fingerprintLockoutMode,
+            @BiometricConstants.LockoutMode int faceLockoutMode) {
+        final int newUser = 12;
+        final boolean faceLocked =
+                faceLockoutMode != BiometricConstants.BIOMETRIC_LOCKOUT_NONE;
+        final boolean fpLocked =
+                fingerprintLockoutMode != BiometricConstants.BIOMETRIC_LOCKOUT_NONE;
+        when(mFingerprintManager.getLockoutModeForUser(eq(FINGERPRINT_SENSOR_ID), eq(newUser)))
+                .thenReturn(fingerprintLockoutMode);
+        when(mFaceManager.getLockoutModeForUser(eq(FACE_SENSOR_ID), eq(newUser)))
+                .thenReturn(faceLockoutMode);
+
+        mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+        mTestableLooper.processAllMessages();
+        mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+
+        verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
+        verify(mFingerprintManager).authenticate(any(), any(), any(), any(), anyInt(), anyInt(),
+                anyInt());
+
+        final CancellationSignal faceCancel = spy(mKeyguardUpdateMonitor.mFaceCancelSignal);
+        final CancellationSignal fpCancel = spy(mKeyguardUpdateMonitor.mFingerprintCancelSignal);
+        mKeyguardUpdateMonitor.mFaceCancelSignal = faceCancel;
+        mKeyguardUpdateMonitor.mFingerprintCancelSignal = fpCancel;
+        KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+        mKeyguardUpdateMonitor.registerCallback(callback);
+
+        mKeyguardUpdateMonitor.handleUserSwitchComplete(newUser);
+        mTestableLooper.processAllMessages();
+
+        verify(faceCancel, faceLocked ? times(1) : never()).cancel();
+        verify(fpCancel, fpLocked ? times(1) : never()).cancel();
+        verify(callback, faceLocked ? times(1) : never()).onBiometricRunningStateChanged(
+                eq(false), eq(BiometricSourceType.FACE));
+        verify(callback, fpLocked ? times(1) : never()).onBiometricRunningStateChanged(
+                eq(false), eq(BiometricSourceType.FINGERPRINT));
+        assertThat(mKeyguardUpdateMonitor.isFingerprintLockedOut()).isEqualTo(fpLocked);
+        assertThat(mKeyguardUpdateMonitor.isFaceLockedOut()).isEqualTo(faceLocked);
+    }
+
+    @Test
     public void testGetUserCanSkipBouncer_whenTrust() {
         int user = KeyguardUpdateMonitor.getCurrentUser();
         mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, user, 0 /* flags */,
@@ -1064,7 +1150,7 @@
     }
 
     private void setKeyguardBouncerVisibility(boolean isVisible) {
-        mKeyguardUpdateMonitor.sendKeyguardBouncerChanged(isVisible);
+        mKeyguardUpdateMonitor.sendKeyguardBouncerChanged(isVisible, isVisible);
         mTestableLooper.processAllMessages();
     }
 
@@ -1115,5 +1201,10 @@
             mSimStateChanged.set(true);
             super.handleSimStateChange(subId, slotId, state);
         }
+
+        @Override
+        protected int getBiometricLockoutDelay() {
+            return 0;
+        }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt
index 96e6bd1..5734c3d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt
@@ -37,7 +37,6 @@
 import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito.`when`
-import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
@@ -104,26 +103,6 @@
         verify(runnable).run()
     }
 
-    @Test
-    fun testWakeAndUnlockDelaysRunnable() {
-        // GIVEN wakeAndUnlocking has been set to true
-        screenOnCoordinator.wakeAndUnlocking = true
-
-        // WHEN the screen turns on and two tasks have completed
-        screenOnCoordinator.onScreenTurningOn(runnable)
-        onUnfoldOverlayReady()
-        onFoldAodReady()
-
-        // THEN the runnable should not have run yet
-        verify(runnable, never()).run()
-
-        // WHEN the value of wakeAndUnlocking changes
-        screenOnCoordinator.wakeAndUnlocking = false
-
-        // THEN the runnable should have run, as it is the last task to complete
-        verify(runnable).run()
-    }
-
     private fun onUnfoldOverlayReady() {
         verify(unfoldAnimation).onScreenTurningOn(capture(readyCaptor))
         readyCaptor.getValue().run()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 483dbf5..666c9e4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -48,6 +48,7 @@
 import org.mockito.Mock
 import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.eq
+import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.junit.MockitoJUnit
 import org.mockito.Mockito.`when` as whenever
@@ -81,8 +82,29 @@
     }
 
     @Test
+    fun testNotifiesAnimatedIn() {
+        initializeContainer()
+        verify(callback).onDialogAnimatedIn()
+    }
+
+    @Test
+    fun testIgnoresAnimatedInWhenDismissed() {
+        val container = initializeContainer(addToView = false)
+        container.dismissFromSystemServer()
+        waitForIdleSync()
+
+        verify(callback, never()).onDialogAnimatedIn()
+
+        container.addToView()
+        waitForIdleSync()
+
+        // attaching the view resets the state and allows this to happen again
+        verify(callback).onDialogAnimatedIn()
+    }
+
+    @Test
     fun testActionAuthenticated_sendsDismissedAuthenticated() {
-        val container = initializeContainer(BiometricManager.Authenticators.BIOMETRIC_WEAK)
+        val container = initializeContainer()
         container.mBiometricCallback.onAction(
             AuthBiometricView.Callback.ACTION_AUTHENTICATED
         )
@@ -97,7 +119,7 @@
 
     @Test
     fun testActionUserCanceled_sendsDismissedUserCanceled() {
-        val container = initializeContainer(BiometricManager.Authenticators.BIOMETRIC_WEAK)
+        val container = initializeContainer()
         container.mBiometricCallback.onAction(
             AuthBiometricView.Callback.ACTION_USER_CANCELED
         )
@@ -115,7 +137,7 @@
 
     @Test
     fun testActionButtonNegative_sendsDismissedButtonNegative() {
-        val container = initializeContainer(BiometricManager.Authenticators.BIOMETRIC_WEAK)
+        val container = initializeContainer()
         container.mBiometricCallback.onAction(
             AuthBiometricView.Callback.ACTION_BUTTON_NEGATIVE
         )
@@ -141,7 +163,7 @@
 
     @Test
     fun testActionError_sendsDismissedError() {
-        val container = initializeContainer(BiometricManager.Authenticators.BIOMETRIC_WEAK)
+        val container = initializeContainer()
         authContainer!!.mBiometricCallback.onAction(
             AuthBiometricView.Callback.ACTION_ERROR
         )
@@ -183,7 +205,7 @@
 
     @Test
     fun testShowBiometricUI() {
-        val container = initializeContainer(BiometricManager.Authenticators.BIOMETRIC_WEAK)
+        val container = initializeContainer()
 
         waitForIdleSync()
 
@@ -252,7 +274,10 @@
         assertThat((layoutParams.fitInsetsTypes and WindowInsets.Type.ime()) == 0).isTrue()
     }
 
-    private fun initializeContainer(authenticators: Int): TestAuthContainerView {
+    private fun initializeContainer(
+        authenticators: Int = BiometricManager.Authenticators.BIOMETRIC_WEAK,
+        addToView: Boolean = true
+    ): TestAuthContainerView {
         val config = AuthContainerView.Config()
         config.mContext = mContext
         config.mCallback = callback
@@ -291,7 +316,11 @@
             lockPatternUtils,
             Handler(TestableLooper.get(this).looper)
         )
-        ViewUtils.attachView(authContainer)
+
+        if (addToView) {
+            authContainer!!.addToView()
+        }
+
         return authContainer!!
     }
 
@@ -316,6 +345,12 @@
         TestableLooper.get(this).processAllMessages()
         super.waitForIdleSync()
     }
+
+    private fun AuthContainerView.addToView() {
+        ViewUtils.attachView(this)
+        waitForIdleSync()
+        assertThat(isAttachedToWindow).isTrue()
+    }
 }
 
 private fun AuthContainerView.hasBiometricPrompt() =
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 42c3c7f..190228d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -20,6 +20,8 @@
 import static android.hardware.biometrics.BiometricManager.Authenticators;
 import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertNull;
 
@@ -104,6 +106,8 @@
 @SmallTest
 public class AuthControllerTest extends SysuiTestCase {
 
+    private static final long REQUEST_ID = 22;
+
     @Rule
     public final MockitoRule mMockitoRule = MockitoJUnit.rule();
 
@@ -173,6 +177,9 @@
         when(mDialog1.isAllowDeviceCredentials()).thenReturn(false);
         when(mDialog2.isAllowDeviceCredentials()).thenReturn(false);
 
+        when(mDialog1.getRequestId()).thenReturn(REQUEST_ID);
+        when(mDialog2.getRequestId()).thenReturn(REQUEST_ID);
+
         when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
 
         final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
@@ -482,7 +489,12 @@
     @Test
     public void testHideAuthenticationDialog_invokesDismissFromSystemServer() {
         showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
-        mAuthController.hideAuthenticationDialog();
+
+        mAuthController.hideAuthenticationDialog(REQUEST_ID + 1);
+        verify(mDialog1, never()).dismissFromSystemServer();
+        assertThat(mAuthController.mCurrentDialog).isSameInstanceAs(mDialog1);
+
+        mAuthController.hideAuthenticationDialog(REQUEST_ID);
         verify(mDialog1).dismissFromSystemServer();
 
         // In this case, BiometricService sends the error to the client immediately, without
@@ -512,7 +524,7 @@
                 eq(BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED),
                 AdditionalMatchers.aryEq(credentialAttestation));
 
-        mAuthController.hideAuthenticationDialog();
+        mAuthController.hideAuthenticationDialog(REQUEST_ID);
     }
 
     @Test
@@ -648,7 +660,7 @@
 
         verify(mDisplayManager).registerDisplayListener(any(), eq(mHandler));
 
-        mAuthController.hideAuthenticationDialog();
+        mAuthController.hideAuthenticationDialog(REQUEST_ID);
         verify(mDisplayManager).unregisterDisplayListener(any());
     }
 
@@ -704,7 +716,7 @@
                 0 /* userId */,
                 0 /* operationId */,
                 "testPackage",
-                1 /* requestId */,
+                REQUEST_ID,
                 BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 406ed5c..05cf6a50 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -67,7 +67,6 @@
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
@@ -150,8 +149,6 @@
     @Mock
     private KeyguardStateController mKeyguardStateController;
     @Mock
-    private KeyguardBypassController mKeyguardBypassController;
-    @Mock
     private DisplayManager mDisplayManager;
     @Mock
     private Handler mHandler;
@@ -252,7 +249,6 @@
                 mUdfpsHapticsSimulator,
                 Optional.of(mHbmProvider),
                 mKeyguardStateController,
-                mKeyguardBypassController,
                 mDisplayManager,
                 mHandler,
                 mConfigurationController,
@@ -310,75 +306,6 @@
     }
 
     @Test
-    public void onActionMove_dozing_setDeviceEntryIntent() throws RemoteException {
-        // GIVEN the current animation is UdfpsKeyguardViewController and device IS dozing
-        when(mKeyguardStateController.canDismissLockScreen()).thenReturn(false);
-        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
-        when(mUdfpsView.getAnimationViewController()).thenReturn(mUdfpsKeyguardViewController);
-        when(mStatusBarStateController.isDozing()).thenReturn(true);
-
-        // GIVEN that the overlay is showing
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
-        mFgExecutor.runAllReady();
-
-        // WHEN ACTION_DOWN is received
-        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
-        MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0);
-        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent);
-        moveEvent.recycle();
-
-        // THEN device entry intent is never to true b/c device was dozing on touch
-        verify(mKeyguardBypassController, never()).setUserHasDeviceEntryIntent(true);
-    }
-
-    @Test
-    public void onActionMove_onKeyguard_setDeviceEntryIntent() throws RemoteException {
-        // GIVEN the current animation is UdfpsKeyguardViewController and device isn't dozing
-        when(mKeyguardStateController.canDismissLockScreen()).thenReturn(false);
-        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
-        when(mUdfpsView.getAnimationViewController()).thenReturn(mUdfpsKeyguardViewController);
-        when(mStatusBarStateController.isDozing()).thenReturn(false);
-
-        // GIVEN that the overlay is showing
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
-        mFgExecutor.runAllReady();
-
-        // WHEN ACTION_DOWN is received
-        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
-        MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0);
-        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent);
-        moveEvent.recycle();
-
-        // THEN device entry intent is set to true
-        verify(mKeyguardBypassController).setUserHasDeviceEntryIntent(true);
-    }
-
-    @Test
-    public void onActionMove_onEnrollment_neverSetDeviceEntryIntent() throws RemoteException {
-        // GIVEN the current animation is UdfpsEnrollViewController
-        when(mKeyguardStateController.canDismissLockScreen()).thenReturn(false);
-        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
-        when(mUdfpsView.getAnimationViewController()).thenReturn(
-                (UdfpsAnimationViewController) mock(UdfpsEnrollViewController.class));
-
-        // GIVEN that the overlay is showing
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
-                BiometricOverlayConstants.REASON_ENROLL_ENROLLING, mUdfpsOverlayControllerCallback);
-        mFgExecutor.runAllReady();
-
-        // WHEN ACTION_DOWN is received
-        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
-        MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0);
-        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent);
-        moveEvent.recycle();
-
-        // THEN device entry intent is never set
-        verify(mKeyguardBypassController, never()).setUserHasDeviceEntryIntent(anyBoolean());
-    }
-
-    @Test
     public void onActionMoveTouch_whenCanDismissLockScreen_entersDevice()
             throws RemoteException {
         onActionMoveTouch_whenCanDismissLockScreen_entersDevice(false /* stale */);
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 3d8d128..6d4cc4c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
@@ -23,7 +23,6 @@
 import android.testing.TestableLooper
 import android.testing.ViewUtils
 import android.view.LayoutInflater
-import android.view.Surface
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
@@ -37,7 +36,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
-import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.never
 import org.mockito.Mockito.nullable
 import org.mockito.Mockito.verify
@@ -148,7 +146,7 @@
         view.startIllumination(onDone)
 
         val illuminator = withArgCaptor<Runnable> {
-            verify(hbmProvider).enableHbm(anyInt(), nullable(Surface::class.java), capture())
+            verify(hbmProvider).enableHbm(capture())
         }
 
         assertThat(view.isIlluminationRequested).isTrue()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
index f514b56..a1d1933 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
@@ -56,6 +56,7 @@
         val user0 = UserHandle.of(0)
         val user1 = UserHandle.of(1)
         const val DEFAULT_FLAG = Context.RECEIVER_EXPORTED
+        val DEFAULT_PERMISSION: String? = null
 
         fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
         const val TEST_ACTION = "TEST_ACTION"
@@ -190,6 +191,38 @@
     }
 
     @Test
+    fun testAddReceiverCorrectPermission_executor() {
+        val flag = 3
+        val permission = "CUSTOM_PERMISSION"
+
+        broadcastDispatcher.registerReceiver(
+            broadcastReceiver,
+            intentFilter,
+            flags = flag,
+            permission = permission
+        )
+        testableLooper.processAllMessages()
+
+        verify(mockUBRUser0).registerReceiver(capture(argumentCaptor), eq(flag))
+
+        assertSame(broadcastReceiver, argumentCaptor.value.receiver)
+        assertSame(intentFilter, argumentCaptor.value.filter)
+        assertSame(permission, argumentCaptor.value.permission)
+    }
+
+    @Test
+    fun testAddReceiverDefaultPermission_executor() {
+        broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter)
+        testableLooper.processAllMessages()
+
+        verify(mockUBRUser0).registerReceiver(capture(argumentCaptor), eq(DEFAULT_FLAG))
+
+        assertSame(broadcastReceiver, argumentCaptor.value.receiver)
+        assertSame(intentFilter, argumentCaptor.value.filter)
+        assertSame(DEFAULT_PERMISSION, argumentCaptor.value.permission)
+    }
+
+    @Test
     fun testAddReceiverCorrectFlag_executor() {
         val flag = 3
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
index fd1c41e..141b3b44 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.broadcast
 
 import android.content.BroadcastReceiver
+import android.content.Context
 import android.content.IntentFilter
 import android.os.Handler
 import android.os.Looper
@@ -45,7 +46,8 @@
         filter: IntentFilter,
         handler: Handler,
         user: UserHandle,
-        flags: Int
+        @Context.RegisterReceiverFlags flags: Int,
+        permission: String?
     ) {
         registeredReceivers.add(receiver)
     }
@@ -55,7 +57,8 @@
         filter: IntentFilter,
         executor: Executor?,
         user: UserHandle?,
-        flags: Int
+        @Context.RegisterReceiverFlags flags: Int,
+        permission: String?
     ) {
         registeredReceivers.add(receiver)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
index 4e3345c..116b81d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
@@ -85,7 +85,11 @@
 
         userBroadcastDispatcher = object : UserBroadcastDispatcher(
                 mockContext, USER_ID, testableLooper.looper, mock(Executor::class.java), logger) {
-            override fun createActionReceiver(action: String, flags: Int): ActionReceiver {
+            override fun createActionReceiver(
+                action: String,
+                permission: String?,
+                flags: Int
+            ): ActionReceiver {
                 return mock(ActionReceiver::class.java)
             }
         }
@@ -123,6 +127,24 @@
     }
 
     @Test
+    fun testDifferentActionReceiversForDifferentPermissions() {
+        intentFilter = IntentFilter(ACTION_1)
+        val receiverData1 =
+            ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE, "PERMISSION1")
+        val receiverData2 =
+            ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE, "PERMISSION2")
+
+        userBroadcastDispatcher.registerReceiver(receiverData1, 0)
+        userBroadcastDispatcher.registerReceiver(receiverData2, 0)
+        testableLooper.processAllMessages()
+
+        assertNotSame(
+            userBroadcastDispatcher.getActionReceiver(ACTION_1, 0, "PERMISSION1"),
+            userBroadcastDispatcher.getActionReceiver(ACTION_1, 0, "PERMISSION2")
+        )
+    }
+
+    @Test
     fun testSingleReceiverRegistered_logging() {
         intentFilter = IntentFilter(ACTION_1)
 
@@ -213,8 +235,45 @@
                 any(), any(), any(), nullable(String::class.java), any(), eq(FLAG))
     }
 
-    private fun UserBroadcastDispatcher
-            .getActionReceiver(action: String, flags: Int): ActionReceiver? {
-        return actionsToActionsReceivers.get(action to flags)
+    @Test
+    fun testCreateActionReceiver_registerWithPermission() {
+        val permission = "CUSTOM_PERMISSION"
+        val uBR = UserBroadcastDispatcher(
+            mockContext,
+            USER_ID,
+            testableLooper.looper,
+            fakeExecutor,
+            logger
+        )
+        uBR.registerReceiver(
+            ReceiverData(
+                broadcastReceiver,
+                IntentFilter(ACTION_1),
+                fakeExecutor,
+                USER_HANDLE,
+                permission
+            ),
+            FLAG
+        )
+
+        testableLooper.processAllMessages()
+        fakeExecutor.runAllReady()
+
+        verify(mockContext).registerReceiverAsUser(
+            any(), any(), any(), eq(permission), any(), eq(FLAG))
+    }
+
+    private fun UserBroadcastDispatcher.getActionReceiver(
+        action: String,
+        flags: Int,
+        permission: String? = null
+    ): ActionReceiver? {
+        return actionsToActionsReceivers.get(
+            UserBroadcastDispatcher.ReceiverProperties(
+                action,
+                flags,
+                permission
+            )
+        )
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
index cff6b9a..3c7ea4f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
@@ -168,7 +168,8 @@
         controller.auxiliaryPersistenceWrapper = auxiliaryPersistenceWrapper
 
         verify(broadcastDispatcher).registerReceiver(
-                capture(broadcastReceiverCaptor), any(), any(), eq(UserHandle.ALL), anyInt())
+            capture(broadcastReceiverCaptor), any(), any(), eq(UserHandle.ALL), anyInt(), any()
+        )
 
         verify(listingController).addCallback(capture(listingCallbackCaptor))
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
index f4b378e..4736587 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
@@ -211,20 +211,6 @@
     }
 
     @Test
-    public void testPausingAod_doesNotResetBrightness() throws Exception {
-        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
-        mScreen.transitionTo(INITIALIZED, DOZE_AOD);
-        waitForSensorManager();
-
-        mSensor.sendSensorEvent(1);
-
-        mScreen.transitionTo(DOZE_AOD, DOZE_AOD_PAUSING);
-        mScreen.transitionTo(DOZE_AOD_PAUSING, DOZE_AOD_PAUSED);
-
-        assertEquals(1, mServiceFake.screenBrightness);
-    }
-
-    @Test
     public void testPulsing_withoutLightSensor_setsAoDDimmingScrimTransparent() throws Exception {
         mScreen = new DozeScreenBrightness(
                 mContext,
@@ -431,37 +417,18 @@
     }
 
     @Test
-    public void pausingAod_unblanksAfterSensor() {
+    public void pausingAod_unblanksAfterSensorEvent() {
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
         mScreen.transitionTo(INITIALIZED, DOZE_AOD);
         waitForSensorManager();
 
-        mSensor.sendSensorEvent(2);
-
-        mScreen.transitionTo(DOZE_AOD, DOZE_AOD_PAUSING);
-        mScreen.transitionTo(DOZE_AOD_PAUSING, DOZE_AOD_PAUSED);
-
-        mSensor.sendSensorEvent(0);
-
-        reset(mDozeHost);
-        mScreen.transitionTo(DOZE_AOD_PAUSED, DOZE_AOD);
-        waitForSensorManager();
-        mSensor.sendSensorEvent(2);
-        verify(mDozeHost).setAodDimmingScrim(eq(0f));
-    }
-
-    @Test
-    public void pausingAod_unblanksIfSensorWasAlwaysReady() throws Exception {
-        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
-        mScreen.transitionTo(INITIALIZED, DOZE_AOD);
-        waitForSensorManager();
-
-        mSensor.sendSensorEvent(2);
         mScreen.transitionTo(DOZE_AOD, DOZE_AOD_PAUSING);
         mScreen.transitionTo(DOZE_AOD_PAUSING, DOZE_AOD_PAUSED);
 
         reset(mDozeHost);
         mScreen.transitionTo(DOZE_AOD_PAUSED, DOZE_AOD);
+        waitForSensorManager();
+        mSensor.sendSensorEvent(2);
         verify(mDozeHost).setAodDimmingScrim(eq(0f));
     }
 
@@ -538,6 +505,44 @@
         assertEquals(mServiceFake.screenBrightness, DEFAULT_BRIGHTNESS);
     }
 
+    @Test
+    public void transitionToAodPaused_resetsToDefaultBrightness_lightSensorDisabled() {
+        // GIVEN AOD
+        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+        mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+
+        // WHEN AOD is paused
+        mScreen.transitionTo(DOZE_AOD, DOZE_AOD_PAUSING);
+        mScreen.transitionTo(DOZE_AOD, DOZE_AOD_PAUSED);
+        waitForSensorManager();
+
+        // THEN brightness is reset and light sensor is unregistered
+        assertEquals(mServiceFake.screenBrightness, DEFAULT_BRIGHTNESS);
+
+        // THEN new light events don't update brightness since the light sensor was unregistered
+        mSensor.sendSensorEvent(1);
+        assertEquals(mServiceFake.screenBrightness, DEFAULT_BRIGHTNESS);
+    }
+
+    @Test
+    public void transitionFromAodPausedToAod_lightSensorEnabled() {
+        // GIVEN AOD paused
+        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+        mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+        mScreen.transitionTo(DOZE_AOD, DOZE_AOD_PAUSING);
+        mScreen.transitionTo(DOZE_AOD, DOZE_AOD_PAUSED);
+
+        // WHEN device transitions back to AOD
+        mScreen.transitionTo(DOZE_AOD_PAUSED, DOZE_AOD);
+        waitForSensorManager();
+
+        // WHEN there are brightness changes
+        mSensor.sendSensorEvent(1);
+
+        // THEN aod brightness is updated
+        assertEquals(mServiceFake.screenBrightness, 1);
+    }
+
     private void waitForSensorManager() {
         mFakeExecutor.runAllReady();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index 76f6529..6453c20 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -107,6 +107,7 @@
                 mStatusBarKeyguardViewManager,
                 mBlurUtils,
                 mHandler,
+                mResources,
                 MAX_BURN_IN_OFFSET,
                 BURN_IN_PROTECTION_UPDATE_INTERVAL,
                 MILLIS_UNTIL_FULL_JITTER);
@@ -160,11 +161,10 @@
 
         bouncerExpansionCaptor.getValue().onExpansionChanged(0.5f);
         verify(mBlurUtils, never()).applyBlur(eq(mViewRoot), anyInt(), eq(false));
-        verify(mDreamOverlayContainerView, never()).setAlpha(anyFloat());
     }
 
     @Test
-    public void testBouncerAnimation_updateBlurAndAlpha() {
+    public void testBouncerAnimation_updateBlur() {
         final ArgumentCaptor<BouncerExpansionCallback> bouncerExpansionCaptor =
                 ArgumentCaptor.forClass(BouncerExpansionCallback.class);
         mController.onViewAttached();
@@ -182,6 +182,5 @@
         bouncerExpansionCaptor.getValue().onExpansionChanged(bouncerHideAmount);
         verify(mBlurUtils).blurRadiusOfRatio(1 - scaledFraction);
         verify(mBlurUtils).applyBlur(mViewRoot, (int) blurRadius, false);
-        verify(mDreamOverlayContainerView).setAlpha(scaledFraction);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java
index 35bcfcd..2448f1a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java
@@ -40,7 +40,10 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
+import java.util.Arrays;
+import java.util.List;
 import java.util.function.Consumer;
+import java.util.stream.Collectors;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -448,4 +451,70 @@
         verify(firstViewInfo.view, never()).getParent();
         verify(mLayout, never()).removeView(firstViewInfo.view);
     }
+
+    @Test
+    public void testGetViews() {
+        final ComplicationLayoutEngine engine =
+                new ComplicationLayoutEngine(mLayout, 0, mTouchSession, 0, 0);
+
+        final ViewInfo topEndView = new ViewInfo(
+                new ComplicationLayoutParams(
+                        100,
+                        100,
+                        ComplicationLayoutParams.POSITION_TOP
+                                | ComplicationLayoutParams.POSITION_END,
+                        ComplicationLayoutParams.DIRECTION_DOWN,
+                        0),
+                Complication.CATEGORY_STANDARD,
+                mLayout);
+
+        addComplication(engine, topEndView);
+
+        final ViewInfo topStartView = new ViewInfo(
+                new ComplicationLayoutParams(
+                        100,
+                        100,
+                        ComplicationLayoutParams.POSITION_TOP
+                                | ComplicationLayoutParams.POSITION_START,
+                        ComplicationLayoutParams.DIRECTION_DOWN,
+                        0),
+                Complication.CATEGORY_SYSTEM,
+                mLayout);
+
+        addComplication(engine, topStartView);
+
+        final ViewInfo bottomEndView = new ViewInfo(
+                new ComplicationLayoutParams(
+                        100,
+                        100,
+                        ComplicationLayoutParams.POSITION_BOTTOM
+                                | ComplicationLayoutParams.POSITION_END,
+                        ComplicationLayoutParams.DIRECTION_START,
+                        1),
+                Complication.CATEGORY_SYSTEM,
+                mLayout);
+
+        addComplication(engine, bottomEndView);
+
+        verifyViewsAtPosition(engine, ComplicationLayoutParams.POSITION_TOP, topStartView,
+                topEndView);
+        verifyViewsAtPosition(engine,
+                ComplicationLayoutParams.POSITION_TOP | ComplicationLayoutParams.POSITION_START,
+                topStartView);
+        verifyViewsAtPosition(engine,
+                ComplicationLayoutParams.POSITION_BOTTOM,
+                bottomEndView);
+    }
+
+    private void verifyViewsAtPosition(ComplicationLayoutEngine engine, int position,
+            ViewInfo... views) {
+        final List<Integer> idList = engine.getViewsAtPosition(position).stream()
+                .map(View::getId)
+                .collect(Collectors.toList());
+
+        assertThat(idList).containsExactlyElementsIn(
+                Arrays.stream(views)
+                        .map(viewInfo -> viewInfo.view.getId())
+                        .collect(Collectors.toList()));
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
index 74cf497..a016a1d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
@@ -169,7 +169,7 @@
      * Makes sure swiping up when bouncer initially showing doesn't change the expansion amount.
      */
     @Test
-    public void testSwipeUp_whenBouncerInitiallyShowing_keepsExpansionAtZero() {
+    public void testSwipeUp_whenBouncerInitiallyShowing_doesNotSetExpansion() {
         when(mCentralSurfaces.isBouncerShowing()).thenReturn(true);
 
         mTouchHandler.onSessionStart(mTouchSession);
@@ -191,21 +191,15 @@
         assertThat(gestureListener.onScroll(event1, event2, 0, distanceY))
                 .isTrue();
 
-        // Ensure only called once
-        verify(mStatusBarKeyguardViewManager)
+        verify(mStatusBarKeyguardViewManager, never())
                 .onPanelExpansionChanged(anyFloat(), anyBoolean(), anyBoolean());
-
-        // TODO(b/227348372): update the logic and also this test.
-        // Ensure the expansion is kept at 0.
-        verify(mStatusBarKeyguardViewManager).onPanelExpansionChanged(eq(0f), eq(false),
-                eq(true));
     }
 
     /**
      * Makes sure swiping down when bouncer initially hidden doesn't change the expansion amount.
      */
     @Test
-    public void testSwipeDown_whenBouncerInitiallyHidden_keepsExpansionAtOne() {
+    public void testSwipeDown_whenBouncerInitiallyHidden_doesNotSetExpansion() {
         mTouchHandler.onSessionStart(mTouchSession);
         ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
                 ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
@@ -225,14 +219,8 @@
         assertThat(gestureListener.onScroll(event1, event2, 0, distanceY))
                 .isTrue();
 
-        // Ensure only called once
-        verify(mStatusBarKeyguardViewManager)
+        verify(mStatusBarKeyguardViewManager, never())
                 .onPanelExpansionChanged(anyFloat(), anyBoolean(), anyBoolean());
-
-        // TODO(b/227348372): update the logic and also this test.
-        // Ensure the expansion is kept at 1.
-        verify(mStatusBarKeyguardViewManager).onPanelExpansionChanged(eq(1f), eq(false),
-                eq(true));
     }
 
     /**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpsysTableLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpsysTableLoggerTest.kt
new file mode 100644
index 0000000..1d2afe4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpsysTableLoggerTest.kt
@@ -0,0 +1,146 @@
+/*
+ * 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.dump
+
+import androidx.test.filters.SmallTest
+
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+
+import java.io.PrintWriter
+import java.io.StringWriter
+
+@SmallTest
+class DumpsysTableLoggerTest : SysuiTestCase() {
+    private val logger = DumpsysTableLogger(
+            TEST_SECTION_NAME,
+            TEST_COLUMNS,
+            TEST_DATA_VALID)
+
+    private val stringWriter = StringWriter()
+    private val printWriter = PrintWriter(stringWriter)
+
+    @Before
+    fun setup() {
+    }
+
+    @Test
+    fun testTableLogger_header() {
+        logger.printTableData(printWriter)
+        val lines = logLines(stringWriter)
+
+        val line1 = lines[0]
+
+        assertEquals("table logger header is incorrect",
+                HEADER_PREFIX + TEST_SECTION_NAME, line1)
+    }
+
+    @Test
+    fun testTableLogger_version() {
+        logger.printTableData(printWriter)
+        val lines = logLines(stringWriter)
+
+        val line2 = lines[1]
+
+        assertEquals("version probably shouldn't have changed",
+        "version $VERSION", line2)
+    }
+
+    @Test
+    fun testTableLogger_footer() {
+        logger.printTableData(printWriter)
+        val lines = logLines(stringWriter)
+
+        val footer = lines.last()
+        android.util.Log.d("evanevan", footer)
+        android.util.Log.d("evanevan", lines.toString())
+
+        assertEquals("table logger footer is incorrect",
+                FOOTER_PREFIX + TEST_SECTION_NAME, footer)
+    }
+
+    @Test
+    fun testTableLogger_data_length() {
+        logger.printTableData(printWriter)
+        val lines = logLines(stringWriter)
+
+        // Header is 2 lines long, plus a line for the column defs so data is lines[3..last()-1]
+        val data = lines.subList(3, lines.size - 1)
+        assertEquals(TEST_DATA_LENGTH, data.size)
+    }
+
+    @Test
+    fun testTableLogger_data_columns() {
+        logger.printTableData(printWriter)
+        val lines = logLines(stringWriter)
+
+        // Header is always 2 lines long so data is lines[2..last()-1]
+        val data = lines.subList(3, lines.size - 1)
+
+        data.forEach { dataLine ->
+            assertEquals(TEST_COLUMNS.size, dataLine.split(SEPARATOR).size)
+        }
+    }
+
+    @Test
+    fun testInvalidLinesAreFiltered() {
+        // GIVEN an invalid data row, by virtue of having an extra field
+        val invalidLine = List(TEST_COLUMNS.size) { col ->
+            "data${col}X"
+        } + "INVALID COLUMN"
+        val invalidData = TEST_DATA_VALID.toMutableList().also {
+            it.add(invalidLine)
+        }
+
+        // WHEN the table logger is created and asked to print the table
+        val tableLogger = DumpsysTableLogger(
+                TEST_SECTION_NAME,
+                TEST_COLUMNS,
+                invalidData)
+
+        tableLogger.printTableData(printWriter)
+
+        // THEN the invalid line is filtered out
+        val invalidString = invalidLine.joinToString(separator = SEPARATOR)
+        val logString = stringWriter.toString()
+
+        assertThat(logString).doesNotContain(invalidString)
+    }
+
+    private fun logLines(sw: StringWriter): List<String> {
+        return sw.toString().split("\n").filter { it.isNotBlank() }
+    }
+}
+
+// Copying these here from [DumpsysTableLogger] so that we catch any accidental versioning change
+private const val HEADER_PREFIX = "SystemUI TableSection START: "
+private const val FOOTER_PREFIX = "SystemUI TableSection END: "
+private const val SEPARATOR = "|" // TBD
+private const val VERSION = "1"
+
+const val TEST_SECTION_NAME = "TestTableSection"
+const val TEST_DATA_LENGTH = 5
+val TEST_COLUMNS = arrayListOf("col1", "col2", "col3")
+val TEST_DATA_VALID = List(TEST_DATA_LENGTH) { row ->
+    List(TEST_COLUMNS.size) { col ->
+        "data$col$row"
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferFreezerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferFreezerTest.kt
index 5dea5a1..3b4888f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferFreezerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferFreezerTest.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.nullable
 import com.android.systemui.util.time.FakeSystemClock
 import org.junit.Before
 import org.junit.Test
@@ -68,7 +69,8 @@
                         any(IntentFilter::class.java),
                         eq(executor),
                         any(UserHandle::class.java),
-                        anyInt())
+                        anyInt(),
+                        nullable())
         receiver = receiverCaptor.value
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
index 46e67568..67fe044 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
@@ -52,6 +52,7 @@
     @Mock lateinit var panel: MediaControlPanel
     @Mock lateinit var visualStabilityProvider: VisualStabilityProvider
     @Mock lateinit var mediaHostStatesManager: MediaHostStatesManager
+    @Mock lateinit var mediaHostState: MediaHostState
     @Mock lateinit var activityStarter: ActivityStarter
     @Mock @Main private lateinit var executor: DelayableExecutor
     @Mock lateinit var mediaDataManager: MediaDataManager
@@ -142,7 +143,7 @@
         expected.forEach {
             clock.setCurrentTimeMillis(it.third)
             MediaPlayerData.addMediaPlayer(it.first, it.second.copy(notificationKey = it.first),
-                panel, clock)
+                panel, clock, isSsReactivated = false)
         }
 
         for ((index, key) in MediaPlayerData.playerKeys().withIndex()) {
@@ -188,4 +189,40 @@
 
         verify(logger).logCarouselSettings()
     }
+
+    @Test
+    fun testLocationChangeQs_logged() {
+        mediaCarouselController.onDesiredLocationChanged(
+            MediaHierarchyManager.LOCATION_QS,
+            mediaHostState,
+            animate = false)
+        verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_QS)
+    }
+
+    @Test
+    fun testLocationChangeQqs_logged() {
+        mediaCarouselController.onDesiredLocationChanged(
+            MediaHierarchyManager.LOCATION_QQS,
+            mediaHostState,
+            animate = false)
+        verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_QQS)
+    }
+
+    @Test
+    fun testLocationChangeLockscreen_logged() {
+        mediaCarouselController.onDesiredLocationChanged(
+            MediaHierarchyManager.LOCATION_LOCKSCREEN,
+            mediaHostState,
+            animate = false)
+        verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_LOCKSCREEN)
+    }
+
+    @Test
+    fun testLocationChangeDream_logged() {
+        mediaCarouselController.onDesiredLocationChanged(
+            MediaHierarchyManager.LOCATION_DREAM_OVERLAY,
+            mediaHostState,
+            animate = false)
+        verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_DREAM_OVERLAY)
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
index f2e3edb..538a9c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.media
 
+import android.app.PendingIntent
 import org.mockito.Mockito.`when` as whenever
 import android.content.Intent
 import android.graphics.Color
@@ -104,6 +105,7 @@
     @Mock private lateinit var mediaOutputDialogFactory: MediaOutputDialogFactory
     @Mock private lateinit var mediaCarouselController: MediaCarouselController
     @Mock private lateinit var falsingManager: FalsingManager
+    @Mock private lateinit var transitionParent: ViewGroup
     private lateinit var appIcon: ImageView
     private lateinit var albumView: ImageView
     private lateinit var titleText: TextView
@@ -241,6 +243,10 @@
         whenever(viewHolder.seamlessText).thenReturn(seamlessText)
         whenever(viewHolder.seekBar).thenReturn(seekBar)
 
+        // Transition View
+        whenever(view.parent).thenReturn(transitionParent)
+        whenever(view.rootView).thenReturn(transitionParent)
+
         // Action buttons
         whenever(viewHolder.actionPlayPause).thenReturn(actionPlayPause)
         whenever(viewHolder.getAction(R.id.actionPlayPause)).thenReturn(actionPlayPause)
@@ -749,6 +755,20 @@
     }
 
     @Test
+    fun tapContentView_isLogged() {
+        val pendingIntent = mock(PendingIntent::class.java)
+        val captor = ArgumentCaptor.forClass(View.OnClickListener::class.java)
+        val data = mediaData.copy(clickIntent = pendingIntent)
+        player.attachPlayer(viewHolder)
+        player.bindPlayer(data, KEY)
+        verify(viewHolder.player).setOnClickListener(captor.capture())
+
+        captor.value.onClick(viewHolder.player)
+
+        verify(logger).logTapContentView(anyInt(), eq(PACKAGE), eq(instanceId))
+    }
+
+    @Test
     fun logSeek() {
         player.attachPlayer(viewHolder)
         player.bindPlayer(mediaData, KEY)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
index b0f6a80..eacec20 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
@@ -87,10 +87,10 @@
     public void eventNotEmittedWithoutDevice() {
         // WHEN data source emits an event without device data
         mManager.onMediaDataLoaded(KEY, null, mMediaData, true /* immediately */,
-                0 /* receivedSmartspaceCardLatency */);
+                0 /* receivedSmartspaceCardLatency */, false /* isSsReactivated */);
         // THEN an event isn't emitted
         verify(mListener, never()).onMediaDataLoaded(eq(KEY), any(), any(), anyBoolean(),
-                anyInt());
+                anyInt(), anyBoolean());
     }
 
     @Test
@@ -99,7 +99,7 @@
         mManager.onMediaDeviceChanged(KEY, null, mDeviceData);
         // THEN an event isn't emitted
         verify(mListener, never()).onMediaDataLoaded(eq(KEY), any(), any(), anyBoolean(),
-                anyInt());
+                anyInt(), anyBoolean());
     }
 
     @Test
@@ -108,11 +108,11 @@
         mManager.onMediaDeviceChanged(KEY, null, mDeviceData);
         // WHEN media event is received
         mManager.onMediaDataLoaded(KEY, null, mMediaData, true /* immediately */,
-                0 /* receivedSmartspaceCardLatency */);
+                0 /* receivedSmartspaceCardLatency */, false /* isSsReactivated */);
         // THEN the listener receives a combined event
         ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
         verify(mListener).onMediaDataLoaded(eq(KEY), any(), captor.capture(), anyBoolean(),
-                anyInt());
+                anyInt(), anyBoolean());
         assertThat(captor.getValue().getDevice()).isNotNull();
     }
 
@@ -120,13 +120,13 @@
     public void emitEventAfterMediaFirst() {
         // GIVEN that media event has already been received
         mManager.onMediaDataLoaded(KEY, null, mMediaData, true /* immediately */,
-                0 /* receivedSmartspaceCardLatency */);
+                0 /* receivedSmartspaceCardLatency */, false /* isSsReactivated */);
         // WHEN device event is received
         mManager.onMediaDeviceChanged(KEY, null, mDeviceData);
         // THEN the listener receives a combined event
         ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
         verify(mListener).onMediaDataLoaded(eq(KEY), any(), captor.capture(), anyBoolean(),
-                anyInt());
+                anyInt(), anyBoolean());
         assertThat(captor.getValue().getDevice()).isNotNull();
     }
 
@@ -134,16 +134,16 @@
     public void migrateKeyMediaFirst() {
         // GIVEN that media and device info has already been received
         mManager.onMediaDataLoaded(OLD_KEY, null, mMediaData, true /* immediately */,
-                0 /* receivedSmartspaceCardLatency */);
+                0 /* receivedSmartspaceCardLatency */, false /* isSsReactivated */);
         mManager.onMediaDeviceChanged(OLD_KEY, null, mDeviceData);
         reset(mListener);
         // WHEN a key migration event is received
         mManager.onMediaDataLoaded(KEY, OLD_KEY, mMediaData, true /* immediately */,
-                0 /* receivedSmartspaceCardLatency */);
+                0 /* receivedSmartspaceCardLatency */, false /* isSsReactivated */);
         // THEN the listener receives a combined event
         ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
         verify(mListener).onMediaDataLoaded(eq(KEY), eq(OLD_KEY), captor.capture(), anyBoolean(),
-                anyInt());
+                anyInt(), anyBoolean());
         assertThat(captor.getValue().getDevice()).isNotNull();
     }
 
@@ -151,7 +151,7 @@
     public void migrateKeyDeviceFirst() {
         // GIVEN that media and device info has already been received
         mManager.onMediaDataLoaded(OLD_KEY, null, mMediaData, true /* immediately */,
-                0 /* receivedSmartspaceCardLatency */);
+                0 /* receivedSmartspaceCardLatency */, false /* isSsReactivated */);
         mManager.onMediaDeviceChanged(OLD_KEY, null, mDeviceData);
         reset(mListener);
         // WHEN a key migration event is received
@@ -159,7 +159,7 @@
         // THEN the listener receives a combined event
         ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
         verify(mListener).onMediaDataLoaded(eq(KEY), eq(OLD_KEY), captor.capture(), anyBoolean(),
-                anyInt());
+                anyInt(), anyBoolean());
         assertThat(captor.getValue().getDevice()).isNotNull();
     }
 
@@ -167,17 +167,17 @@
     public void migrateKeyMediaAfter() {
         // GIVEN that media and device info has already been received
         mManager.onMediaDataLoaded(OLD_KEY, null, mMediaData, true /* immediately */,
-                0 /* receivedSmartspaceCardLatency */);
+                0 /* receivedSmartspaceCardLatency */, false /* isSsReactivated */);
         mManager.onMediaDeviceChanged(OLD_KEY, null, mDeviceData);
         mManager.onMediaDeviceChanged(KEY, OLD_KEY, mDeviceData);
         reset(mListener);
         // WHEN a second key migration event is received for media
         mManager.onMediaDataLoaded(KEY, OLD_KEY, mMediaData, true /* immediately */,
-                0 /* receivedSmartspaceCardLatency */);
+                0 /* receivedSmartspaceCardLatency */, false /* isSsReactivated */);
         // THEN the key has already been migrated
         ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
         verify(mListener).onMediaDataLoaded(eq(KEY), eq(KEY), captor.capture(), anyBoolean(),
-                anyInt());
+                anyInt(), anyBoolean());
         assertThat(captor.getValue().getDevice()).isNotNull();
     }
 
@@ -185,17 +185,17 @@
     public void migrateKeyDeviceAfter() {
         // GIVEN that media and device info has already been received
         mManager.onMediaDataLoaded(OLD_KEY, null, mMediaData, true /* immediately */,
-                0 /* receivedSmartspaceCardLatency */);
+                0 /* receivedSmartspaceCardLatency */, false /* isSsReactivated */);
         mManager.onMediaDeviceChanged(OLD_KEY, null, mDeviceData);
         mManager.onMediaDataLoaded(KEY, OLD_KEY, mMediaData, true /* immediately */,
-                0 /* receivedSmartspaceCardLatency */);
+                0 /* receivedSmartspaceCardLatency */, false /* isSsReactivated */);
         reset(mListener);
         // WHEN a second key migration event is received for the device
         mManager.onMediaDeviceChanged(KEY, OLD_KEY, mDeviceData);
         // THEN the key has already be migrated
         ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
         verify(mListener).onMediaDataLoaded(eq(KEY), eq(KEY), captor.capture(), anyBoolean(),
-                anyInt());
+                anyInt(), anyBoolean());
         assertThat(captor.getValue().getDevice()).isNotNull();
     }
 
@@ -210,7 +210,7 @@
     @Test
     public void mediaDataRemovedAfterMediaEvent() {
         mManager.onMediaDataLoaded(KEY, null, mMediaData, true /* immediately */,
-                0 /* receivedSmartspaceCardLatency */);
+                0 /* receivedSmartspaceCardLatency */, false /* isSsReactivated */);
         mManager.onMediaDataRemoved(KEY);
         verify(mListener).onMediaDataRemoved(eq(KEY));
     }
@@ -226,14 +226,14 @@
     public void mediaDataKeyUpdated() {
         // GIVEN that device and media events have already been received
         mManager.onMediaDataLoaded(KEY, null, mMediaData, true /* immediately */,
-                0 /* receivedSmartspaceCardLatency */);
+                0 /* receivedSmartspaceCardLatency */, false /* isSsReactivated */);
         mManager.onMediaDeviceChanged(KEY, null, mDeviceData);
         // WHEN the key is changed
         mManager.onMediaDataLoaded("NEW_KEY", KEY, mMediaData, true /* immediately */,
-                0 /* receivedSmartspaceCardLatency */);
+                0 /* receivedSmartspaceCardLatency */, false /* isSsReactivated */);
         // THEN the listener gets a load event with the correct keys
         ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
         verify(mListener).onMediaDataLoaded(
-                eq("NEW_KEY"), any(), captor.capture(), anyBoolean(), anyInt());
+                eq("NEW_KEY"), any(), captor.capture(), anyBoolean(), anyInt(), anyBoolean());
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
index b8e249f..3b996d4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
@@ -115,7 +115,7 @@
 
         // THEN we should tell the listener
         verify(listener).onMediaDataLoaded(eq(KEY), eq(null), eq(dataMain), eq(true),
-                eq(0))
+                eq(0), eq(false))
     }
 
     @Test
@@ -124,7 +124,8 @@
         mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest)
 
         // THEN we should NOT tell the listener
-        verify(listener, never()).onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt())
+        verify(listener, never()).onMediaDataLoaded(any(), any(), any(), anyBoolean(),
+                anyInt(), anyBoolean())
     }
 
     @Test
@@ -171,51 +172,56 @@
 
         // THEN we should add back the guest user media
         verify(listener).onMediaDataLoaded(eq(KEY_ALT), eq(null), eq(dataGuest), eq(true),
-                eq(0))
+                eq(0), eq(false))
 
         // but not the main user's
         verify(listener, never()).onMediaDataLoaded(eq(KEY), any(), eq(dataMain), anyBoolean(),
-                anyInt())
+                anyInt(), anyBoolean())
     }
 
     @Test
-    fun testHasAnyMedia() {
-        assertThat(mediaDataFilter.hasAnyMedia()).isFalse()
+    fun testHasAnyMediaOrRecommendation() {
+        assertThat(mediaDataFilter.hasAnyMediaOrRecommendation()).isFalse()
 
         mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = dataMain)
+        assertThat(mediaDataFilter.hasAnyMediaOrRecommendation()).isTrue()
         assertThat(mediaDataFilter.hasAnyMedia()).isTrue()
     }
 
     @Test
-    fun testHasActiveMedia() {
-        assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
+    fun testHasActiveMediaOrRecommendation() {
+        assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse()
         val data = dataMain.copy(active = true)
 
         mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = data)
+        assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isTrue()
         assertThat(mediaDataFilter.hasActiveMedia()).isTrue()
     }
 
     @Test
-    fun testHasAnyMedia_onlyCurrentUser() {
-        assertThat(mediaDataFilter.hasAnyMedia()).isFalse()
+    fun testHasAnyMediaOrRecommendation_onlyCurrentUser() {
+        assertThat(mediaDataFilter.hasAnyMediaOrRecommendation()).isFalse()
 
         mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = dataGuest)
+        assertThat(mediaDataFilter.hasAnyMediaOrRecommendation()).isFalse()
         assertThat(mediaDataFilter.hasAnyMedia()).isFalse()
     }
 
     @Test
-    fun testHasActiveMedia_onlyCurrentUser() {
-        assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
+    fun testHasActiveMediaOrRecommendation_onlyCurrentUser() {
+        assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse()
         val data = dataGuest.copy(active = true)
 
         mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = data)
-        assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
+        assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse()
+        assertThat(mediaDataFilter.hasAnyMedia()).isFalse()
     }
 
     @Test
     fun testOnNotificationRemoved_doesntHaveMedia() {
         mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = dataMain)
         mediaDataFilter.onMediaDataRemoved(KEY)
+        assertThat(mediaDataFilter.hasAnyMediaOrRecommendation()).isFalse()
         assertThat(mediaDataFilter.hasAnyMedia()).isFalse()
     }
 
@@ -232,9 +238,9 @@
         mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
 
         verify(listener)
-                .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(true),
-                        eq(false))
-        assertThat(mediaDataFilter.hasActiveMedia()).isTrue()
+                .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(true))
+        assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isTrue()
+        assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
     }
 
     @Test
@@ -243,9 +249,10 @@
 
         mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
 
-        verify(listener, never()).onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt())
-        verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean(),
-                anyBoolean())
+        verify(listener, never()).onMediaDataLoaded(any(), any(), any(), anyBoolean(),
+                anyInt(), anyBoolean())
+        verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
+        assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse()
         assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
     }
 
@@ -257,9 +264,9 @@
         mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
 
         verify(listener)
-                .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(true),
-                        eq(true))
-        assertThat(mediaDataFilter.hasActiveMedia()).isTrue()
+                .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(true))
+        assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isTrue()
+        assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
     }
 
     @Test
@@ -271,8 +278,8 @@
         clock.advanceTime(SMARTSPACE_MAX_AGE + 100)
         mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
 
-        verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean(),
-                anyBoolean())
+        verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
+        assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse()
         assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
     }
 
@@ -284,16 +291,16 @@
         val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
         mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
         verify(listener).onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true),
-                eq(0))
+                eq(0), eq(false))
 
         // AND we get a smartspace signal
         mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
 
         // THEN we should tell listeners to treat the media as not active instead
         verify(listener, never()).onMediaDataLoaded(eq(KEY), eq(KEY), any(), anyBoolean(),
-                anyInt())
-        verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean(),
-                anyBoolean())
+                anyInt(), anyBoolean())
+        verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
+        assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse()
         assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
     }
 
@@ -305,7 +312,7 @@
         val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
         mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
         verify(listener).onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true),
-                eq(0))
+                eq(0), eq(false))
 
         // AND we get a smartspace signal
         mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
@@ -313,11 +320,10 @@
         // THEN we should tell listeners to treat the media as active instead
         val dataCurrentAndActive = dataCurrent.copy(active = true)
         verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY), eq(dataCurrentAndActive), eq(true),
-                eq(100))
-        assertThat(mediaDataFilter.hasActiveMedia()).isTrue()
+                eq(100), eq(true))
+        assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse()
         // Smartspace update shouldn't be propagated for the empty rec list.
-        verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean(),
-                anyBoolean())
+        verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
     }
 
     @Test
@@ -326,7 +332,7 @@
         val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
         mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
         verify(listener).onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true),
-                eq(0))
+                eq(0), eq(false))
 
         // AND we get a smartspace signal
         mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
@@ -334,12 +340,11 @@
         // THEN we should tell listeners to treat the media as active instead
         val dataCurrentAndActive = dataCurrent.copy(active = true)
         verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY), eq(dataCurrentAndActive), eq(true),
-                eq(100))
-        assertThat(mediaDataFilter.hasActiveMedia()).isTrue()
+                eq(100), eq(true))
+        assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isTrue()
         // Smartspace update should also be propagated but not prioritized.
         verify(listener)
-                .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false),
-                        eq(true))
+                .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
     }
 
     @Test
@@ -348,6 +353,7 @@
         mediaDataFilter.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
 
         verify(listener).onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
+        assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse()
         assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
     }
 
@@ -356,17 +362,18 @@
         val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
         mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
         verify(listener).onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true),
-                eq(0))
+                eq(0), eq(false))
 
         mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
 
         val dataCurrentAndActive = dataCurrent.copy(active = true)
         verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY), eq(dataCurrentAndActive), eq(true),
-                eq(100))
+                eq(100), eq(true))
 
         mediaDataFilter.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
 
         verify(listener).onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
+        assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse()
         assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
index ccd8ef1..1921cb6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
@@ -210,7 +210,7 @@
         backgroundExecutor.runAllReady()
         foregroundExecutor.runAllReady()
         verify(listener).onMediaDataLoaded(eq(PACKAGE_NAME), eq(null), capture(mediaDataCaptor),
-            eq(true), eq(0))
+            eq(true), eq(0), eq(false))
 
         mediaDataManager.setTimedOut(PACKAGE_NAME, timedOut = true)
         verify(logger).logMediaTimeout(anyInt(), eq(PACKAGE_NAME),
@@ -244,7 +244,7 @@
         assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
         assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
         verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
-                eq(0))
+                eq(0), eq(false))
         assertThat(mediaDataCaptor.value!!.active).isTrue()
     }
 
@@ -266,7 +266,7 @@
         assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
         assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
         verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
-                eq(0))
+                eq(0), eq(false))
         assertThat(mediaDataCaptor.value!!.playbackLocation).isEqualTo(
                 MediaData.PLAYBACK_CAST_REMOTE)
         verify(logger).logActiveMediaAdded(anyInt(), eq(SYSTEM_PACKAGE_NAME),
@@ -295,7 +295,7 @@
         // THEN the media data indicates that it is for resumption
         verify(listener)
             .onMediaDataLoaded(eq(PACKAGE_NAME), eq(KEY), capture(mediaDataCaptor), eq(true),
-                    eq(0))
+                    eq(0), eq(false))
         assertThat(mediaDataCaptor.value.resumption).isTrue()
         assertThat(mediaDataCaptor.value.isPlaying).isFalse()
         verify(logger).logActiveConvertedToResume(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
@@ -311,7 +311,7 @@
         assertThat(foregroundExecutor.runAllReady()).isEqualTo(2)
         verify(listener)
             .onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
-                    eq(0))
+                    eq(0), eq(false))
         val data = mediaDataCaptor.value
         assertThat(data.resumption).isFalse()
         val resumableData = data.copy(resumeAction = Runnable {})
@@ -323,7 +323,7 @@
         // THEN the data is for resumption and the key is migrated to the package name
         verify(listener)
             .onMediaDataLoaded(eq(PACKAGE_NAME), eq(KEY), capture(mediaDataCaptor), eq(true),
-                    eq(0))
+                    eq(0), eq(false))
         assertThat(mediaDataCaptor.value.resumption).isTrue()
         verify(listener, never()).onMediaDataRemoved(eq(KEY))
         // WHEN the second is removed
@@ -332,7 +332,7 @@
         verify(listener)
             .onMediaDataLoaded(
                 eq(PACKAGE_NAME), eq(PACKAGE_NAME), capture(mediaDataCaptor), eq(true),
-                    eq(0))
+                    eq(0), eq(false))
         assertThat(mediaDataCaptor.value.resumption).isTrue()
         verify(listener).onMediaDataRemoved(eq(KEY_2))
     }
@@ -373,7 +373,7 @@
         // THEN the media data indicates that it is for resumption
         verify(listener)
             .onMediaDataLoaded(eq(PACKAGE_NAME), eq(null), capture(mediaDataCaptor), eq(true),
-                    eq(0))
+                    eq(0), eq(false))
         val data = mediaDataCaptor.value
         assertThat(data.resumption).isTrue()
         assertThat(data.song).isEqualTo(SESSION_TITLE)
@@ -396,7 +396,7 @@
         assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
         assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
         verify(listener).onMediaDataLoaded(eq(PACKAGE_NAME), eq(null), capture(mediaDataCaptor),
-            eq(true), eq(0))
+            eq(true), eq(0), eq(false))
         val data = mediaDataCaptor.value
         mediaDataManager.setMediaResumptionEnabled(false)
 
@@ -445,7 +445,7 @@
         assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
         verify(listener)
             .onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
-                    eq(0))
+                    eq(0), eq(false))
     }
 
     @Test
@@ -456,7 +456,7 @@
             eq(SmartspaceMediaData(KEY_MEDIA_SMARTSPACE, true /* isActive */, true /*isValid */,
                 PACKAGE_NAME, mediaSmartspaceBaseAction, listOf(mediaRecommendationItem),
                 DISMISS_INTENT, 0, 1234L)),
-            eq(false), eq(false))
+            eq(false))
     }
 
     @Test
@@ -469,7 +469,7 @@
                 .copy(targetId = KEY_MEDIA_SMARTSPACE, isActive = true,
                     isValid = false, dismissIntent = DISMISS_INTENT,
                 headphoneConnectionTimeMillis = 1234L)),
-            eq(false), eq(false))
+            eq(false))
     }
 
     @Test
@@ -489,14 +489,14 @@
             eq(EMPTY_SMARTSPACE_MEDIA_DATA
                 .copy(targetId = KEY_MEDIA_SMARTSPACE, isActive = true,
                     isValid = false, dismissIntent = null, headphoneConnectionTimeMillis = 1234L)),
-            eq(false), eq(false))
+            eq(false))
     }
 
     @Test
     fun testOnSmartspaceMediaDataLoaded_hasNoneMediaTarget_notCallsListener() {
         smartspaceMediaDataProvider.onTargetsAvailable(listOf())
         verify(listener, never())
-                .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean(), anyBoolean())
+                .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean())
     }
 
     @Test
@@ -520,7 +520,7 @@
 
         // THEN smartspace signal is ignored
         verify(listener, never())
-                .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean(), anyBoolean())
+                .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean())
     }
 
     @Test
@@ -528,7 +528,7 @@
         // GIVEN a media recommendation card is present
         smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
         verify(listener).onSmartspaceMediaDataLoaded(eq(KEY_MEDIA_SMARTSPACE), anyObject(),
-                anyBoolean(), anyBoolean())
+                anyBoolean())
 
         // WHEN the media recommendation setting is turned off
         Settings.Secure.putInt(context.contentResolver,
@@ -562,7 +562,7 @@
 
         // THEN the last active time is not changed
         verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY), capture(mediaDataCaptor), eq(true),
-                eq(0))
+                eq(0), eq(false))
         assertThat(mediaDataCaptor.value.lastActive).isLessThan(currentTime)
     }
 
@@ -584,7 +584,7 @@
         // THEN the last active time is not changed
         verify(listener)
             .onMediaDataLoaded(eq(PACKAGE_NAME), eq(KEY), capture(mediaDataCaptor), eq(true),
-                    eq(0))
+                    eq(0), eq(false))
         assertThat(mediaDataCaptor.value.resumption).isTrue()
         assertThat(mediaDataCaptor.value.lastActive).isLessThan(currentTime)
 
@@ -615,7 +615,7 @@
 
         // THEN only the first MAX_COMPACT_ACTIONS are actually set
         verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
-                eq(0))
+                eq(0), eq(false))
         assertThat(mediaDataCaptor.value.actionsToShowInCompact.size).isEqualTo(
                 MediaDataManager.MAX_COMPACT_ACTIONS)
     }
@@ -640,7 +640,7 @@
         assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
         assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
         verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
-                eq(0))
+                eq(0), eq(false))
 
         assertThat(mediaDataCaptor.value!!.semanticActions).isNull()
         assertThat(mediaDataCaptor.value!!.actions).hasSize(1)
@@ -726,6 +726,25 @@
     }
 
     @Test
+    fun testPlaybackActions_connecting() {
+        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        val stateActions = PlaybackState.ACTION_PLAY
+        val stateBuilder = PlaybackState.Builder()
+                .setState(PlaybackState.STATE_BUFFERING, 0, 10f)
+                .setActions(stateActions)
+        whenever(controller.playbackState).thenReturn(stateBuilder.build())
+
+        addNotificationAndLoad()
+
+        assertThat(mediaDataCaptor.value!!.semanticActions).isNotNull()
+        val actions = mediaDataCaptor.value!!.semanticActions!!
+
+        assertThat(actions.playOrPause).isNotNull()
+        assertThat(actions.playOrPause!!.contentDescription).isEqualTo(
+                context.getString(R.string.controls_media_button_connecting))
+    }
+
+    @Test
     fun testPlaybackActions_reservedSpace() {
         val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4")
         whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
@@ -803,6 +822,6 @@
         assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
         assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
         verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
-            eq(0))
+            eq(0), eq(false))
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
index e6f48ec..10eeb11 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
@@ -265,20 +265,58 @@
     }
 
     @Test
-    fun onAboutToConnectDeviceChangedWithNonNullParams() {
+    fun onAboutToConnectDeviceAdded_findsDeviceInfoFromAddress() {
         manager.onMediaDataLoaded(KEY, null, mediaData)
         // Run and reset the executors and listeners so we only focus on new events.
         fakeBgExecutor.runAllReady()
         fakeFgExecutor.runAllReady()
         reset(listener)
 
-        val deviceCallback = captureCallback()
+        // Ensure we'll get device info when using the address
+        val fullMediaDevice = mock(MediaDevice::class.java)
+        val address = "fakeAddress"
+        val nameFromDevice = "nameFromDevice"
+        val iconFromDevice = mock(Drawable::class.java)
+        whenever(lmm.getMediaDeviceById(eq(address))).thenReturn(fullMediaDevice)
+        whenever(fullMediaDevice.name).thenReturn(nameFromDevice)
+        whenever(fullMediaDevice.iconWithoutBackground).thenReturn(iconFromDevice)
+
         // WHEN the about-to-connect device changes to non-null
+        val deviceCallback = captureCallback()
+        val nameFromParam = "nameFromParam"
+        val iconFromParam = mock(Drawable::class.java)
+        deviceCallback.onAboutToConnectDeviceAdded(address, nameFromParam, iconFromParam)
+        assertThat(fakeFgExecutor.runAllReady()).isEqualTo(1)
+
+        // THEN the about-to-connect device based on the address is returned
+        val data = captureDeviceData(KEY)
+        assertThat(data.enabled).isTrue()
+        assertThat(data.name).isEqualTo(nameFromDevice)
+        assertThat(data.name).isNotEqualTo(nameFromParam)
+        assertThat(data.icon).isEqualTo(iconFromDevice)
+        assertThat(data.icon).isNotEqualTo(iconFromParam)
+    }
+
+    @Test
+    fun onAboutToConnectDeviceAdded_cantFindDeviceInfoFromAddress() {
+        manager.onMediaDataLoaded(KEY, null, mediaData)
+        // Run and reset the executors and listeners so we only focus on new events.
+        fakeBgExecutor.runAllReady()
+        fakeFgExecutor.runAllReady()
+        reset(listener)
+
+        // Ensure we can't get device info based on the address
+        val address = "fakeAddress"
+        whenever(lmm.getMediaDeviceById(eq(address))).thenReturn(null)
+
+        // WHEN the about-to-connect device changes to non-null
+        val deviceCallback = captureCallback()
         val name = "AboutToConnectDeviceName"
         val mockIcon = mock(Drawable::class.java)
-        deviceCallback.onAboutToConnectDeviceChanged(name, mockIcon)
+        deviceCallback.onAboutToConnectDeviceAdded(address, name, mockIcon)
         assertThat(fakeFgExecutor.runAllReady()).isEqualTo(1)
-        // THEN the about-to-connect device is returned
+
+        // THEN the about-to-connect device based on the parameters is returned
         val data = captureDeviceData(KEY)
         assertThat(data.enabled).isTrue()
         assertThat(data.name).isEqualTo(name)
@@ -286,21 +324,21 @@
     }
 
     @Test
-    fun onAboutToConnectDeviceChangedWithNullParams() {
+    fun onAboutToConnectDeviceAddedThenRemoved_usesNormalDevice() {
         manager.onMediaDataLoaded(KEY, null, mediaData)
         fakeBgExecutor.runAllReady()
         val deviceCallback = captureCallback()
         // First set a non-null about-to-connect device
-        deviceCallback.onAboutToConnectDeviceChanged(
-            "AboutToConnectDeviceName", mock(Drawable::class.java)
+        deviceCallback.onAboutToConnectDeviceAdded(
+            "fakeAddress", "AboutToConnectDeviceName", mock(Drawable::class.java)
         )
         // Run and reset the executors and listeners so we only focus on new events.
         fakeBgExecutor.runAllReady()
         fakeFgExecutor.runAllReady()
         reset(listener)
 
-        // WHEN the about-to-connect device changes to null
-        deviceCallback.onAboutToConnectDeviceChanged(null, null)
+        // WHEN hasDevice switches to false
+        deviceCallback.onAboutToConnectDeviceRemoved()
         assertThat(fakeFgExecutor.runAllReady()).isEqualTo(1)
         // THEN the normal device is returned
         val data = captureDeviceData(KEY)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt
index 7bd210d..6e38d264 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt
@@ -61,8 +61,10 @@
         val playerIsRemote = mock(MediaControlPanel::class.java)
         val dataIsRemote = createMediaData("app2", PLAYING, REMOTE, !RESUMPTION)
 
-        MediaPlayerData.addMediaPlayer("2", dataIsRemote, playerIsRemote, systemClock)
-        MediaPlayerData.addMediaPlayer("1", dataIsPlaying, playerIsPlaying, systemClock)
+        MediaPlayerData.addMediaPlayer("2", dataIsRemote, playerIsRemote, systemClock,
+                isSsReactivated = false)
+        MediaPlayerData.addMediaPlayer("1", dataIsPlaying, playerIsPlaying, systemClock,
+                isSsReactivated = false)
 
         val players = MediaPlayerData.players()
         assertThat(players).hasSize(2)
@@ -77,18 +79,22 @@
         val playerIsPlaying2 = mock(MediaControlPanel::class.java)
         var dataIsPlaying2 = createMediaData("app2", !PLAYING, LOCAL, !RESUMPTION)
 
-        MediaPlayerData.addMediaPlayer("1", dataIsPlaying1, playerIsPlaying1, systemClock)
+        MediaPlayerData.addMediaPlayer("1", dataIsPlaying1, playerIsPlaying1, systemClock,
+                isSsReactivated = false)
         systemClock.advanceTime(1)
-        MediaPlayerData.addMediaPlayer("2", dataIsPlaying2, playerIsPlaying2, systemClock)
+        MediaPlayerData.addMediaPlayer("2", dataIsPlaying2, playerIsPlaying2, systemClock,
+                isSsReactivated = false)
         systemClock.advanceTime(1)
 
         dataIsPlaying1 = createMediaData("app1", !PLAYING, LOCAL, !RESUMPTION)
         dataIsPlaying2 = createMediaData("app2", PLAYING, LOCAL, !RESUMPTION)
 
-        MediaPlayerData.addMediaPlayer("1", dataIsPlaying1, playerIsPlaying1, systemClock)
+        MediaPlayerData.addMediaPlayer("1", dataIsPlaying1, playerIsPlaying1, systemClock,
+                isSsReactivated = false)
         systemClock.advanceTime(1)
 
-        MediaPlayerData.addMediaPlayer("2", dataIsPlaying2, playerIsPlaying2, systemClock)
+        MediaPlayerData.addMediaPlayer("2", dataIsPlaying2, playerIsPlaying2, systemClock,
+                isSsReactivated = false)
         systemClock.advanceTime(1)
 
         val players = MediaPlayerData.players()
@@ -116,14 +122,20 @@
         val dataUndetermined = createMediaData("app6", UNDETERMINED, LOCAL, RESUMPTION)
 
         MediaPlayerData.addMediaPlayer(
-                "3", dataIsStoppedAndLocal, playerIsStoppedAndLocal, systemClock)
+                "3", dataIsStoppedAndLocal, playerIsStoppedAndLocal, systemClock,
+                isSsReactivated = false)
         MediaPlayerData.addMediaPlayer(
-                "5", dataIsStoppedAndRemote, playerIsStoppedAndRemote, systemClock)
-        MediaPlayerData.addMediaPlayer("4", dataCanResume, playerCanResume, systemClock)
-        MediaPlayerData.addMediaPlayer("1", dataIsPlaying, playerIsPlaying, systemClock)
+                "5", dataIsStoppedAndRemote, playerIsStoppedAndRemote, systemClock,
+                isSsReactivated = false)
+        MediaPlayerData.addMediaPlayer("4", dataCanResume, playerCanResume, systemClock,
+                isSsReactivated = false)
+        MediaPlayerData.addMediaPlayer("1", dataIsPlaying, playerIsPlaying, systemClock,
+                isSsReactivated = false)
         MediaPlayerData.addMediaPlayer(
-                "2", dataIsPlayingAndRemote, playerIsPlayingAndRemote, systemClock)
-        MediaPlayerData.addMediaPlayer("6", dataUndetermined, playerUndetermined, systemClock)
+                "2", dataIsPlayingAndRemote, playerIsPlayingAndRemote, systemClock,
+                isSsReactivated = false)
+        MediaPlayerData.addMediaPlayer("6", dataUndetermined, playerUndetermined, systemClock,
+                isSsReactivated = false)
 
         val players = MediaPlayerData.players()
         assertThat(players).hasSize(6)
@@ -141,11 +153,13 @@
 
         assertThat(MediaPlayerData.players()).hasSize(0)
 
-        MediaPlayerData.addMediaPlayer(keyA, data, playerIsPlaying, systemClock)
+        MediaPlayerData.addMediaPlayer(keyA, data, playerIsPlaying, systemClock,
+                isSsReactivated = false)
         systemClock.advanceTime(1)
 
         assertThat(MediaPlayerData.players()).hasSize(1)
-        MediaPlayerData.addMediaPlayer(keyB, data, playerIsPlaying, systemClock)
+        MediaPlayerData.addMediaPlayer(keyB, data, playerIsPlaying, systemClock,
+                isSsReactivated = false)
         systemClock.advanceTime(1)
 
         assertThat(MediaPlayerData.players()).hasSize(2)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt
index 2d34913..3e98a12 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt
@@ -149,7 +149,7 @@
                 resumeBrowserFactory, dumpManager, clock)
         listener.setManager(mediaDataManager)
         verify(broadcastDispatcher, never()).registerReceiver(eq(listener.userChangeReceiver),
-            any(), any(), any(), anyInt())
+            any(), any(), any(), anyInt(), any())
 
         // When data is loaded, we do NOT execute or update anything
         listener.onMediaDataLoaded(KEY, OLD_KEY, data)
@@ -272,7 +272,7 @@
         // Make sure broadcast receiver is registered
         resumeListener.setManager(mediaDataManager)
         verify(broadcastDispatcher).registerReceiver(eq(resumeListener.userChangeReceiver),
-                any(), any(), any(), anyInt())
+                any(), any(), any(), anyInt(), any())
 
         // When we get an unlock event
         val intent = Intent(Intent.ACTION_USER_UNLOCKED)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaSessionBasedFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaSessionBasedFilterTest.kt
index ee4f8db..5586453 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaSessionBasedFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaSessionBasedFilterTest.kt
@@ -162,7 +162,7 @@
         bgExecutor.runAllReady()
         fgExecutor.runAllReady()
         verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true),
-                eq(0))
+                eq(0), eq(false))
     }
 
     @Test
@@ -185,7 +185,7 @@
         fgExecutor.runAllReady()
         // THEN the event is not filtered
         verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true),
-                eq(0))
+                eq(0), eq(false))
     }
 
     @Test
@@ -215,7 +215,7 @@
         fgExecutor.runAllReady()
         // THEN the event is not filtered
         verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true),
-                eq(0))
+                eq(0), eq(false))
     }
 
     @Test
@@ -231,14 +231,14 @@
         fgExecutor.runAllReady()
         // THEN the event is not filtered
         verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true),
-                eq(0))
+                eq(0), eq(false))
         // WHEN a loaded event is received that matches the local session
         filter.onMediaDataLoaded(KEY, null, mediaData2)
         bgExecutor.runAllReady()
         fgExecutor.runAllReady()
         // THEN the event is filtered
         verify(mediaListener, never()).onMediaDataLoaded(
-            eq(KEY), eq(null), eq(mediaData2), anyBoolean(), anyInt())
+            eq(KEY), eq(null), eq(mediaData2), anyBoolean(), anyInt(), anyBoolean())
     }
 
     @Test
@@ -255,7 +255,7 @@
         // THEN the event is not filtered because there isn't a notification for the remote
         // session.
         verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true),
-                eq(0))
+                eq(0), eq(false))
     }
 
     @Test
@@ -273,14 +273,15 @@
         fgExecutor.runAllReady()
         // THEN the event is not filtered
         verify(mediaListener).onMediaDataLoaded(eq(key1), eq(null), eq(mediaData1), eq(true),
-                eq(0))
+                eq(0), eq(false))
         // WHEN a loaded event is received that matches the local session
         filter.onMediaDataLoaded(key2, null, mediaData2)
         bgExecutor.runAllReady()
         fgExecutor.runAllReady()
         // THEN the event is filtered
         verify(mediaListener, never())
-            .onMediaDataLoaded(eq(key2), eq(null), eq(mediaData2), anyBoolean(), anyInt())
+            .onMediaDataLoaded(eq(key2), eq(null), eq(mediaData2), anyBoolean(),
+                    anyInt(), anyBoolean())
         // AND there should be a removed event for key2
         verify(mediaListener).onMediaDataRemoved(eq(key2))
     }
@@ -300,14 +301,14 @@
         fgExecutor.runAllReady()
         // THEN the event is not filtered
         verify(mediaListener).onMediaDataLoaded(eq(key1), eq(null), eq(mediaData1), eq(true),
-                eq(0))
+                eq(0), eq(false))
         // WHEN a loaded event is received that matches the remote session
         filter.onMediaDataLoaded(key2, null, mediaData2)
         bgExecutor.runAllReady()
         fgExecutor.runAllReady()
         // THEN the event is not filtered
         verify(mediaListener).onMediaDataLoaded(eq(key2), eq(null), eq(mediaData2), eq(true),
-                eq(0))
+                eq(0), eq(false))
     }
 
     @Test
@@ -324,14 +325,14 @@
         fgExecutor.runAllReady()
         // THEN the event is not filtered
         verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true),
-                eq(0))
+                eq(0), eq(false))
         // WHEN a loaded event is received that matches the local session
         filter.onMediaDataLoaded(KEY, null, mediaData2)
         bgExecutor.runAllReady()
         fgExecutor.runAllReady()
         // THEN the event is not filtered
         verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData2), eq(true),
-                eq(0))
+                eq(0), eq(false))
     }
 
     @Test
@@ -350,7 +351,7 @@
         fgExecutor.runAllReady()
         // THEN the event is not filtered
         verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true),
-                eq(0))
+                eq(0), eq(false))
     }
 
     @Test
@@ -373,7 +374,7 @@
         fgExecutor.runAllReady()
         // THEN the key migration event is fired
         verify(mediaListener).onMediaDataLoaded(eq(key2), eq(key1), eq(mediaData2), eq(true),
-                eq(0))
+                eq(0), eq(false))
     }
 
     @Test
@@ -403,13 +404,14 @@
         fgExecutor.runAllReady()
         // THEN the key migration event is filtered
         verify(mediaListener, never())
-            .onMediaDataLoaded(eq(key2), eq(null), eq(mediaData2), anyBoolean(), anyInt())
+            .onMediaDataLoaded(eq(key2), eq(null), eq(mediaData2), anyBoolean(),
+                    anyInt(), anyBoolean())
         // WHEN a loaded event is received that matches the remote session
         filter.onMediaDataLoaded(key2, null, mediaData1)
         bgExecutor.runAllReady()
         fgExecutor.runAllReady()
         // THEN the key migration event is fired
         verify(mediaListener).onMediaDataLoaded(eq(key2), eq(null), eq(mediaData1), eq(true),
-                eq(0))
+                eq(0), eq(false))
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
index 114fc90..247316a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
@@ -75,11 +75,13 @@
         final MediaDataManager.Listener listener = listenerCaptor.getValue();
 
         when(mMediaDataManager.hasActiveMedia()).thenReturn(false);
-        listener.onMediaDataLoaded(mKey, mOldKey, mData, true, 0);
+        listener.onMediaDataLoaded(mKey, mOldKey, mData, /* immediately= */ true,
+                /* receivedSmartspaceCardLatency= */ 0, /* isSsReactived= */ false);
         verify(mDreamOverlayStateController, never()).addComplication(any());
 
         when(mMediaDataManager.hasActiveMedia()).thenReturn(true);
-        listener.onMediaDataLoaded(mKey, mOldKey, mData, true, 0);
+        listener.onMediaDataLoaded(mKey, mOldKey, mData, /* immediately= */true,
+                /* receivedSmartspaceCardLatency= */0, /* isSsReactived= */ false);
         verify(mDreamOverlayStateController).addComplication(eq(mComplication));
 
         listener.onMediaDataRemoved(mKey);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerTest.kt
index 88c4514..27c039d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerTest.kt
@@ -24,7 +24,7 @@
 import android.media.AudioDeviceInfo
 import android.media.AudioManager
 import android.media.AudioManager.MuteAwaitConnectionCallback.EVENT_CONNECTION
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
 import com.android.settingslib.media.DeviceIconUtil
 import com.android.settingslib.media.LocalMediaManager
 import com.android.systemui.R
@@ -95,7 +95,7 @@
 
         muteAwaitConnectionManager.startListening()
 
-        verify(localMediaManager, never()).dispatchAboutToConnectDeviceChanged(any(), any())
+        verify(localMediaManager, never()).dispatchAboutToConnectDeviceAdded(any(), any(), any())
     }
 
     @Test
@@ -104,7 +104,9 @@
 
         muteAwaitConnectionManager.startListening()
 
-        verify(localMediaManager).dispatchAboutToConnectDeviceChanged(eq(DEVICE_NAME), eq(icon))
+        verify(localMediaManager).dispatchAboutToConnectDeviceAdded(
+            eq(DEVICE_ADDRESS), eq(DEVICE_NAME), eq(icon)
+        )
     }
 
     @Test
@@ -114,7 +116,7 @@
 
         muteAwaitListener.onMutedUntilConnection(DEVICE, intArrayOf(USAGE_UNKNOWN))
 
-        verify(localMediaManager, never()).dispatchAboutToConnectDeviceChanged(any(), any())
+        verify(localMediaManager, never()).dispatchAboutToConnectDeviceAdded(any(), any(), any())
     }
 
     @Test
@@ -125,7 +127,9 @@
 
         muteAwaitListener.onMutedUntilConnection(DEVICE, intArrayOf(USAGE_MEDIA))
 
-        verify(localMediaManager).dispatchAboutToConnectDeviceChanged(eq(DEVICE_NAME), eq(icon))
+        verify(localMediaManager).dispatchAboutToConnectDeviceAdded(
+            eq(DEVICE_ADDRESS), eq(DEVICE_NAME), eq(icon)
+        )
     }
 
     @Test
@@ -135,7 +139,7 @@
 
         muteAwaitListener.onUnmutedEvent(EVENT_CONNECTION, DEVICE, intArrayOf(USAGE_MEDIA))
 
-        verify(localMediaManager, never()).dispatchAboutToConnectDeviceChanged(any(), any())
+        verify(localMediaManager, never()).dispatchAboutToConnectDeviceAdded(any(), any(), any())
     }
 
     @Test
@@ -155,7 +159,7 @@
         )
         muteAwaitListener.onUnmutedEvent(EVENT_CONNECTION, otherDevice, intArrayOf(USAGE_MEDIA))
 
-        verify(localMediaManager, never()).dispatchAboutToConnectDeviceChanged(any(), any())
+        verify(localMediaManager, never()).dispatchAboutToConnectDeviceAdded(any(), any(), any())
     }
 
     @Test
@@ -167,7 +171,7 @@
 
         muteAwaitListener.onUnmutedEvent(EVENT_CONNECTION, DEVICE, intArrayOf(USAGE_UNKNOWN))
 
-        verify(localMediaManager, never()).dispatchAboutToConnectDeviceChanged(any(), any())
+        verify(localMediaManager, never()).dispatchAboutToConnectDeviceAdded(any(), any(), any())
     }
 
     @Test
@@ -179,7 +183,7 @@
 
         muteAwaitListener.onUnmutedEvent(EVENT_CONNECTION, DEVICE, intArrayOf(USAGE_MEDIA))
 
-        verify(localMediaManager).dispatchAboutToConnectDeviceChanged(eq(null), eq(null))
+        verify(localMediaManager).dispatchAboutToConnectDeviceRemoved()
     }
 
     private fun getMuteAwaitListener(): AudioManager.MuteAwaitConnectionCallback {
@@ -191,11 +195,12 @@
     }
 }
 
+private const val DEVICE_ADDRESS = "DeviceAddress"
 private const val DEVICE_NAME = "DeviceName"
 private val DEVICE = AudioDeviceAttributes(
         AudioDeviceAttributes.ROLE_OUTPUT,
         AudioDeviceInfo.TYPE_USB_HEADSET,
-        "address",
+        DEVICE_ADDRESS,
         DEVICE_NAME,
         listOf(),
         listOf(),
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 ef53154..9a01464 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
@@ -277,6 +277,17 @@
     }
 
     @Test
+    fun commandQueueCallback_invalidStateParam_noChipShown() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            100,
+            routeInfo,
+            null
+        )
+
+        verify(windowManager, never()).addView(any(), any())
+    }
+
+    @Test
     fun receivesNewStateFromCommandQueue_isLogged() {
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
index a156820..1ffa9dd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
@@ -25,29 +25,48 @@
 import static org.mockito.Mockito.any;
 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;
+import static org.mockito.Mockito.when;
 
 import android.app.Notification;
 import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.os.BatteryManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.UserHandle;
 import android.test.suitebuilder.annotation.SmallTest;
-
-import androidx.test.runner.AndroidJUnit4;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.View;
 
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
+import com.android.settingslib.fuelgauge.BatterySaverUtils;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.broadcast.BroadcastSender;
 import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.util.NotificationChannels;
 
 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.lang.ref.WeakReference;
 
 @SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class PowerNotificationWarningsTest extends SysuiTestCase {
 
     public static final String FORMATTED_45M = "0h 45m";
@@ -55,14 +74,34 @@
     private final NotificationManager mMockNotificationManager = mock(NotificationManager.class);
     private PowerNotificationWarnings mPowerNotificationWarnings;
 
+    @Mock
+    private BatteryController mBatteryController;
+    @Mock
+    private DialogLaunchAnimator mDialogLaunchAnimator;
+    @Mock
+    private View mView;
+
+    private BroadcastReceiver mReceiver;
+
     @Before
     public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        Context wrapper = new ContextWrapper(mContext) {
+            @Override
+            public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
+                    IntentFilter filter, String broadcastPermission, Handler scheduler, int flags) {
+                mReceiver = receiver;
+                return null;
+            }
+        };
+
         // Test Instance.
         mContext.addMockSystemService(NotificationManager.class, mMockNotificationManager);
         ActivityStarter starter = mDependency.injectMockDependency(ActivityStarter.class);
         BroadcastSender broadcastSender = mDependency.injectMockDependency(BroadcastSender.class);
-        mPowerNotificationWarnings = new PowerNotificationWarnings(mContext, starter,
-                broadcastSender);
+        mPowerNotificationWarnings = new PowerNotificationWarnings(wrapper, starter,
+                broadcastSender, () -> mBatteryController, mDialogLaunchAnimator);
         BatteryStateSnapshot snapshot = new BatteryStateSnapshot(100, false, false, 1,
                 BatteryManager.BATTERY_HEALTH_GOOD, 5, 15);
         mPowerNotificationWarnings.updateSnapshot(snapshot);
@@ -168,4 +207,52 @@
 
         mPowerNotificationWarnings.mUsbHighTempDialog.dismiss();
     }
+
+    @Test
+    public void testDialogStartedFromLauncher_viewVisible() {
+        when(mBatteryController.getLastPowerSaverStartView())
+                .thenReturn(new WeakReference<>(mView));
+        when(mView.isAggregatedVisible()).thenReturn(true);
+
+        Intent intent = new Intent(BatterySaverUtils.ACTION_SHOW_START_SAVER_CONFIRMATION);
+        intent.putExtras(new Bundle());
+
+        mReceiver.onReceive(mContext, intent);
+
+        verify(mDialogLaunchAnimator).showFromView(any(), eq(mView));
+
+        mPowerNotificationWarnings.getSaverConfirmationDialog().dismiss();
+    }
+
+    @Test
+    public void testDialogStartedNotFromLauncher_viewNotVisible() {
+        when(mBatteryController.getLastPowerSaverStartView())
+                .thenReturn(new WeakReference<>(mView));
+        when(mView.isAggregatedVisible()).thenReturn(false);
+
+        Intent intent = new Intent(BatterySaverUtils.ACTION_SHOW_START_SAVER_CONFIRMATION);
+        intent.putExtras(new Bundle());
+
+        mReceiver.onReceive(mContext, intent);
+
+        verify(mDialogLaunchAnimator, never()).showFromView(any(), any());
+
+        assertThat(mPowerNotificationWarnings.getSaverConfirmationDialog().isShowing()).isTrue();
+        mPowerNotificationWarnings.getSaverConfirmationDialog().dismiss();
+    }
+
+    @Test
+    public void testDialogShownNotFromLauncher() {
+        when(mBatteryController.getLastPowerSaverStartView()).thenReturn(null);
+
+        Intent intent = new Intent(BatterySaverUtils.ACTION_SHOW_START_SAVER_CONFIRMATION);
+        intent.putExtras(new Bundle());
+
+        mReceiver.onReceive(mContext, intent);
+
+        verify(mDialogLaunchAnimator, never()).showFromView(any(), any());
+
+        assertThat(mPowerNotificationWarnings.getSaverConfirmationDialog().isShowing()).isTrue();
+        mPowerNotificationWarnings.getSaverConfirmationDialog().dismiss();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java
index 3e92e90..43fb1bd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java
@@ -145,7 +145,7 @@
     public void testBroadcastReceiverRegistered() {
         verify(mBroadcastDispatcher).registerReceiver(
                 any(), mIntentFilterArgumentCaptor.capture(), any(), eq(UserHandle.of(USER)),
-                anyInt());
+                anyInt(), any());
 
         assertTrue(
                 mIntentFilterArgumentCaptor.getValue().hasAction(Intent.ACTION_SETTING_RESTORED));
@@ -158,13 +158,14 @@
         InOrder inOrder = Mockito.inOrder(mBroadcastDispatcher);
         inOrder.verify(mBroadcastDispatcher).unregisterReceiver(any());
         inOrder.verify(mBroadcastDispatcher)
-                .registerReceiver(any(), any(), any(), eq(UserHandle.of(USER + 1)), anyInt());
+                .registerReceiver(any(), any(), any(), eq(UserHandle.of(USER + 1)), anyInt(),
+                        any());
     }
 
     @Test
     public void testSettingRestoredWithTilesNotRemovedInSource_noAutoAddedInTarget() {
         verify(mBroadcastDispatcher).registerReceiver(
-                mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any(), anyInt());
+                mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any(), anyInt(), any());
 
         // These tiles were present in the original device
         String restoredTiles = "saver,work,internet,cast";
@@ -188,7 +189,7 @@
     public void testSettingRestoredWithTilesRemovedInSource_noAutoAddedInTarget() {
         verify(mBroadcastDispatcher)
                 .registerReceiver(mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any(),
-                        anyInt());
+                        anyInt(), any());
 
         // These tiles were present in the original device
         String restoredTiles = "saver,internet,cast";
@@ -212,7 +213,7 @@
     public void testSettingRestoredWithTilesRemovedInSource_sameAutoAddedinTarget() {
         verify(mBroadcastDispatcher)
                 .registerReceiver(mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any(),
-                        anyInt());
+                        anyInt(), any());
 
         // These tiles were present in the original device
         String restoredTiles = "saver,internet,cast";
@@ -237,7 +238,7 @@
     public void testSettingRestoredWithTilesRemovedInSource_othersAutoAddedinTarget() {
         verify(mBroadcastDispatcher)
                 .registerReceiver(mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any(),
-                        anyInt());
+                        anyInt(), any());
 
         // These tiles were present in the original device
         String restoredTiles = "saver,internet,cast";
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
index 7b17c36..35d0024 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
@@ -8,20 +8,20 @@
 import android.testing.ViewUtils
 import android.view.LayoutInflater
 import android.view.View
+import android.view.ViewGroup
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
 import com.android.internal.logging.UiEventLogger
 import com.android.internal.logging.testing.FakeMetricsLogger
 import com.android.systemui.R
 import com.android.systemui.classifier.FalsingManagerFake
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.globalactions.GlobalActionsDialogLite
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.phone.MultiUserSwitchController
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.statusbar.policy.UserInfoController
+import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.settings.FakeSettings
 import com.android.systemui.utils.leaks.LeakCheckedTest
 import com.google.common.truth.Truth.assertThat
@@ -29,10 +29,14 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers.any
 import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.never
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
@@ -65,11 +69,12 @@
     @Mock
     private lateinit var uiEventLogger: UiEventLogger
     @Mock
-    private lateinit var featureFlags: FeatureFlags
-    @Mock
     private lateinit var securityFooterController: QSSecurityFooter
     @Mock
     private lateinit var fgsManagerController: QSFgsManagerFooter
+    @Captor
+    private lateinit var visibilityChangedCaptor:
+        ArgumentCaptor<VisibilityChangedDispatcher.OnVisibilityChangedListener>
 
     private lateinit var controller: FooterActionsController
 
@@ -78,6 +83,8 @@
     private val falsingManager: FalsingManagerFake = FalsingManagerFake()
     private lateinit var testableLooper: TestableLooper
     private lateinit var fakeSettings: FakeSettings
+    private lateinit var securityFooter: View
+    private lateinit var fgsFooter: View
 
     @Before
     fun setUp() {
@@ -87,9 +94,14 @@
 
         whenever(multiUserSwitchControllerFactory.create(any()))
                 .thenReturn(multiUserSwitchController)
-        whenever(featureFlags.isEnabled(Flags.NEW_FOOTER)).thenReturn(false)
         whenever(globalActionsDialogProvider.get()).thenReturn(globalActionsDialog)
 
+        securityFooter = View(mContext)
+        fgsFooter = View(mContext)
+
+        whenever(securityFooterController.view).thenReturn(securityFooter)
+        whenever(fgsManagerController.view).thenReturn(fgsFooter)
+
         view = inflateView()
 
         controller = constructFooterActionsController(view)
@@ -107,6 +119,13 @@
     }
 
     @Test
+    fun testInitializesControllers() {
+        verify(multiUserSwitchController).init()
+        verify(fgsManagerController).init()
+        verify(securityFooterController).init()
+    }
+
+    @Test
     fun testLogPowerMenuClick() {
         controller.visible = true
         falsingManager.setFalseTap(false)
@@ -182,6 +201,10 @@
     @Test
     fun testCleanUpGAD() {
         reset(globalActionsDialogProvider)
+        // We are creating a new controller, so detach the views from it
+        (securityFooter.parent as ViewGroup).removeView(securityFooter)
+        (fgsFooter.parent as ViewGroup).removeView(fgsFooter)
+
         whenever(globalActionsDialogProvider.get()).thenReturn(globalActionsDialog)
         val view = inflateView()
         controller = constructFooterActionsController(view)
@@ -198,6 +221,80 @@
         verify(globalActionsDialog).destroy()
     }
 
+    @Test
+    fun testSeparatorVisibility_noneVisible_gone() {
+        verify(securityFooterController)
+            .setOnVisibilityChangedListener(capture(visibilityChangedCaptor))
+        val listener = visibilityChangedCaptor.value
+        val separator = controller.securityFootersSeparator
+
+        setVisibilities(securityFooterVisible = false, fgsFooterVisible = false, listener)
+        assertThat(separator.visibility).isEqualTo(View.GONE)
+    }
+
+    @Test
+    fun testSeparatorVisibility_onlySecurityFooterVisible_gone() {
+        verify(securityFooterController)
+            .setOnVisibilityChangedListener(capture(visibilityChangedCaptor))
+        val listener = visibilityChangedCaptor.value
+        val separator = controller.securityFootersSeparator
+
+        setVisibilities(securityFooterVisible = true, fgsFooterVisible = false, listener)
+        assertThat(separator.visibility).isEqualTo(View.GONE)
+    }
+
+    @Test
+    fun testSeparatorVisibility_onlyFgsFooterVisible_gone() {
+        verify(securityFooterController)
+            .setOnVisibilityChangedListener(capture(visibilityChangedCaptor))
+        val listener = visibilityChangedCaptor.value
+        val separator = controller.securityFootersSeparator
+
+        setVisibilities(securityFooterVisible = false, fgsFooterVisible = true, listener)
+        assertThat(separator.visibility).isEqualTo(View.GONE)
+    }
+
+    @Test
+    fun testSeparatorVisibility_bothVisible_visible() {
+        verify(securityFooterController)
+            .setOnVisibilityChangedListener(capture(visibilityChangedCaptor))
+        val listener = visibilityChangedCaptor.value
+        val separator = controller.securityFootersSeparator
+
+        setVisibilities(securityFooterVisible = true, fgsFooterVisible = true, listener)
+        assertThat(separator.visibility).isEqualTo(View.VISIBLE)
+    }
+
+    @Test
+    fun testFgsFooterCollapsed() {
+        verify(securityFooterController)
+            .setOnVisibilityChangedListener(capture(visibilityChangedCaptor))
+        val listener = visibilityChangedCaptor.value
+
+        val booleanCaptor = ArgumentCaptor.forClass(Boolean::class.java)
+
+        clearInvocations(fgsManagerController)
+        setVisibilities(securityFooterVisible = false, fgsFooterVisible = true, listener)
+        verify(fgsManagerController, atLeastOnce()).setCollapsed(capture(booleanCaptor))
+        assertThat(booleanCaptor.allValues.last()).isFalse()
+
+        clearInvocations(fgsManagerController)
+        setVisibilities(securityFooterVisible = true, fgsFooterVisible = true, listener)
+        verify(fgsManagerController, atLeastOnce()).setCollapsed(capture(booleanCaptor))
+        assertThat(booleanCaptor.allValues.last()).isTrue()
+    }
+
+    private fun setVisibilities(
+        securityFooterVisible: Boolean,
+        fgsFooterVisible: Boolean,
+        listener: VisibilityChangedDispatcher.OnVisibilityChangedListener
+    ) {
+        securityFooter.visibility = if (securityFooterVisible) View.VISIBLE else View.GONE
+        listener.onVisibilityChanged(securityFooter.visibility)
+        fgsFooter.visibility = if (fgsFooterVisible) View.VISIBLE else View.GONE
+        listener.onVisibilityChanged(fgsFooter.visibility)
+    }
+
     private fun inflateView(): FooterActionsView {
         return LayoutInflater.from(context)
                 .inflate(R.layout.footer_actions, null) as FooterActionsView
@@ -208,6 +305,6 @@
                 activityStarter, userManager, userTracker, userInfoController,
                 deviceProvisionedController, securityFooterController, fgsManagerController,
                 falsingManager, metricsLogger, globalActionsDialogProvider, uiEventLogger,
-                showPMLiteButton = true, fakeSettings, Handler(testableLooper.looper), featureFlags)
+                showPMLiteButton = true, fakeSettings, Handler(testableLooper.looper))
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt
index a67483b..4c72406 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt
@@ -163,7 +163,7 @@
         val receiverCaptor = argumentCaptor<BroadcastReceiver>()
         whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(true)
         verify(broadcastDispatcher).registerReceiver(capture(receiverCaptor),
-                any(), any(), nullable(), anyInt())
+                any(), any(), nullable(), anyInt(), nullable())
         receiverCaptor.value.onReceive(
                 context,
                 Intent(SafetyCenterManager.ACTION_SAFETY_CENTER_ENABLED_CHANGED)
@@ -193,8 +193,10 @@
         val broadcastReceiverCaptor = argumentCaptor<BroadcastReceiver>()
         val intentFilterCaptor = argumentCaptor<IntentFilter>()
         // Broadcast receiver is registered on init and when privacy chip is attached
-        verify(broadcastDispatcher, times(2)).registerReceiver(capture(broadcastReceiverCaptor),
-                capture(intentFilterCaptor), any(), nullable(), anyInt())
+        verify(broadcastDispatcher, times(2)).registerReceiver(
+            capture(broadcastReceiverCaptor),
+            capture(intentFilterCaptor), any(), nullable(), anyInt(), nullable()
+        )
     }
 
     private fun setPrivacyController(micCamera: Boolean, location: Boolean) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSContainerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSContainerImplTest.kt
index bf82e90..489c8c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSContainerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSContainerImplTest.kt
@@ -59,22 +59,14 @@
     fun testContainerBottomPadding() {
         qsContainer.updateResources(
             qsPanelController,
-            quickStatusBarHeaderController,
-            /* newFooter */ false
-        )
-        verify(qsPanelContainer).setPaddingRelative(anyInt(), anyInt(), anyInt(), eq(0))
-
-        qsContainer.updateResources(
-            qsPanelController,
-            quickStatusBarHeaderController,
-            /* newFooter */ true
+            quickStatusBarHeaderController
         )
         verify(qsPanelContainer)
             .setPaddingRelative(
                 anyInt(),
                 anyInt(),
                 anyInt(),
-                eq(mContext.resources.getDimensionPixelSize(R.dimen.new_footer_height))
+                eq(mContext.resources.getDimensionPixelSize(R.dimen.footer_actions_height))
             )
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index 63f8641..829445e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -23,6 +23,7 @@
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.Fragment;
@@ -304,6 +305,16 @@
         assertThat(mQsFragmentView.getY()).isEqualTo(-qsAbsoluteBottom);
     }
 
+    @Test
+    public void setCollapseExpandAction_passedToControllers() {
+        Runnable action = () -> {};
+        QSFragment fragment = resumeAndGetFragment();
+        fragment.setCollapseExpandAction(action);
+
+        verify(mQSPanelController).setCollapseExpandAction(action);
+        verify(mQuickQSPanelController).setCollapseExpandAction(action);
+    }
+
     @Override
     protected Fragment instantiate(Context context, String className, Bundle arguments) {
         MockitoAnnotations.initMocks(this);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
index 58a070d..689de50 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
@@ -6,7 +6,6 @@
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.media.MediaHost
 import com.android.systemui.media.MediaHostState
 import com.android.systemui.plugins.FalsingManager
@@ -35,8 +34,6 @@
 class QSPanelControllerTest : SysuiTestCase() {
 
     @Mock private lateinit var qsPanel: QSPanel
-    @Mock private lateinit var qsFgsManagerFooter: QSFgsManagerFooter
-    @Mock private lateinit var qsSecurityFooter: QSSecurityFooter
     @Mock private lateinit var tunerService: TunerService
     @Mock private lateinit var qsTileHost: QSTileHost
     @Mock private lateinit var qsCustomizerController: QSCustomizerController
@@ -50,7 +47,6 @@
     @Mock private lateinit var brightnessSlider: BrightnessSliderController
     @Mock private lateinit var brightnessSliderFactory: BrightnessSliderController.Factory
     @Mock private lateinit var falsingManager: FalsingManager
-    @Mock private lateinit var featureFlags: FeatureFlags
     @Mock private lateinit var mediaHost: MediaHost
     @Mock private lateinit var tile: QSTile
     @Mock private lateinit var otherTile: QSTile
@@ -69,8 +65,6 @@
 
         controller = QSPanelController(
             qsPanel,
-            qsFgsManagerFooter,
-            qsSecurityFooter,
             tunerService,
             qsTileHost,
             qsCustomizerController,
@@ -84,7 +78,6 @@
             brightnessControllerFactory,
             brightnessSliderFactory,
             falsingManager,
-            featureFlags,
             statusBarKeyguardViewManager
         )
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
index 04bbd60..e237a5c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
@@ -13,32 +13,24 @@
  */
 package com.android.systemui.qs
 
-import android.content.res.Configuration
-import android.content.res.Configuration.ORIENTATION_LANDSCAPE
-import android.content.res.Configuration.ORIENTATION_PORTRAIT
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.testing.TestableLooper.RunWithLooper
 import android.view.View
 import android.view.ViewGroup
+import android.view.accessibility.AccessibilityNodeInfo
 import android.widget.FrameLayout
 import android.widget.LinearLayout
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.plugins.qs.QSTileView
-import com.android.systemui.qs.QSPanelControllerBase.TileRecord
-import com.android.systemui.qs.logging.QSLogger
-import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.any
-import org.mockito.ArgumentMatchers.anyBoolean
-import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
-import org.mockito.Mockito.`when` as whenever
 
 @RunWith(AndroidTestingRunner::class)
 @RunWithLooper
@@ -47,22 +39,8 @@
     private lateinit var mTestableLooper: TestableLooper
     private lateinit var mQsPanel: QSPanel
 
-    @Mock
-    private lateinit var mHost: QSTileHost
-
-    @Mock
-    private lateinit var dndTile: QSTileImpl<*>
-
-    @Mock
-    private lateinit var mDndTileRecord: TileRecord
-
-    @Mock
-    private lateinit var mQSLogger: QSLogger
     private lateinit var mParentView: ViewGroup
 
-    @Mock
-    private lateinit var mQSTileView: QSTileView
-
     private lateinit var mFooter: View
 
     @Before
@@ -71,8 +49,6 @@
         MockitoAnnotations.initMocks(this)
         mTestableLooper = TestableLooper.get(this)
 
-        mDndTileRecord.tile = dndTile
-        mDndTileRecord.tileView = mQSTileView
         mTestableLooper.runWithLooper {
             mQsPanel = QSPanel(mContext, null)
             mQsPanel.initialize()
@@ -80,63 +56,28 @@
             mFooter = LinearLayout(mContext).apply { id = R.id.qs_footer }
             mQsPanel.addView(mFooter)
             mQsPanel.onFinishInflate()
-            mQsPanel.setSecurityFooter(View(mContext), false)
-            mQsPanel.setHeaderContainer(LinearLayout(mContext))
             // Provides a parent with non-zero size for QSPanel
             mParentView = FrameLayout(mContext).apply {
                 addView(mQsPanel)
             }
-
-            whenever(dndTile.tileSpec).thenReturn("dnd")
-            whenever(mHost.tiles).thenReturn(emptyList())
-            whenever(mHost.createTileView(any(), any(), anyBoolean())).thenReturn(mQSTileView)
-            mQsPanel.addTile(mDndTileRecord)
         }
     }
 
     @Test
-    fun testSecurityFooter_appearsOnBottomOnSplitShade() {
-        mQsPanel.onConfigurationChanged(getNewOrientationConfig(ORIENTATION_LANDSCAPE))
-        mQsPanel.switchSecurityFooter(true)
+    fun testHasCollapseAccessibilityAction() {
+        val info = AccessibilityNodeInfo(mQsPanel)
+        mQsPanel.onInitializeAccessibilityNodeInfo(info)
 
-        mTestableLooper.runWithLooper {
-            mQsPanel.isExpanded = true
-        }
-
-        // After mFooter
-        assertThat(mQsPanel.indexOfChild(mQsPanel.mSecurityFooter)).isEqualTo(
-                mQsPanel.indexOfChild(mFooter) + 1
-        )
+        assertThat(info.actions and AccessibilityNodeInfo.ACTION_COLLAPSE).isNotEqualTo(0)
+        assertThat(info.actions and AccessibilityNodeInfo.ACTION_EXPAND).isEqualTo(0)
     }
 
     @Test
-    fun testSecurityFooter_appearsOnBottomIfPortrait() {
-        mQsPanel.onConfigurationChanged(getNewOrientationConfig(ORIENTATION_PORTRAIT))
-        mQsPanel.switchSecurityFooter(false)
+    fun testCollapseActionCallsRunnable() {
+        val mockRunnable = mock(Runnable::class.java)
+        mQsPanel.setCollapseExpandAction(mockRunnable)
 
-        mTestableLooper.runWithLooper {
-            mQsPanel.isExpanded = true
-        }
-
-        // After mFooter
-        assertThat(mQsPanel.indexOfChild(mQsPanel.mSecurityFooter)).isEqualTo(
-                mQsPanel.indexOfChild(mFooter) + 1
-        )
+        mQsPanel.performAccessibilityAction(AccessibilityNodeInfo.ACTION_COLLAPSE, null)
+        verify(mockRunnable).run()
     }
-
-    @Test
-    fun testSecurityFooter_appearsOnTopIfSmallScreenAndLandscape() {
-        mQsPanel.onConfigurationChanged(getNewOrientationConfig(ORIENTATION_LANDSCAPE))
-        mQsPanel.switchSecurityFooter(false)
-
-        mTestableLooper.runWithLooper {
-            mQsPanel.isExpanded = true
-        }
-
-        // -1 means that it is part of the mHeaderContainer
-        assertThat(mQsPanel.indexOfChild(mQsPanel.mSecurityFooter)).isEqualTo(-1)
-    }
-
-    private fun getNewOrientationConfig(@Configuration.Orientation newOrientation: Int) =
-            context.resources.configuration.apply { orientation = newOrientation }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
index 1501346..a3c353b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
@@ -30,8 +30,11 @@
 import static org.mockito.Mockito.when;
 
 import android.app.AlertDialog;
+import android.app.admin.DevicePolicyManager;
+import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.DialogInterface;
+import android.content.Intent;
 import android.content.pm.UserInfo;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.VectorDrawable;
@@ -44,6 +47,7 @@
 import android.testing.TestableImageView;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
+import android.testing.ViewUtils;
 import android.text.SpannableStringBuilder;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -53,11 +57,12 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.DialogLaunchAnimator;
-import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.SecurityController;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -104,7 +109,7 @@
     @Mock
     private DialogLaunchAnimator mDialogLaunchAnimator;
     @Mock
-    private FeatureFlags mFeatureFlags;
+    private BroadcastDispatcher mBroadcastDispatcher;
 
     private TestableLooper mTestableLooper;
 
@@ -119,7 +124,7 @@
                 .build().inflate(R.layout.quick_settings_security_footer, null, false);
         mFooter = new QSSecurityFooter(mRootView, mUserTracker, new Handler(looper),
                 mActivityStarter, mSecurityController, mDialogLaunchAnimator, looper,
-                mFeatureFlags);
+                mBroadcastDispatcher);
         mFooterText = mRootView.findViewById(R.id.footer_text);
         mPrimaryFooterIcon = mRootView.findViewById(R.id.primary_footer_icon);
 
@@ -127,6 +132,14 @@
                 .thenReturn(DEVICE_OWNER_COMPONENT);
         when(mSecurityController.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
                 .thenReturn(DEVICE_OWNER_TYPE_DEFAULT);
+        ViewUtils.attachView(mRootView);
+
+        mFooter.init();
+    }
+
+    @After
+    public void tearDown() {
+        ViewUtils.detachView(mRootView);
     }
 
     @Test
@@ -769,8 +782,7 @@
     @Test
     public void testVisibilityListener() {
         final AtomicInteger lastVisibility = new AtomicInteger(-1);
-        VisibilityChangedDispatcher.OnVisibilityChangedListener listener =
-                (VisibilityChangedDispatcher.OnVisibilityChangedListener) lastVisibility::set;
+        VisibilityChangedDispatcher.OnVisibilityChangedListener listener = lastVisibility::set;
 
         mFooter.setOnVisibilityChangedListener(listener);
 
@@ -785,6 +797,28 @@
         assertEquals(View.GONE, lastVisibility.get());
     }
 
+    @Test
+    public void testBroadcastShowsDialog() {
+        // Setup dialog content
+        when(mSecurityController.isDeviceManaged()).thenReturn(true);
+        when(mSecurityController.getDeviceOwnerOrganizationName())
+                .thenReturn(MANAGING_ORGANIZATION);
+        when(mSecurityController.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
+                .thenReturn(DEVICE_OWNER_TYPE_FINANCED);
+
+        ArgumentCaptor<BroadcastReceiver> captor = ArgumentCaptor.forClass(BroadcastReceiver.class);
+        verify(mBroadcastDispatcher).registerReceiverWithHandler(captor.capture(), any(), any(),
+                any());
+
+        // Pretend view is not visible temporarily
+        mRootView.onVisibilityAggregated(false);
+        captor.getValue().onReceive(mContext,
+                new Intent(DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG));
+        mTestableLooper.processAllMessages();
+
+        assertTrue(mFooter.getDialog().isShowing());
+        mFooter.getDialog().dismiss();
+    }
 
     private CharSequence addLink(CharSequence description) {
         final SpannableStringBuilder message = new SpannableStringBuilder();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelTest.kt
new file mode 100644
index 0000000..a6a584d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelTest.kt
@@ -0,0 +1,62 @@
+package com.android.systemui.qs
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.ViewGroup
+import android.view.accessibility.AccessibilityNodeInfo
+import android.widget.FrameLayout
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+@SmallTest
+class QuickQSPanelTest : SysuiTestCase() {
+
+    private lateinit var testableLooper: TestableLooper
+    private lateinit var quickQSPanel: QuickQSPanel
+
+    private lateinit var parentView: ViewGroup
+
+    @Before
+    @Throws(Exception::class)
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        testableLooper = TestableLooper.get(this)
+
+        testableLooper.runWithLooper {
+            quickQSPanel = QuickQSPanel(mContext, null)
+            quickQSPanel.initialize()
+
+            quickQSPanel.onFinishInflate()
+            // Provides a parent with non-zero size for QSPanel
+            parentView = FrameLayout(mContext).apply {
+                addView(quickQSPanel)
+            }
+        }
+    }
+
+    @Test
+    fun testHasExpandAccessibilityAction() {
+        val info = AccessibilityNodeInfo(quickQSPanel)
+        quickQSPanel.onInitializeAccessibilityNodeInfo(info)
+
+        Truth.assertThat(info.actions and AccessibilityNodeInfo.ACTION_EXPAND).isNotEqualTo(0)
+        Truth.assertThat(info.actions and AccessibilityNodeInfo.ACTION_COLLAPSE).isEqualTo(0)
+    }
+
+    @Test
+    fun testExpandActionCallsRunnable() {
+        val mockRunnable = Mockito.mock(Runnable::class.java)
+        quickQSPanel.setCollapseExpandAction(mockRunnable)
+
+        quickQSPanel.performAccessibilityAction(AccessibilityNodeInfo.ACTION_EXPAND, null)
+        Mockito.verify(mockRunnable).run()
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
index bd4bfff..5abc0e1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
@@ -28,6 +28,7 @@
 import static org.mockito.Mockito.verify;
 
 import android.test.suitebuilder.annotation.SmallTest;
+import android.view.accessibility.AccessibilityNodeInfo;
 
 import androidx.test.runner.AndroidJUnit4;
 
@@ -170,4 +171,36 @@
         mTileLayout.measure(mLayoutSizeForOneTile, mLayoutSizeForOneTile);
         assertEquals(0, mTileLayout.getMeasuredHeight());
     }
+
+    @Test
+    public void testCollectionInfo() {
+        QSPanelControllerBase.TileRecord tileRecord1 = createTileRecord();
+        QSPanelControllerBase.TileRecord tileRecord2 = createTileRecord();
+        AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(mTileLayout);
+        mTileLayout.addTile(tileRecord1);
+
+        mTileLayout.onInitializeAccessibilityNodeInfo(info);
+        AccessibilityNodeInfo.CollectionInfo collectionInfo = info.getCollectionInfo();
+        assertEquals(1, collectionInfo.getRowCount());
+        assertEquals(1, collectionInfo.getColumnCount()); // always use one column
+
+        mTileLayout.addTile(tileRecord2);
+        mTileLayout.onInitializeAccessibilityNodeInfo(info);
+        collectionInfo = info.getCollectionInfo();
+        assertEquals(2, collectionInfo.getRowCount());
+        assertEquals(1, collectionInfo.getColumnCount()); // always use one column
+    }
+
+    @Test
+    public void testSetPositionOnTiles() {
+        QSPanelControllerBase.TileRecord tileRecord1 = createTileRecord();
+        QSPanelControllerBase.TileRecord tileRecord2 = createTileRecord();
+        mTileLayout.addTile(tileRecord1);
+        mTileLayout.addTile(tileRecord2);
+        mTileLayout.measure(mLayoutSizeForOneTile * 2, mLayoutSizeForOneTile * 2);
+        mTileLayout.layout(0, 0, mLayoutSizeForOneTile * 2, mLayoutSizeForOneTile * 2);
+
+        verify(tileRecord1.tileView).setPosition(0);
+        verify(tileRecord2.tileView).setPosition(1);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
index a2b5013..9fdc2fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
@@ -23,6 +23,7 @@
 import android.testing.TestableLooper
 import android.text.TextUtils
 import android.view.View
+import android.view.accessibility.AccessibilityNodeInfo
 import android.widget.TextView
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
@@ -257,6 +258,28 @@
         assertThat((tileView.secondaryLabel as TextView).text).isEqualTo(onString)
     }
 
+    @Test
+    fun testCollectionItemInfoHasPosition() {
+        val position = 5
+        tileView.setPosition(position)
+
+        val info = AccessibilityNodeInfo(tileView)
+        tileView.onInitializeAccessibilityNodeInfo(info)
+
+        assertThat(info.collectionItemInfo.rowIndex).isEqualTo(position)
+        assertThat(info.collectionItemInfo.rowSpan).isEqualTo(1)
+        assertThat(info.collectionItemInfo.columnIndex).isEqualTo(0)
+        assertThat(info.collectionItemInfo.columnSpan).isEqualTo(1)
+    }
+
+    @Test
+    fun testCollectionItemInfoNoPosition() {
+        val info = AccessibilityNodeInfo(tileView)
+        tileView.onInitializeAccessibilityNodeInfo(info)
+
+        assertThat(info.collectionItemInfo).isNull()
+    }
+
     class FakeTileView(
         context: Context,
         icon: QSIconView,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt
index 1bf8351..3d9205e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt
@@ -21,6 +21,7 @@
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.testing.TestableLooper.RunWithLooper
+import android.view.View
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
 import com.android.systemui.SysuiTestCase
@@ -38,6 +39,9 @@
 import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.Mockito.`when`
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
 @RunWith(AndroidTestingRunner::class)
@@ -63,6 +67,8 @@
     private lateinit var qsLogger: QSLogger
     @Mock
     private lateinit var batteryController: BatteryController
+    @Mock
+    private lateinit var view: View
     private lateinit var secureSettings: SecureSettings
     private lateinit var testableLooper: TestableLooper
     private lateinit var tile: BatterySaverTile
@@ -105,4 +111,26 @@
 
         assertEquals(USER + 1, tile.mSetting.currentUser)
     }
+
+    @Test
+    fun testClickingPowerSavePassesView() {
+        tile.onPowerSaveChanged(true)
+        tile.handleClick(view)
+
+        tile.onPowerSaveChanged(false)
+        tile.handleClick(view)
+
+        verify(batteryController).setPowerSaveMode(true, view)
+        verify(batteryController).setPowerSaveMode(false, view)
+    }
+
+    @Test
+    fun testStopListeningClearsViewInController() {
+        clearInvocations(batteryController)
+        tile.handleSetListening(true)
+        verify(batteryController, never()).clearLastPowerSaverStartView()
+
+        tile.handleSetListening(false)
+        verify(batteryController).clearLastPowerSaverStartView()
+    }
 }
\ No newline at end of file
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 11f76a3..fc4d9c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -475,9 +475,10 @@
 
     @Test
     public void testHideAuthenticationDialog() {
-        mCommandQueue.hideAuthenticationDialog();
+        final long id = 4;
+        mCommandQueue.hideAuthenticationDialog(id);
         waitForIdleSync();
-        verify(mCallbacks).hideAuthenticationDialog();
+        verify(mCallbacks).hideAuthenticationDialog(eq(id));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index e2d6ae0..562c970 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -93,24 +93,34 @@
                 .addOverride(R.bool.config_use_split_notification_shade, false)
         context.getOrCreateTestableResources()
             .addOverride(R.dimen.lockscreen_shade_depth_controller_transition_distance, 100)
-        transitionController = LockscreenShadeTransitionController(
-            statusBarStateController = statusbarStateController,
-            logger = logger,
-            keyguardBypassController = keyguardBypassController,
-            lockScreenUserManager = lockScreenUserManager,
-            falsingCollector = falsingCollector,
-            ambientState = ambientState,
-            mediaHierarchyManager = mediaHierarchyManager,
-            scrimController = scrimController,
-            depthController = depthController,
-            wakefulnessLifecycle = wakefulnessLifecycle,
-            context = context,
-            configurationController = configurationController,
-            falsingManager = falsingManager,
-            dumpManager = dumpManager,
-            splitShadeOverScrollerFactory = { _, _ -> splitShadeOverScroller },
-            singleShadeOverScrollerFactory = { singleShadeOverScroller }
-        )
+        transitionController =
+            LockscreenShadeTransitionController(
+                statusBarStateController = statusbarStateController,
+                logger = logger,
+                keyguardBypassController = keyguardBypassController,
+                lockScreenUserManager = lockScreenUserManager,
+                falsingCollector = falsingCollector,
+                ambientState = ambientState,
+                mediaHierarchyManager = mediaHierarchyManager,
+                depthController = depthController,
+                wakefulnessLifecycle = wakefulnessLifecycle,
+                context = context,
+                configurationController = configurationController,
+                falsingManager = falsingManager,
+                dumpManager = dumpManager,
+                splitShadeOverScrollerFactory = { _, _ -> splitShadeOverScroller },
+                singleShadeOverScrollerFactory = { singleShadeOverScroller },
+                scrimTransitionController =
+                LockscreenShadeScrimTransitionController(
+                    scrimController, context, configurationController, dumpManager),
+                keyguardTransitionControllerFactory = { notificationPanelController ->
+                    LockscreenShadeKeyguardTransitionController(
+                        mediaHierarchyManager,
+                        notificationPanelController,
+                        context,
+                        configurationController,
+                        dumpManager)
+                })
         whenever(nsslController.view).thenReturn(stackscroller)
         whenever(nsslController.expandHelperCallback).thenReturn(expandHelperCallback)
         transitionController.notificationPanelController = notificationPanelController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index 48f8206..7687d12 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -66,6 +66,7 @@
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.settings.FakeSettings;
 
 import com.google.android.collect.Lists;
 
@@ -109,6 +110,7 @@
     private UserInfo mCurrentUser;
     private UserInfo mSecondaryUser;
     private UserInfo mWorkUser;
+    private FakeSettings mSettings;
     private TestNotificationLockscreenUserManager mLockscreenUserManager;
     private NotificationEntry mCurrentUserNotif;
     private NotificationEntry mSecondaryUserNotif;
@@ -120,6 +122,8 @@
         mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
 
         int currentUserId = ActivityManager.getCurrentUser();
+        mSettings = new FakeSettings();
+        mSettings.setUserId(ActivityManager.getCurrentUser());
         mCurrentUser = new UserInfo(currentUserId, "", 0);
         mSecondaryUser = new UserInfo(currentUserId + 1, "", 0);
         mWorkUser = new UserInfo(currentUserId + 2, "" /* name */, null /* iconPath */, 0,
@@ -157,48 +161,45 @@
 
     @Test
     public void testLockScreenShowNotificationsFalse() {
-        Settings.Secure.putInt(mContext.getContentResolver(),
-                Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0);
+        mSettings.putInt(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0);
         mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
         assertFalse(mLockscreenUserManager.shouldShowLockscreenNotifications());
     }
 
     @Test
     public void testLockScreenShowNotificationsTrue() {
-        Settings.Secure.putInt(mContext.getContentResolver(),
-                Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 1);
+        mSettings.putInt(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 1);
         mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
         assertTrue(mLockscreenUserManager.shouldShowLockscreenNotifications());
     }
 
     @Test
     public void testLockScreenAllowPrivateNotificationsTrue() {
-        Settings.Secure.putInt(mContext.getContentResolver(),
-                Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1);
+        mSettings.putInt(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1);
         mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
         assertTrue(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id));
     }
 
     @Test
     public void testLockScreenAllowPrivateNotificationsFalse() {
-        Settings.Secure.putIntForUser(mContext.getContentResolver(),
-                Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, mCurrentUser.id);
+        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+                mCurrentUser.id);
         mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
         assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id));
     }
 
     @Test
     public void testLockScreenAllowsWorkPrivateNotificationsFalse() {
-        Settings.Secure.putIntForUser(mContext.getContentResolver(),
-                Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, mWorkUser.id);
+        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+                mWorkUser.id);
         mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
         assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mWorkUser.id));
     }
 
     @Test
     public void testLockScreenAllowsWorkPrivateNotificationsTrue() {
-        Settings.Secure.putIntForUser(mContext.getContentResolver(),
-                Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, mWorkUser.id);
+        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+                mWorkUser.id);
         mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
         assertTrue(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mWorkUser.id));
     }
@@ -206,8 +207,8 @@
     @Test
     public void testCurrentUserPrivateNotificationsNotRedacted() {
         // GIVEN current user doesn't allow private notifications to show
-        Settings.Secure.putIntForUser(mContext.getContentResolver(),
-                Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, mCurrentUser.id);
+        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+                mCurrentUser.id);
         mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
 
         // THEN current user's notification is redacted
@@ -217,8 +218,8 @@
     @Test
     public void testCurrentUserPrivateNotificationsRedacted() {
         // GIVEN current user allows private notifications to show
-        Settings.Secure.putIntForUser(mContext.getContentResolver(),
-                Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, mCurrentUser.id);
+        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+                mCurrentUser.id);
         mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
 
         // THEN current user's notification isn't redacted
@@ -228,8 +229,8 @@
     @Test
     public void testWorkPrivateNotificationsRedacted() {
         // GIVEN work profile doesn't private notifications to show
-        Settings.Secure.putIntForUser(mContext.getContentResolver(),
-                Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, mWorkUser.id);
+        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+                mWorkUser.id);
         mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
 
         // THEN work profile notification is redacted
@@ -239,8 +240,8 @@
     @Test
     public void testWorkPrivateNotificationsNotRedacted() {
         // GIVEN work profile allows private notifications to show
-        Settings.Secure.putIntForUser(mContext.getContentResolver(),
-                Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, mWorkUser.id);
+        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+                mWorkUser.id);
         mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
 
         // THEN work profile notification isn't redacted
@@ -250,12 +251,11 @@
     @Test
     public void testWorkPrivateNotificationsNotRedacted_otherUsersRedacted() {
         // GIVEN work profile allows private notifications to show but the other users don't
-        Settings.Secure.putIntForUser(mContext.getContentResolver(),
-                Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, mWorkUser.id);
-        Settings.Secure.putIntForUser(mContext.getContentResolver(),
-                Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, mCurrentUser.id);
-        Settings.Secure.putIntForUser(mContext.getContentResolver(),
-                Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+                mWorkUser.id);
+        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+                mCurrentUser.id);
+        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
                 mSecondaryUser.id);
         mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
 
@@ -270,12 +270,11 @@
     @Test
     public void testWorkProfileRedacted_otherUsersNotRedacted() {
         // GIVEN work profile doesn't allow private notifications to show but the other users do
-        Settings.Secure.putIntForUser(mContext.getContentResolver(),
-                Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, mWorkUser.id);
-        Settings.Secure.putIntForUser(mContext.getContentResolver(),
-                Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, mCurrentUser.id);
-        Settings.Secure.putIntForUser(mContext.getContentResolver(),
-                Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+                mWorkUser.id);
+        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+                mCurrentUser.id);
+        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
                 mSecondaryUser.id);
         mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
 
@@ -291,10 +290,9 @@
     public void testSecondaryUserNotRedacted_currentUserRedacted() {
         // GIVEN secondary profile allows private notifications to show but the current user
         // doesn't allow private notifications to show
-        Settings.Secure.putIntForUser(mContext.getContentResolver(),
-                Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, mCurrentUser.id);
-        Settings.Secure.putIntForUser(mContext.getContentResolver(),
-                Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+                mCurrentUser.id);
+        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
                 mSecondaryUser.id);
         mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
 
@@ -328,10 +326,9 @@
 
     @Test
     public void testShowSilentNotifications_settingSaysShow() {
-        Settings.Secure.putInt(mContext.getContentResolver(),
-                Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 1);
-        Settings.Secure.putInt(mContext.getContentResolver(),
-                Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, 1);
+        mSettings.putInt(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 1);
+        mSettings.putInt(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, 1);
+        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
 
         NotificationEntry entry = new NotificationEntryBuilder()
                 .setImportance(IMPORTANCE_LOW)
@@ -343,10 +340,9 @@
 
     @Test
     public void testShowSilentNotifications_settingSaysHide() {
-        Settings.Secure.putInt(mContext.getContentResolver(),
-                Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 1);
-        Settings.Secure.putInt(mContext.getContentResolver(),
-                Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, 0);
+        mSettings.putInt(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 1);
+        mSettings.putInt(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, 0);
+        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
 
         final Notification notification = mock(Notification.class);
         when(notification.isForegroundService()).thenReturn(true);
@@ -360,10 +356,9 @@
 
     @Test
     public void testShowSilentNotificationsPeopleBucket_settingSaysHide() {
-        Settings.Secure.putInt(mContext.getContentResolver(),
-                Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 1);
-        Settings.Secure.putInt(mContext.getContentResolver(),
-                Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, 0);
+        mSettings.putInt(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 1);
+        mSettings.putInt(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, 0);
+        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
 
         final Notification notification = mock(Notification.class);
         when(notification.isForegroundService()).thenReturn(true);
@@ -377,10 +372,9 @@
 
     @Test
     public void testShowSilentNotificationsMediaBucket_settingSaysHide() {
-        Settings.Secure.putInt(mContext.getContentResolver(),
-                Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 1);
-        Settings.Secure.putInt(mContext.getContentResolver(),
-                Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, 0);
+        mSettings.putInt(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 1);
+        mSettings.putInt(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, 0);
+        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
 
         final Notification notification = mock(Notification.class);
         when(notification.isForegroundService()).thenReturn(true);
@@ -396,8 +390,8 @@
     @Test
     public void testKeyguardNotificationSuppressors() {
         // GIVEN a notification that should be shown on the lockscreen
-        Settings.Secure.putInt(mContext.getContentResolver(),
-                Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 1);
+        mSettings.putInt(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 1);
+        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
         final NotificationEntry entry = new NotificationEntryBuilder()
                 .setImportance(IMPORTANCE_HIGH)
                 .build();
@@ -433,6 +427,7 @@
                     Handler.createAsync(Looper.myLooper()),
                     mDeviceProvisionedController,
                     mKeyguardStateController,
+                    mSettings,
                     mock(DumpManager.class));
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
index 4e07d59..cf99607 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
@@ -192,7 +192,8 @@
                 argThat(intentFilter -> intentFilter.hasAction(Intent.ACTION_USER_SWITCHED)),
                 isNull(),
                 isNull(),
-                eq(Context.RECEIVER_EXPORTED));
+                eq(Context.RECEIVER_EXPORTED),
+                isNull());
         BroadcastReceiver callback = callbackCaptor.getValue();
 
         Consumer<String> listener = mock(Consumer.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
index 9a4e10c..497a857 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
@@ -22,8 +22,6 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.StatusBarState.KEYGUARD
-import com.android.systemui.statusbar.StatusBarState.SHADE
 import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
@@ -142,11 +140,13 @@
     }
 
     @Test
-    fun computeHeight_returnsLessThanAvailableSpaceUsedToCalculateMaxNotifications() {
+    fun computeHeight_returnsAtMostSpaceAvailable_withGapBeforeShelf() {
         val rowHeight = ROW_HEIGHT
         val shelfHeight = SHELF_HEIGHT
         val totalSpaceForEachRow = GAP_HEIGHT + rowHeight + NOTIFICATION_PADDING
         val availableSpace = totalSpaceForEachRow * 2
+
+        // All rows in separate sections (default setup).
         val rows =
             listOf(
                 createMockRow(rowHeight),
@@ -157,6 +157,28 @@
         assertThat(maxNotifications).isEqualTo(2)
 
         val height = sizeCalculator.computeHeight(stackLayout, maxNotifications, SHELF_HEIGHT)
+        assertThat(height).isAtMost(availableSpace + GAP_HEIGHT + SHELF_HEIGHT)
+    }
+
+    @Test
+    fun computeHeight_returnsAtMostSpaceAvailable_noGapBeforeShelf() {
+        val rowHeight = ROW_HEIGHT
+        val shelfHeight = SHELF_HEIGHT
+        val totalSpaceForEachRow = GAP_HEIGHT + rowHeight + NOTIFICATION_PADDING
+        val availableSpace = totalSpaceForEachRow * 1
+
+        // Both rows are in the same section.
+        whenever(stackLayout.calculateGapHeight(nullable(), nullable(), any()))
+                .thenReturn(0f)
+        val rows =
+                listOf(
+                        createMockRow(rowHeight),
+                        createMockRow(rowHeight))
+
+        val maxNotifications = computeMaxKeyguardNotifications(rows, availableSpace, shelfHeight)
+        assertThat(maxNotifications).isEqualTo(1)
+
+        val height = sizeCalculator.computeHeight(stackLayout, maxNotifications, SHELF_HEIGHT)
         assertThat(height).isAtMost(availableSpace + SHELF_HEIGHT)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 142c2c1..d6a2f0f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -113,6 +113,8 @@
     private SessionTracker mSessionTracker;
     @Mock
     private LatencyTracker mLatencyTracker;
+    @Mock
+    private ScreenOffAnimationController mScreenOffAnimationController;
     private BiometricUnlockController mBiometricUnlockController;
 
     @Before
@@ -127,7 +129,6 @@
         when(mAuthController.isUdfpsFingerDown()).thenReturn(false);
         when(mKeyguardBypassController.canPlaySubtleWindowAnimations()).thenReturn(true);
         mDependency.injectTestDependency(NotificationMediaManager.class, mMediaManager);
-        res.addOverride(com.android.internal.R.integer.config_wakeUpDelayDoze, 0);
         mBiometricUnlockController = new BiometricUnlockController(mDozeScrimController,
                 mKeyguardViewMediator, mScrimController, mShadeController,
                 mNotificationShadeWindowController, mKeyguardStateController, mHandler,
@@ -135,7 +136,7 @@
                 mMetricsLogger, mDumpManager, mPowerManager,
                 mNotificationMediaManager, mWakefulnessLifecycle, mScreenLifecycle,
                 mAuthController, mStatusBarStateController, mKeyguardUnlockAnimationController,
-                mSessionTracker, mLatencyTracker);
+                mSessionTracker, mLatencyTracker, mScreenOffAnimationController);
         mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
         mBiometricUnlockController.setBiometricModeListener(mBiometricModeListener);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
index 22bf6c8..7ef656c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
@@ -876,7 +876,7 @@
     public void testSwitchesToBigClockInSplitShadeOnAod() {
         mStatusBarStateController.setState(KEYGUARD);
         enableSplitShade(/* enabled= */ true);
-        when(mMediaDataManager.hasActiveMedia()).thenReturn(true);
+        when(mMediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(true);
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
         clearInvocations(mKeyguardStatusViewController);
 
@@ -904,7 +904,7 @@
         mStatusBarStateController.setState(KEYGUARD);
         enableSplitShade(/* enabled= */ true);
         clearInvocations(mKeyguardStatusViewController);
-        when(mMediaDataManager.hasActiveMedia()).thenReturn(true);
+        when(mMediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(true);
 
         // one notification + media player visible
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt
index 05a21db..e2fabbd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt
@@ -13,7 +13,6 @@
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.navigationbar.NavigationModeController
 import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener
 import com.android.systemui.recents.OverviewProxyService
@@ -114,25 +113,6 @@
     @Test
     fun testTaskbarVisibleInSplitShade() {
         enableSplitShade()
-        useNewFooter(false)
-
-        given(taskbarVisible = true,
-                navigationMode = GESTURES_NAVIGATION,
-                insets = windowInsets().withStableBottom())
-        then(expectedContainerPadding = 0, // taskbar should disappear when shade is expanded
-                expectedNotificationsMargin = NOTIFICATIONS_MARGIN)
-
-        given(taskbarVisible = true,
-                navigationMode = BUTTONS_NAVIGATION,
-                insets = windowInsets().withStableBottom())
-        then(expectedContainerPadding = STABLE_INSET_BOTTOM,
-                expectedNotificationsMargin = NOTIFICATIONS_MARGIN)
-    }
-
-    @Test
-    fun testTaskbarVisibleInSplitShade_newFooter() {
-        enableSplitShade()
-        useNewFooter(true)
 
         given(taskbarVisible = true,
                 navigationMode = GESTURES_NAVIGATION,
@@ -153,25 +133,6 @@
     fun testTaskbarNotVisibleInSplitShade() {
         // when taskbar is not visible, it means we're on the home screen
         enableSplitShade()
-        useNewFooter(false)
-
-        given(taskbarVisible = false,
-                navigationMode = GESTURES_NAVIGATION,
-                insets = windowInsets().withStableBottom())
-        then(expectedContainerPadding = 0)
-
-        given(taskbarVisible = false,
-                navigationMode = BUTTONS_NAVIGATION,
-                insets = windowInsets().withStableBottom())
-        then(expectedContainerPadding = 0, // qs goes full height as it's not obscuring nav buttons
-                expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN)
-    }
-
-    @Test
-    fun testTaskbarNotVisibleInSplitShade_newFooter() {
-        // when taskbar is not visible, it means we're on the home screen
-        enableSplitShade()
-        useNewFooter(true)
 
         given(taskbarVisible = false,
                 navigationMode = GESTURES_NAVIGATION,
@@ -190,24 +151,6 @@
     @Test
     fun testTaskbarNotVisibleInSplitShadeWithCutout() {
         enableSplitShade()
-        useNewFooter(false)
-
-        given(taskbarVisible = false,
-                navigationMode = GESTURES_NAVIGATION,
-                insets = windowInsets().withCutout())
-        then(expectedContainerPadding = CUTOUT_HEIGHT)
-
-        given(taskbarVisible = false,
-                navigationMode = BUTTONS_NAVIGATION,
-                insets = windowInsets().withCutout().withStableBottom())
-        then(expectedContainerPadding = 0,
-                expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN)
-    }
-
-    @Test
-    fun testTaskbarNotVisibleInSplitShadeWithCutout_newFooter() {
-        enableSplitShade()
-        useNewFooter(true)
 
         given(taskbarVisible = false,
                 navigationMode = GESTURES_NAVIGATION,
@@ -226,23 +169,6 @@
     @Test
     fun testTaskbarVisibleInSinglePaneShade() {
         disableSplitShade()
-        useNewFooter(false)
-
-        given(taskbarVisible = true,
-                navigationMode = GESTURES_NAVIGATION,
-                insets = windowInsets().withStableBottom())
-        then(expectedContainerPadding = 0)
-
-        given(taskbarVisible = true,
-                navigationMode = BUTTONS_NAVIGATION,
-                insets = windowInsets().withStableBottom())
-        then(expectedContainerPadding = STABLE_INSET_BOTTOM)
-    }
-
-    @Test
-    fun testTaskbarVisibleInSinglePaneShade_newFooter() {
-        disableSplitShade()
-        useNewFooter(true)
 
         given(taskbarVisible = true,
                 navigationMode = GESTURES_NAVIGATION,
@@ -260,28 +186,6 @@
     @Test
     fun testTaskbarNotVisibleInSinglePaneShade() {
         disableSplitShade()
-        useNewFooter(false)
-
-        given(taskbarVisible = false,
-                navigationMode = GESTURES_NAVIGATION,
-                insets = emptyInsets())
-        then(expectedContainerPadding = 0)
-
-        given(taskbarVisible = false,
-                navigationMode = GESTURES_NAVIGATION,
-                insets = windowInsets().withCutout().withStableBottom())
-        then(expectedContainerPadding = CUTOUT_HEIGHT)
-
-        given(taskbarVisible = false,
-                navigationMode = BUTTONS_NAVIGATION,
-                insets = windowInsets().withStableBottom())
-        then(expectedContainerPadding = 0, expectedQsPadding = STABLE_INSET_BOTTOM)
-    }
-
-    @Test
-    fun testTaskbarNotVisibleInSinglePaneShade_newFooter() {
-        disableSplitShade()
-        useNewFooter(true)
 
         given(taskbarVisible = false,
                 navigationMode = GESTURES_NAVIGATION,
@@ -303,27 +207,6 @@
     fun testCustomizingInSinglePaneShade() {
         disableSplitShade()
         controller.setCustomizerShowing(true)
-        useNewFooter(false)
-
-        // always sets spacings to 0
-        given(taskbarVisible = false,
-                navigationMode = GESTURES_NAVIGATION,
-                insets = windowInsets().withStableBottom())
-        then(expectedContainerPadding = 0,
-                expectedNotificationsMargin = 0)
-
-        given(taskbarVisible = false,
-                navigationMode = BUTTONS_NAVIGATION,
-                insets = emptyInsets())
-        then(expectedContainerPadding = 0,
-                expectedNotificationsMargin = 0)
-    }
-
-    @Test
-    fun testCustomizingInSinglePaneShade_newFooter() {
-        disableSplitShade()
-        controller.setCustomizerShowing(true)
-        useNewFooter(true)
 
         // always sets spacings to 0
         given(taskbarVisible = false,
@@ -343,27 +226,6 @@
     fun testDetailShowingInSinglePaneShade() {
         disableSplitShade()
         controller.setDetailShowing(true)
-        useNewFooter(false)
-
-        // always sets spacings to 0
-        given(taskbarVisible = false,
-                navigationMode = GESTURES_NAVIGATION,
-                insets = windowInsets().withStableBottom())
-        then(expectedContainerPadding = 0,
-                expectedNotificationsMargin = 0)
-
-        given(taskbarVisible = false,
-                navigationMode = BUTTONS_NAVIGATION,
-                insets = emptyInsets())
-        then(expectedContainerPadding = 0,
-                expectedNotificationsMargin = 0)
-    }
-
-    @Test
-    fun testDetailShowingInSinglePaneShade_newFooter() {
-        disableSplitShade()
-        controller.setDetailShowing(true)
-        useNewFooter(true)
 
         // always sets spacings to 0
         given(taskbarVisible = false,
@@ -383,25 +245,6 @@
     fun testDetailShowingInSplitShade() {
         enableSplitShade()
         controller.setDetailShowing(true)
-        useNewFooter(false)
-
-        given(taskbarVisible = false,
-                navigationMode = GESTURES_NAVIGATION,
-                insets = windowInsets().withStableBottom())
-        then(expectedContainerPadding = 0)
-
-        // should not influence spacing
-        given(taskbarVisible = false,
-                navigationMode = BUTTONS_NAVIGATION,
-                insets = emptyInsets())
-        then(expectedContainerPadding = 0)
-    }
-
-    @Test
-    fun testDetailShowingInSplitShade_newFooter() {
-        enableSplitShade()
-        controller.setDetailShowing(true)
-        useNewFooter(true)
 
         given(taskbarVisible = false,
                 navigationMode = GESTURES_NAVIGATION,
@@ -531,7 +374,6 @@
     @Test
     fun testWindowInsetDebounce() {
         disableSplitShade()
-        useNewFooter(true)
 
         given(taskbarVisible = false,
             navigationMode = GESTURES_NAVIGATION,
@@ -596,13 +438,8 @@
         verify(notificationsQSContainer)
                 .setPadding(anyInt(), anyInt(), anyInt(), eq(expectedContainerPadding))
         verify(notificationsQSContainer).setNotificationsMarginBottom(expectedNotificationsMargin)
-        val newFooter = featureFlags.isEnabled(Flags.NEW_FOOTER)
-        if (newFooter) {
-            verify(notificationsQSContainer)
+        verify(notificationsQSContainer)
                     .setQSContainerPaddingBottom(expectedQsPadding)
-        } else {
-            verify(notificationsQSContainer).setQSScrollPaddingBottom(expectedQsPadding)
-        }
         Mockito.clearInvocations(notificationsQSContainer)
     }
 
@@ -620,10 +457,6 @@
         return this
     }
 
-    private fun useNewFooter(useNewFooter: Boolean) {
-        whenever(featureFlags.isEnabled(Flags.NEW_FOOTER)).thenReturn(useNewFooter)
-    }
-
     private fun getConstraintSetLayout(@IdRes id: Int): ConstraintSet.Layout {
         return constraintSetCaptor.value.getConstraint(id).layout
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java
index 79cbed8..ddccd83 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java
@@ -18,6 +18,7 @@
 
 import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -92,7 +93,12 @@
                 mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController,
                 mConfigurationController, mKeyguardViewMediator, mKeyguardBypassController,
                 mColorExtractor, mDumpManager, mKeyguardStateController,
-                mScreenOffAnimationController, mAuthController);
+                mScreenOffAnimationController, mAuthController) {
+                    @Override
+                    protected boolean isDebuggable() {
+                        return false;
+                    }
+            };
         mNotificationShadeWindowController.setScrimsVisibilityListener((visibility) -> {});
         mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView);
 
@@ -232,6 +238,22 @@
     }
 
     @Test
+    public void setKeyguardShowing_enablesSecureFlag() {
+        mNotificationShadeWindowController.setBouncerShowing(true);
+
+        verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
+        assertThat((mLayoutParameters.getValue().flags & FLAG_SECURE) != 0).isTrue();
+    }
+
+    @Test
+    public void setKeyguardNotShowing_disablesSecureFlag() {
+        mNotificationShadeWindowController.setBouncerShowing(false);
+
+        verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
+        assertThat((mLayoutParameters.getValue().flags & FLAG_SECURE) == 0).isTrue();
+    }
+
+    @Test
     public void rotationBecameAllowed_layoutParamsUpdated() {
         mNotificationShadeWindowController.setKeyguardShowing(true);
         when(mKeyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 3fe6cca..509fa3b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -1460,6 +1460,24 @@
                 .isEqualTo(ScrimState.KEYGUARD.getBehindTint());
     }
 
+    @Test
+    public void testHidesScrimFlickerInActivity() {
+        mScrimController.setKeyguardOccluded(true);
+        mScrimController.transitionTo(ScrimState.KEYGUARD);
+        finishAnimationsImmediately();
+        assertScrimAlpha(Map.of(
+                mScrimInFront, TRANSPARENT,
+                mScrimBehind, TRANSPARENT,
+                mNotificationsScrim, TRANSPARENT));
+
+        mScrimController.transitionTo(ScrimState.SHADE_LOCKED);
+        finishAnimationsImmediately();
+        assertScrimAlpha(Map.of(
+                mScrimInFront, TRANSPARENT,
+                mScrimBehind, TRANSPARENT,
+                mNotificationsScrim, TRANSPARENT));
+    }
+
     private void assertAlphaAfterExpansion(ScrimView scrim, float expectedAlpha, float expansion) {
         mScrimController.setRawPanelExpansionFraction(expansion);
         finishAnimationsImmediately();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 5095094..497f7fb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -59,6 +59,7 @@
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
 import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.CarrierConfigTracker;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.settings.SecureSettings;
 import com.android.systemui.util.time.FakeSystemClock;
@@ -90,6 +91,7 @@
     private OperatorNameViewController mOperatorNameViewController;
     private SecureSettings mSecureSettings;
     private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
+    private final CarrierConfigTracker mCarrierConfigTracker = mock(CarrierConfigTracker.class);
 
     @Mock
     private StatusBarFragmentComponent.Factory mStatusBarFragmentComponentFactory;
@@ -373,6 +375,7 @@
                 mNetworkController,
                 mStatusBarStateController,
                 mCommandQueue,
+                mCarrierConfigTracker,
                 new CollapsedStatusBarFragmentLogger(
                         new LogBuffer("TEST", 1, 1, mock(LogcatEchoTracker.class)),
                         new DisableFlagsLogger()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
index 2577dbd..b714df5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
@@ -18,6 +18,10 @@
 
 import static android.os.BatteryManager.EXTRA_PRESENT;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.staticMockMarker;
+
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
@@ -30,20 +34,24 @@
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.view.View;
 
+import com.android.dx.mockito.inline.extended.StaticInOrder;
+import com.android.settingslib.fuelgauge.BatterySaverUtils;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.power.EnhancedEstimates;
 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
 
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
-
+import org.mockito.MockitoSession;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -53,11 +61,19 @@
     @Mock private PowerManager mPowerManager;
     @Mock private BroadcastDispatcher mBroadcastDispatcher;
     @Mock private DemoModeController mDemoModeController;
+    @Mock private View mView;
     private BatteryControllerImpl mBatteryController;
 
+    private MockitoSession mMockitoSession;
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        mMockitoSession = mockitoSession()
+                .initMocks(this)
+                .mockStatic(BatterySaverUtils.class)
+                .startMocking();
+
         mBatteryController = new BatteryControllerImpl(getContext(),
                 mock(EnhancedEstimates.class),
                 mPowerManager,
@@ -68,6 +84,11 @@
         mBatteryController.init();
     }
 
+    @After
+    public void tearDown() {
+        mMockitoSession.finishMocking();
+    }
+
     @Test
     public void testBatteryInitialized() {
         Assert.assertTrue(mBatteryController.mHasReceivedBattery);
@@ -135,4 +156,33 @@
         // THEN it is informed about the battery state
         verify(cb, atLeastOnce()).onBatteryUnknownStateChanged(true);
     }
+
+    @Test
+    public void testBatteryUtilsCalledOnSetPowerSaveMode() {
+        mBatteryController.setPowerSaveMode(true, mView);
+        mBatteryController.setPowerSaveMode(false, mView);
+
+        StaticInOrder inOrder = inOrder(staticMockMarker(BatterySaverUtils.class));
+        inOrder.verify(() -> BatterySaverUtils.setPowerSaveMode(getContext(), true, true));
+        inOrder.verify(() -> BatterySaverUtils.setPowerSaveMode(getContext(), false, true));
+    }
+
+    @Test
+    public void testSaveViewReferenceWhenSettingPowerSaveMode() {
+        mBatteryController.setPowerSaveMode(false, mView);
+
+        Assert.assertNull(mBatteryController.getLastPowerSaverStartView());
+
+        mBatteryController.setPowerSaveMode(true, mView);
+
+        Assert.assertSame(mView, mBatteryController.getLastPowerSaverStartView().get());
+    }
+
+    @Test
+    public void testClearViewReference() {
+        mBatteryController.setPowerSaveMode(true, mView);
+        mBatteryController.clearLastPowerSaverStartView();
+
+        Assert.assertNull(mBatteryController.getLastPowerSaverStartView());
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt
index 3a5d9ee..146b56e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt
@@ -5,22 +5,22 @@
 /** Fake implementation of [ConfigurationController] for tests. */
 class FakeConfigurationController : ConfigurationController {
 
-    private var listener: ConfigurationController.ConfigurationListener? = null
+    private var listeners = mutableListOf<ConfigurationController.ConfigurationListener>()
 
     override fun addCallback(listener: ConfigurationController.ConfigurationListener) {
-        this.listener = listener
+        listeners += listener
     }
 
     override fun removeCallback(listener: ConfigurationController.ConfigurationListener) {
-        this.listener = null
+        listeners -= listener
     }
 
     override fun onConfigurationChanged(newConfiguration: Configuration?) {
-        listener?.onConfigChanged(newConfiguration)
+        listeners.forEach { it.onConfigChanged(newConfiguration) }
     }
 
     override fun notifyThemeChanged() {
-        listener?.onThemeChanged()
+        listeners.forEach { it.onThemeChanged() }
     }
 
     fun notifyConfigurationChanged() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/RingerModeLiveDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/RingerModeLiveDataTest.kt
index 1fb2aec..436f5b8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/RingerModeLiveDataTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/RingerModeLiveDataTest.kt
@@ -88,14 +88,14 @@
     fun testOnActive_broadcastRegistered() {
         liveData.observeForever(observer)
         verify(broadcastDispatcher)
-                .registerReceiver(any(), any(), eq(executor), eq(UserHandle.ALL), anyInt())
+                .registerReceiver(any(), any(), eq(executor), eq(UserHandle.ALL), anyInt(), any())
     }
 
     @Test
     fun testOnActive_intentFilterHasIntent() {
         liveData.observeForever(observer)
         verify(broadcastDispatcher).registerReceiver(any(), capture(intentFilterCaptor), any(),
-                any(), anyInt())
+                any(), anyInt(), any())
         assertTrue(intentFilterCaptor.value.hasAction(INTENT))
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java
index e66491e..e660e1f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.util.settings;
 
+import android.annotation.UserIdInt;
 import android.content.ContentResolver;
 import android.database.ContentObserver;
 import android.net.Uri;
@@ -34,6 +35,8 @@
     private final Map<String, List<ContentObserver>> mContentObserversAllUsers = new HashMap<>();
 
     public static final Uri CONTENT_URI = Uri.parse("content://settings/fake");
+    @UserIdInt
+    private int mUserId = UserHandle.USER_CURRENT;
 
     public FakeSettings() {
     }
@@ -85,9 +88,13 @@
         return Uri.withAppendedPath(CONTENT_URI, name);
     }
 
+    public void setUserId(@UserIdInt int userId) {
+        mUserId = userId;
+    }
+
     @Override
     public int getUserId() {
-        return UserHandle.USER_CURRENT;
+        return mUserId;
     }
 
     @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java
index 50c1e73..9ca4db4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java
@@ -16,6 +16,7 @@
 
 import android.os.Bundle;
 import android.testing.LeakCheck;
+import android.view.View;
 
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
@@ -47,6 +48,11 @@
     }
 
     @Override
+    public void setPowerSaveMode(boolean powerSave, View view) {
+
+    }
+
+    @Override
     public boolean isPluggedIn() {
         return false;
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
index 40657fb..ce7924a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
@@ -807,7 +807,7 @@
         assertTrue(mBubbleController.hasBubbles());
 
         // Removes the notification
-        mEntryListener.onEntryRemoved(mRow, 0);
+        mEntryListener.onEntryRemoved(mRow, REASON_APP_CANCEL);
         assertFalse(mBubbleController.hasBubbles());
     }
 
@@ -938,7 +938,7 @@
         mBubblesManager.handleDismissalInterception(groupSummary.getEntry());
 
         // WHEN the summary is cancelled by the app
-        mEntryListener.onEntryRemoved(groupSummary.getEntry(), 0);
+        mEntryListener.onEntryRemoved(groupSummary.getEntry(), REASON_APP_CANCEL);
 
         // THEN the summary and its children are removed from bubble data
         assertFalse(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey()));
diff --git a/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java b/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java
index 7e3ede1..d75d648 100644
--- a/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java
+++ b/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java
@@ -34,6 +34,7 @@
 import java.util.List;
 import java.util.Queue;
 import java.util.Set;
+import java.util.concurrent.CancellationException;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
@@ -374,7 +375,8 @@
     private <T> T getFutureResult(AndroidFuture<T> future) {
         try {
             return future.get(600, TimeUnit.SECONDS);
-        } catch (InterruptedException | ExecutionException | TimeoutException e) {
+        } catch (InterruptedException | ExecutionException | TimeoutException
+                | CancellationException e) {
             Slog.w(TAG, "Failed to get result from transport:", e);
             return null;
         } finally {
@@ -403,7 +405,11 @@
         void cancelActiveFutures() {
             synchronized (mActiveFuturesLock) {
                 for (AndroidFuture<?> future : mActiveFutures) {
-                    future.cancel(true);
+                    try {
+                        future.cancel(true);
+                    } catch (CancellationException ignored) {
+                        // This is expected, so ignore the exception.
+                    }
                 }
                 mActiveFutures.clear();
             }
diff --git a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
index 76df8b9..e78c8d1 100644
--- a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
+++ b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
@@ -24,8 +24,10 @@
 import static com.android.server.backup.UserBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
 import static com.android.server.backup.internal.BackupHandler.MSG_RESTORE_OPERATION_TIMEOUT;
 
+import android.annotation.NonNull;
 import android.app.ApplicationThreadConstants;
 import android.app.IBackupAgent;
+import android.app.backup.BackupAgent;
 import android.app.backup.BackupManager;
 import android.app.backup.FullBackup;
 import android.app.backup.IBackupManagerMonitor;
@@ -38,10 +40,12 @@
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.provider.Settings;
+import android.system.OsConstants;
 import android.text.TextUtils;
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.LocalServices;
 import com.android.server.backup.BackupAgentTimeoutParameters;
 import com.android.server.backup.BackupRestoreTask;
@@ -57,6 +61,7 @@
 import com.android.server.backup.utils.RestoreUtils;
 import com.android.server.backup.utils.TarBackupReader;
 
+import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -135,6 +140,8 @@
     private boolean mPipesClosed;
     private final BackupEligibilityRules mBackupEligibilityRules;
 
+    private FileMetadata mReadOnlyParent = null;
+
     public FullRestoreEngine(
             UserBackupManagerService backupManagerService, OperationStorage operationStorage,
             BackupRestoreTask monitorTask, IFullBackupRestoreObserver observer,
@@ -158,6 +165,22 @@
         mBackupEligibilityRules = backupEligibilityRules;
     }
 
+    @VisibleForTesting
+    FullRestoreEngine() {
+        mIsAdbRestore = false;
+        mAllowApks = false;
+        mEphemeralOpToken = 0;
+        mUserId = 0;
+        mBackupEligibilityRules = null;
+        mAgentTimeoutParameters = null;
+        mBuffer = null;
+        mBackupManagerService = null;
+        mOperationStorage = null;
+        mMonitor = null;
+        mMonitorTask = null;
+        mOnlyPackage = null;
+    }
+
     public IBackupAgent getAgent() {
         return mAgent;
     }
@@ -397,6 +420,11 @@
                         okay = false;
                     }
 
+                    if (shouldSkipReadOnlyDir(info)) {
+                        // b/194894879: We don't support restore of read-only dirs.
+                        okay = false;
+                    }
+
                     // At this point we have an agent ready to handle the full
                     // restore data as well as a pipe for sending data to
                     // that agent.  Tell the agent to start reading from the
@@ -573,6 +601,45 @@
         return (info != null);
     }
 
+    boolean shouldSkipReadOnlyDir(FileMetadata info) {
+        if (isValidParent(mReadOnlyParent, info)) {
+            // This file has a read-only parent directory, we shouldn't
+            // restore it.
+            return true;
+        } else {
+            // We're now in a different branch of the file tree, update the parent
+            // value.
+            if (isReadOnlyDir(info)) {
+                // Current directory is read-only. Remember it so that we can skip all
+                // of its contents.
+                mReadOnlyParent = info;
+                Slog.w(TAG, "Skipping restore of " + info.path + " and its contents as "
+                        + "read-only dirs are currently not supported.");
+                return true;
+            } else {
+                mReadOnlyParent = null;
+            }
+        }
+
+        return false;
+    }
+
+    private static boolean isValidParent(FileMetadata parentDir, @NonNull FileMetadata childDir) {
+        return parentDir != null
+                && childDir.packageName.equals(parentDir.packageName)
+                && childDir.domain.equals(parentDir.domain)
+                && childDir.path.startsWith(getPathWithTrailingSeparator(parentDir.path));
+    }
+
+    private static String getPathWithTrailingSeparator(String path) {
+        return path.endsWith(File.separator) ? path : path + File.separator;
+    }
+
+    private static boolean isReadOnlyDir(FileMetadata file) {
+        // Check if owner has 'write' bit in the file's mode value (see 'man -7 inode' for details).
+        return file.type == BackupAgent.TYPE_DIRECTORY && (file.mode & OsConstants.S_IWUSR) == 0;
+    }
+
     private void setUpPipes() throws IOException {
         synchronized (mPipesLock) {
             mPipes = ParcelFileDescriptor.createPipe();
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 5406f71..89c8ca5 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -120,7 +120,6 @@
         "java/com/android/server/am/EventLogTags.logtags",
         "java/com/android/server/wm/EventLogTags.logtags",
         "java/com/android/server/policy/EventLogTags.logtags",
-        ":services.connectivity-tiramisu-sources",
     ],
 
     libs: [
@@ -174,9 +173,6 @@
         "overlayable_policy_aidl-java",
         "SurfaceFlingerProperties",
         "com.android.sysprop.watchdog",
-        // This is used for services.connectivity-tiramisu-sources.
-        // TODO: delete when NetworkStatsService is moved to the mainline module.
-        "net-utils-device-common-bpf",
     ],
     javac_shard_size: 50,
 }
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index e6bf109..7ab3008 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -2132,15 +2132,19 @@
             }
         }
 
-        PackageMonitor monitor = new PackageMonitor() {
+        if (mPackageMonitorsForUser.get(userId) == null) {
+            PackageMonitor monitor = new PackageMonitor() {
                 @Override
                 public void onPackageRemoved(String packageName, int uid) {
                     updateLegacyStorageApps(packageName, uid, false);
                 }
             };
-        // TODO(b/149391976): Use different handler?
-        monitor.register(mContext, user, true, mHandler);
-        mPackageMonitorsForUser.put(userId, monitor);
+            // TODO(b/149391976): Use different handler?
+            monitor.register(mContext, user, true, mHandler);
+            mPackageMonitorsForUser.put(userId, monitor);
+        } else {
+            Slog.w(TAG, "PackageMonitor is already registered for: " + userId);
+        }
     }
 
     private static long getLastAccessTime(AppOpsManager manager,
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 839cdc6..a67b858d 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -1036,12 +1036,6 @@
                 callingFeatureId, callback, eventList, notifyNow, subId);
     }
 
-    private void listen(String callingPackage, @Nullable String callingFeatureId,
-            IPhoneStateListener callback, Set<Integer> events, boolean notifyNow, int subId) {
-        listen(false, false, callingPackage,
-                callingFeatureId, callback, events, notifyNow, subId);
-    }
-
     private void listen(boolean renounceFineLocationAccess,
             boolean renounceCoarseLocationAccess, String callingPackage,
             @Nullable String callingFeatureId, IPhoneStateListener callback,
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 2f84ec5..e68a0a6 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -38,6 +38,7 @@
 import android.app.AlarmManager;
 import android.app.IOnProjectionStateChangedListener;
 import android.app.IUiModeManager;
+import android.app.KeyguardManager;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
@@ -149,6 +150,8 @@
     private int mCarModeEnableFlags;
     private boolean mSetupWizardComplete;
 
+    // flag set by resource, whether to start dream immediately upon docking even if unlocked.
+    private boolean mStartDreamImmediatelyOnDock = true;
     // flag set by resource, whether to enable Car dock launch when starting car mode.
     private boolean mEnableCarDockLaunch = true;
     // flag set by resource, whether to lock UI mode to the default one or not.
@@ -173,6 +176,7 @@
     private ActivityTaskManagerInternal mActivityTaskManager;
     private AlarmManager mAlarmManager;
     private PowerManager mPowerManager;
+    private KeyguardManager mKeyguardManager;
 
     // In automatic scheduling, the user is able
     // to override the computed night mode until the two match
@@ -374,6 +378,7 @@
             synchronized (mLock) {
                 final Context context = getContext();
                 mSystemReady = true;
+                mKeyguardManager = context.getSystemService(KeyguardManager.class);
                 mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
                 mWakeLock = mPowerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG);
                 mWindowManager = LocalServices.getService(WindowManagerInternal.class);
@@ -412,6 +417,8 @@
         verifySetupWizardCompleted();
 
         final Resources res = context.getResources();
+        mStartDreamImmediatelyOnDock = res.getBoolean(
+                com.android.internal.R.bool.config_startDreamImmediatelyOnDock);
         mNightMode = res.getInteger(
                 com.android.internal.R.integer.config_defaultNightMode);
         mDefaultUiModeType = res.getInteger(
@@ -1294,6 +1301,8 @@
             pw.print("  mDockState="); pw.print(mDockState);
             pw.print(" mLastBroadcastState="); pw.println(mLastBroadcastState);
 
+            pw.print(" mStartDreamImmediatelyOnDock="); pw.print(mStartDreamImmediatelyOnDock);
+
             pw.print("  mNightMode="); pw.print(mNightMode); pw.print(" (");
             pw.print(Shell.nightModeToStr(mNightMode, mNightModeCustomType)); pw.print(") ");
             pw.print(" mOverrideOn/Off="); pw.print(mOverrideNightModeOn);
@@ -1803,8 +1812,9 @@
         // Send the new configuration.
         applyConfigurationExternallyLocked();
 
-        // If we did not start a dock app, then start dreaming if supported.
-        if (category != null && !dockAppStarted) {
+        // If we did not start a dock app, then start dreaming if appropriate.
+        if (category != null && !dockAppStarted && (mStartDreamImmediatelyOnDock
+                || mKeyguardManager.isKeyguardLocked())) {
             Sandman.startDreamWhenDockedIfAppropriate(getContext());
         }
     }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index be0d797..0da14bc 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -30,6 +30,7 @@
 import static android.app.ActivityManager.INSTR_FLAG_DISABLE_TEST_API_CHECKS;
 import static android.app.ActivityManager.INSTR_FLAG_NO_RESTART;
 import static android.app.ActivityManager.INTENT_SENDER_ACTIVITY;
+import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL;
 import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
 import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
 import static android.app.ActivityManager.PROCESS_STATE_TOP;
@@ -207,7 +208,7 @@
 import android.appwidget.AppWidgetManager;
 import android.appwidget.AppWidgetManagerInternal;
 import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledSince;
+import android.compat.annotation.Disabled;
 import android.content.AttributionSource;
 import android.content.AutofillOptions;
 import android.content.BroadcastReceiver;
@@ -309,7 +310,6 @@
 import android.sysprop.VoldProperties;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
-import android.text.format.DateUtils;
 import android.text.style.SuggestionSpan;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -460,14 +460,6 @@
     private static final String SYSTEM_PROPERTY_DEVICE_PROVISIONED =
             "persist.sys.device_provisioned";
 
-    /**
-     * Enabling this flag enforces the requirement for context registered receivers to use one of
-     * {@link Context#RECEIVER_EXPORTED} or {@link Context#RECEIVER_NOT_EXPORTED} for unprotected
-     * broadcasts
-     */
-    private static final boolean ENFORCE_DYNAMIC_RECEIVER_EXPLICIT_EXPORT =
-            SystemProperties.getBoolean("fw.enforce_dynamic_receiver_explicit_export", false);
-
     static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityManagerService" : TAG_AM;
     static final String TAG_BACKUP = TAG + POSTFIX_BACKUP;
     private static final String TAG_BROADCAST = TAG + POSTFIX_BROADCAST;
@@ -584,7 +576,7 @@
      * unprotected broadcast in code.
      */
     @ChangeId
-    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+    @Disabled
     private static final long DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED = 161145287L;
 
     /**
@@ -1483,6 +1475,7 @@
     final ActivityThread mSystemThread;
 
     final UidObserverController mUidObserverController;
+    private volatile IUidObserver mNetworkPolicyUidObserver;
 
     final AppRestrictionController mAppRestrictionController;
 
@@ -8691,7 +8684,7 @@
         }
     }
 
-    private final ArrayMap<String, long[]> mErrorClusterRecords = new ArrayMap<>();
+    private final DropboxRateLimiter mDropboxRateLimiter = new DropboxRateLimiter();
 
     /**
      * Write a description of an error (crash, WTF, ANR) to the drop box.
@@ -8726,22 +8719,8 @@
         final String dropboxTag = processClass(process) + "_" + eventType;
         if (dbox == null || !dbox.isTagEnabled(dropboxTag)) return;
 
-        // Rate-limit how often we're willing to do the heavy lifting below to
-        // collect and record logs; currently 5 logs per 10 second period per eventType.
-        final long now = SystemClock.elapsedRealtime();
-        synchronized (mErrorClusterRecords) {
-            long[] errRecord = mErrorClusterRecords.get(eventType);
-            if (errRecord == null) {
-                errRecord = new long[2]; // [0]: startTime, [1]: count
-                mErrorClusterRecords.put(eventType, errRecord);
-            }
-            if (now - errRecord[0] > 10 * DateUtils.SECOND_IN_MILLIS) {
-                errRecord[0] = now;
-                errRecord[1] = 1L;
-            } else {
-                if (errRecord[1]++ >= 5) return;
-            }
-        }
+        // Check if we should rate limit and abort early if needed.
+        if (mDropboxRateLimiter.shouldRateLimit(eventType, processName)) return;
 
         final StringBuilder sb = new StringBuilder(1024);
         appendDropBoxProcessHeaders(process, processName, sb);
@@ -13068,25 +13047,14 @@
                     // sticky broadcast, no flag specified (flag isn't required)
                     flags |= Context.RECEIVER_EXPORTED;
                 } else if (requireExplicitFlagForDynamicReceivers && !explicitExportStateDefined) {
-                    if (ENFORCE_DYNAMIC_RECEIVER_EXPLICIT_EXPORT) {
-                        throw new SecurityException(
-                                callerPackage + ": Targeting T+ (version "
-                                        + Build.VERSION_CODES.TIRAMISU
-                                        + " and above) requires that one of RECEIVER_EXPORTED or "
-                                        + "RECEIVER_NOT_EXPORTED be specified when registering a "
-                                        + "receiver");
-                    } else {
-                        Slog.wtf(TAG,
-                                callerPackage + ": Targeting T+ (version "
-                                        + Build.VERSION_CODES.TIRAMISU
-                                        + " and above) requires that one of RECEIVER_EXPORTED or "
-                                        + "RECEIVER_NOT_EXPORTED be specified when registering a "
-                                        + "receiver");
-                        // Assume default behavior-- flag check is not enforced
-                        flags |= Context.RECEIVER_EXPORTED;
-                    }
-                } else if (!requireExplicitFlagForDynamicReceivers) {
-                    // Change is not enabled, thus not targeting T+. Assume exported.
+                    throw new SecurityException(
+                            callerPackage + ": One of RECEIVER_EXPORTED or "
+                                    + "RECEIVER_NOT_EXPORTED should be specified when a receiver "
+                                    + "isn't being registered exclusively for system broadcasts");
+                    // Assume default behavior-- flag check is not enforced
+                } else if (!requireExplicitFlagForDynamicReceivers && (
+                        (flags & Context.RECEIVER_NOT_EXPORTED) == 0)) {
+                    // Change is not enabled, assume exported unless otherwise specified.
                     flags |= Context.RECEIVER_EXPORTED;
                 }
             } else if ((flags & Context.RECEIVER_NOT_EXPORTED) == 0) {
@@ -17232,17 +17200,17 @@
         }
 
         @Override
-        public boolean isUidCurrentlyInstrumented(int uid) {
+        public int getInstrumentationSourceUid(int uid) {
             synchronized (mProcLock) {
                 for (int i = mActiveInstrumentation.size() - 1; i >= 0; i--) {
                     ActiveInstrumentation activeInst = mActiveInstrumentation.get(i);
                     if (!activeInst.mFinished && activeInst.mTargetInfo != null
                             && activeInst.mTargetInfo.uid == uid) {
-                        return true;
+                        return activeInst.mSourceUid;
                     }
                 }
             }
-            return false;
+            return INVALID_UID;
         }
 
         @Override
@@ -17300,6 +17268,19 @@
             if (isNewPending && mOomAdjuster != null) { // It can be null in unit test.
                 mOomAdjuster.mCachedAppOptimizer.unfreezeProcess(pid);
             }
+            // We need to update the network rules for the app coming to the top state so that
+            // it can access network when the device or the app is in a restricted state
+            // (e.g. battery/data saver) but since waiting for updateOomAdj to complete and then
+            // informing NetworkPolicyManager might get delayed, informing the state change as soon
+            // as we know app is going to come to the top state.
+            if (mNetworkPolicyUidObserver != null) {
+                try {
+                    mNetworkPolicyUidObserver.onUidStateChanged(uid, PROCESS_STATE_TOP,
+                            mProcessList.getProcStateSeqCounter(), PROCESS_CAPABILITY_ALL);
+                } catch (RemoteException e) {
+                    // Should not happen; call is within the same process
+                }
+            }
         }
 
         @Override
@@ -17497,6 +17478,14 @@
         public void restart() {
             ActivityManagerService.this.restart();
         }
+
+        @Override
+        public void registerNetworkPolicyUidObserver(@NonNull IUidObserver observer,
+                int which, int cutpoint, @NonNull String callingPackage) {
+            mNetworkPolicyUidObserver = observer;
+            mUidObserverController.register(observer, which, cutpoint, callingPackage,
+                    Binder.getCallingUid());
+        }
     }
 
     long inputDispatchingTimedOut(int pid, final boolean aboveSystem, String reason) {
diff --git a/services/core/java/com/android/server/am/AppBatteryExemptionTracker.java b/services/core/java/com/android/server/am/AppBatteryExemptionTracker.java
index 6f11b00..b07d9a6 100644
--- a/services/core/java/com/android/server/am/AppBatteryExemptionTracker.java
+++ b/services/core/java/com/android/server/am/AppBatteryExemptionTracker.java
@@ -39,6 +39,7 @@
 import com.android.server.am.AppBatteryTracker.AppBatteryPolicy;
 import com.android.server.am.AppBatteryTracker.BatteryUsage;
 import com.android.server.am.AppBatteryTracker.ImmutableBatteryUsage;
+import com.android.server.am.AppRestrictionController.TrackerType;
 import com.android.server.am.BaseAppStateTimeEvents.BaseTimeEvent;
 import com.android.server.am.BaseAppStateTracker.Injector;
 import com.android.server.am.BaseAppStateTracker.StateListener;
@@ -85,6 +86,11 @@
     }
 
     @Override
+    @TrackerType int getType() {
+        return AppRestrictionController.TRACKER_TYPE_BATTERY_EXEMPTION;
+    }
+
+    @Override
     void onSystemReady() {
         super.onSystemReady();
         mAppRestrictionController.forEachTracker(tracker -> {
diff --git a/services/core/java/com/android/server/am/AppBatteryTracker.java b/services/core/java/com/android/server/am/AppBatteryTracker.java
index 0cbc7ad..64ff532 100644
--- a/services/core/java/com/android/server/am/AppBatteryTracker.java
+++ b/services/core/java/com/android/server/am/AppBatteryTracker.java
@@ -49,12 +49,14 @@
 import android.content.pm.ServiceInfo;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
+import android.os.AppBackgroundRestrictionsInfo;
 import android.os.AppBatteryStatsProto;
 import android.os.BatteryConsumer;
 import android.os.BatteryConsumer.Dimensions;
 import android.os.BatteryStatsInternal;
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
+import android.os.Build;
 import android.os.PowerExemptionManager;
 import android.os.PowerExemptionManager.ReasonCode;
 import android.os.SystemClock;
@@ -62,6 +64,7 @@
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
 import android.util.ArraySet;
+import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
@@ -72,7 +75,9 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.am.AppBatteryTracker.AppBatteryPolicy;
+import com.android.server.am.AppRestrictionController.TrackerType;
 import com.android.server.am.AppRestrictionController.UidBatteryUsageProvider;
 import com.android.server.pm.UserManagerInternal;
 
@@ -80,6 +85,7 @@
 import java.lang.reflect.Constructor;
 import java.util.Arrays;
 import java.util.List;
+import java.util.concurrent.CountDownLatch;
 
 /**
  * The battery usage tracker for apps, currently we are focusing on background + FGS battery here.
@@ -90,6 +96,9 @@
 
     static final boolean DEBUG_BACKGROUND_BATTERY_TRACKER = false;
 
+    static final boolean DEBUG_BACKGROUND_BATTERY_TRACKER_VERBOSE =
+            DEBUG_BACKGROUND_BATTERY_TRACKER | Build.IS_DEBUGGABLE;
+
     // As we don't support realtime per-UID battery usage stats yet, we're polling the stats
     // in a regular time basis.
     private final long mBatteryUsageStatsPollingIntervalMs;
@@ -168,8 +177,14 @@
     @GuardedBy("mLock")
     private long mLastUidBatteryUsageStartTs;
 
+    /**
+     * elapseRealTime of last time the AppBatteryTracker is reported to statsd.
+     */
+    @GuardedBy("mLock")
+    private long mLastReportTime = 0;
+
     // For debug only.
-    private final SparseArray<BatteryUsage> mDebugUidPercentages = new SparseArray<>();
+    private final SparseArray<ImmutableBatteryUsage> mDebugUidPercentages = new SparseArray<>();
 
     AppBatteryTracker(Context context, AppRestrictionController controller) {
         this(context, controller, null, null);
@@ -195,6 +210,11 @@
     }
 
     @Override
+    @TrackerType int getType() {
+        return AppRestrictionController.TRACKER_TYPE_BATTERY;
+    }
+
+    @Override
     void onSystemReady() {
         super.onSystemReady();
         final UserManagerInternal um = mInjector.getUserManagerInternal();
@@ -216,9 +236,92 @@
                     mBgHandler.postDelayed(mBgBatteryUsageStatsPolling, delay);
                 }
             }
+            logAppBatteryTrackerIfNeeded();
         }
     }
 
+    /**
+     * Log per-uid BatteryTrackerInfo to statsd every 24 hours (as the window specified in
+     * {@link AppBatteryPolicy#mBgCurrentDrainWindowMs})
+     */
+    private void logAppBatteryTrackerIfNeeded() {
+        final long now = SystemClock.elapsedRealtime();
+        synchronized (mLock) {
+            final AppBatteryPolicy bgPolicy = mInjector.getPolicy();
+            if (now - mLastReportTime < bgPolicy.mBgCurrentDrainWindowMs) {
+                return;
+            } else {
+                mLastReportTime = now;
+            }
+        }
+        updateBatteryUsageStatsIfNecessary(mInjector.currentTimeMillis(), true);
+        synchronized (mLock) {
+            for (int i = 0, size = mUidBatteryUsageInWindow.size(); i < size; i++) {
+                final int uid = mUidBatteryUsageInWindow.keyAt(i);
+                if (!UserHandle.isCore(uid) && !UserHandle.isApp(uid)) {
+                    continue;
+                }
+                if (BATTERY_USAGE_NONE.equals(mUidBatteryUsageInWindow.valueAt(i))) {
+                    continue;
+                }
+                FrameworkStatsLog.write(FrameworkStatsLog.APP_BACKGROUND_RESTRICTIONS_INFO,
+                        uid,
+                        FrameworkStatsLog
+                                .APP_BACKGROUND_RESTRICTIONS_INFO__RESTRICTION_LEVEL__LEVEL_UNKNOWN,
+                        FrameworkStatsLog
+                                .APP_BACKGROUND_RESTRICTIONS_INFO__THRESHOLD__THRESHOLD_UNKNOWN,
+                        FrameworkStatsLog
+                                .APP_BACKGROUND_RESTRICTIONS_INFO__TRACKER__UNKNOWN_TRACKER,
+                        null /*byte[] fgs_tracker_info*/,
+                        getBatteryTrackerInfoProtoLocked(uid) /*byte[] battery_tracker_info*/,
+                        null /*byte[] broadcast_events_tracker_info*/,
+                        null /*byte[] bind_service_events_tracker_info*/,
+                        FrameworkStatsLog
+                                .APP_BACKGROUND_RESTRICTIONS_INFO__EXEMPTION_REASON__REASON_UNKNOWN,
+                        FrameworkStatsLog
+                                .APP_BACKGROUND_RESTRICTIONS_INFO__OPT_LEVEL__UNKNOWN,
+                        FrameworkStatsLog
+                                .APP_BACKGROUND_RESTRICTIONS_INFO__TARGET_SDK__SDK_UNKNOWN,
+                        isLowRamDeviceStatic());
+            }
+        }
+    }
+
+    /**
+     * Get the BatteryTrackerInfo proto of a UID.
+     * @param uid
+     * @return byte array of the proto.
+     */
+     @NonNull byte[] getBatteryTrackerInfoProtoLocked(int uid) {
+        final ImmutableBatteryUsage temp = mUidBatteryUsageInWindow.get(uid);
+        if (temp == null) {
+            return new byte[0];
+        }
+        final BatteryUsage bgUsage = temp.calcPercentage(uid, mInjector.getPolicy());
+        final double allUsage = bgUsage.mPercentage[BatteryUsage.BATTERY_USAGE_INDEX_UNSPECIFIED]
+                + bgUsage.mPercentage[BatteryUsage.BATTERY_USAGE_INDEX_FOREGROUND]
+                + bgUsage.mPercentage[BatteryUsage.BATTERY_USAGE_INDEX_BACKGROUND]
+                + bgUsage.mPercentage[BatteryUsage.BATTERY_USAGE_INDEX_FOREGROUND_SERVICE]
+                + bgUsage.mPercentage[BatteryUsage.BATTERY_USAGE_INDEX_CACHED];
+        final double usageBackground =
+                bgUsage.mPercentage[BatteryUsage.BATTERY_USAGE_INDEX_BACKGROUND];
+        final double usageFgs =
+                bgUsage.mPercentage[BatteryUsage.BATTERY_USAGE_INDEX_FOREGROUND_SERVICE];
+        Slog.d(TAG, "getBatteryTrackerInfoProtoLocked uid:" + uid
+                + " allUsage:" + String.format("%4.2f%%", allUsage)
+                + " usageBackground:" + String.format("%4.2f%%", usageBackground)
+                + " usageFgs:" + String.format("%4.2f%%", usageFgs));
+        final ProtoOutputStream proto = new ProtoOutputStream();
+        proto.write(AppBackgroundRestrictionsInfo.BatteryTrackerInfo.BATTERY_24H,
+                allUsage * 10000);
+        proto.write(AppBackgroundRestrictionsInfo.BatteryTrackerInfo.BATTERY_USAGE_BACKGROUND,
+                usageBackground * 10000);
+        proto.write(AppBackgroundRestrictionsInfo.BatteryTrackerInfo.BATTERY_USAGE_FGS,
+                usageFgs * 10000);
+        proto.flush();
+        return proto.getBytes();
+    }
+
     @Override
     void onUserStarted(final @UserIdInt int userId) {
         synchronized (mLock) {
@@ -331,10 +434,12 @@
                                 bgPolicy.mBgCurrentDrainExemptedTypes);
                 // It's possible the exemptedUsage could be larger than actualUsage,
                 // as the former one is an approximate value.
-                final BatteryUsage bgUsage = actualUsage.mutate()
+                final ImmutableBatteryUsage bgUsage = actualUsage.mutate()
                         .subtract(exemptedUsage)
-                        .calcPercentage(uid, bgPolicy);
-                if (DEBUG_BACKGROUND_BATTERY_TRACKER) {
+                        .calcPercentage(uid, bgPolicy)
+                        .unmutate();
+                if (DEBUG_BACKGROUND_BATTERY_TRACKER_VERBOSE
+                        && !BATTERY_USAGE_NONE.equals(actualUsage)) {
                     Slog.i(TAG, String.format(
                             "UID %d: %s (%s) | %s | %s over the past %s",
                             uid,
@@ -414,7 +519,7 @@
             }
         }
 
-        if (DEBUG_BACKGROUND_BATTERY_TRACKER) {
+        if (DEBUG_BACKGROUND_BATTERY_TRACKER_VERBOSE) {
             Slog.i(TAG, "updateBatteryUsageStatsOnce");
         }
 
@@ -471,14 +576,17 @@
                 final BatteryUsage lastUsage = mLastUidBatteryUsage.get(uid, BATTERY_USAGE_NONE);
                 final BatteryUsage curUsage = buf.valueAt(i);
                 final BatteryUsage before;
+                final BatteryUsage totalUsage;
                 if (index >= 0) {
-                    before = mUidBatteryUsage.valueAt(index);
-                    before.subtract(lastUsage).add(curUsage);
+                    totalUsage = mUidBatteryUsage.valueAt(index);
+                    before = DEBUG_BACKGROUND_BATTERY_TRACKER_VERBOSE
+                            ? new BatteryUsage(totalUsage) : BATTERY_USAGE_NONE;
+                    totalUsage.subtract(lastUsage).add(curUsage);
                 } else {
-                    before = BATTERY_USAGE_NONE;
+                    before = totalUsage = BATTERY_USAGE_NONE;
                     mUidBatteryUsage.put(uid, curUsage);
                 }
-                if (DEBUG_BACKGROUND_BATTERY_TRACKER) {
+                if (DEBUG_BACKGROUND_BATTERY_TRACKER_VERBOSE) {
                     final BatteryUsage actualDelta = new BatteryUsage(curUsage).subtract(lastUsage);
                     String msg = "Updating mUidBatteryUsage uid=" + uid + ", before=" + before
                             + ", after=" + mUidBatteryUsage.get(uid, BATTERY_USAGE_NONE)
@@ -490,7 +598,7 @@
                     if (!actualDelta.isValid()) {
                         // Something is wrong, the battery usage shouldn't be negative.
                         Slog.e(TAG, msg);
-                    } else {
+                    } else if (!BATTERY_USAGE_NONE.equals(actualDelta)) {
                         Slog.i(TAG, msg);
                     }
                 }
@@ -513,11 +621,16 @@
                 copyUidBatteryUsage(buf, mUidBatteryUsageInWindow);
             }
         }
-        if (DEBUG_BACKGROUND_BATTERY_TRACKER) {
+        if (DEBUG_BACKGROUND_BATTERY_TRACKER_VERBOSE) {
             synchronized (mLock) {
                 for (int i = 0, size = mUidBatteryUsageInWindow.size(); i < size; i++) {
+                    final ImmutableBatteryUsage usage = mUidBatteryUsageInWindow.valueAt(i);
+                    if (BATTERY_USAGE_NONE.equals(usage)) {
+                        // Skip the logging to avoid spamming.
+                        continue;
+                    }
                     Slog.i(TAG, "mUidBatteryUsageInWindow uid=" + mUidBatteryUsageInWindow.keyAt(i)
-                            + " usage=" + mUidBatteryUsageInWindow.valueAt(i));
+                            + " usage=" + usage);
                 }
             }
         }
@@ -546,7 +659,19 @@
             for (UidBatteryConsumer uidConsumer : uidConsumers) {
                 // TODO: b/200326767 - as we are not supporting per proc state attribution yet,
                 // we couldn't distinguish between a real FGS vs. a bound FGS proc state.
-                final int uid = uidConsumer.getUid();
+                final int rawUid = uidConsumer.getUid();
+                if (UserHandle.isIsolated(rawUid)) {
+                    // Isolated processes should have been attributed to their parent processes.
+                    continue;
+                }
+                int uid = rawUid;
+                // Keep the logic in sync with BatteryAppListPreferenceController.java
+                // Check if this UID is a shared GID. If so, we combine it with the OWNER's
+                // actual app UID.
+                final int sharedAppId = UserHandle.getAppIdFromSharedAppGid(uid);
+                if (sharedAppId > 0) {
+                    uid = UserHandle.getUid(UserHandle.USER_SYSTEM, sharedAppId);
+                }
                 final BatteryUsage bgUsage = new BatteryUsage(uidConsumer, bgPolicy)
                         .scale(scale);
                 int index = buf.indexOfKey(uid);
@@ -556,9 +681,13 @@
                     final BatteryUsage before = buf.valueAt(index);
                     before.add(bgUsage);
                 }
-                if (DEBUG_BACKGROUND_BATTERY_TRACKER) {
-                    Slog.i(TAG, "updateBatteryUsageStatsOnceInternal uid=" + uid
+                if (DEBUG_BACKGROUND_BATTERY_TRACKER_VERBOSE
+                        && !BATTERY_USAGE_NONE.equals(bgUsage)) {
+                    Slog.i(TAG, "updateBatteryUsageStatsOnceInternal uid=" + rawUid
                             + ", bgUsage=" + bgUsage
+                            + (rawUid == uid ? ""
+                            : ", realUid=" + uid
+                            + ", realUsage=" + buf.get(uid))
                             + ", start=" + start
                             + ", end=" + end);
                 }
@@ -610,7 +739,8 @@
     void setDebugUidPercentage(int[] uids, double[][] percentages) {
         mDebugUidPercentages.clear();
         for (int i = 0; i < uids.length; i++) {
-            mDebugUidPercentages.put(uids[i], new BatteryUsage().setPercentage(percentages[i]));
+            mDebugUidPercentages.put(uids[i],
+                    new BatteryUsage().setPercentage(percentages[i]).unmutate());
         }
         scheduleBgBatteryUsageStatsCheck();
     }
@@ -636,7 +766,20 @@
     void dump(PrintWriter pw, String prefix) {
         pw.print(prefix);
         pw.println("APP BATTERY STATE TRACKER:");
+        // Force an update.
         updateBatteryUsageStatsIfNecessary(mInjector.currentTimeMillis(), true);
+        // Force a check.
+        scheduleBgBatteryUsageStatsCheck();
+        // Wait for its completion (as it runs in handler thread for the sake of thread safe)
+        final CountDownLatch latch = new CountDownLatch(1);
+        mBgHandler.getLooper().getQueue().addIdleHandler(() -> {
+            latch.countDown();
+            return false;
+        });
+        try {
+            latch.await();
+        } catch (InterruptedException e) {
+        }
         synchronized (mLock) {
             final SparseArray<ImmutableBatteryUsage> uidConsumers = mUidBatteryUsageInWindow;
             pw.print("  " + prefix);
@@ -708,6 +851,7 @@
         final double foregroundUsage = usage.getUsagePowerMah(PROCESS_STATE_FOREGROUND);
         final double backgroundUsage = usage.getUsagePowerMah(PROCESS_STATE_BACKGROUND);
         final double fgsUsage = usage.getUsagePowerMah(PROCESS_STATE_FOREGROUND_SERVICE);
+        final double cachedUsage = usage.getUsagePowerMah(PROCESS_STATE_CACHED);
 
         if (foregroundUsage == 0 && backgroundUsage == 0 && fgsUsage == 0) {
             return;
@@ -724,6 +868,9 @@
         dumpProcessStateStats(proto,
                 AppBatteryStatsProto.UidStats.ProcessStateStats.FOREGROUND_SERVICE,
                 fgsUsage);
+        dumpProcessStateStats(proto,
+                AppBatteryStatsProto.UidStats.ProcessStateStats.CACHED,
+                cachedUsage);
         proto.end(token);
     }
 
@@ -801,8 +948,12 @@
         }
 
         private BatteryUsage setToInternal(@NonNull BatteryUsage other) {
-            for (int i = 0; i < other.mUsage.length; i++) {
-                mUsage[i] = other.mUsage[i];
+            System.arraycopy(other.mUsage, 0, mUsage, 0, other.mUsage.length);
+            if (other.mPercentage != null) {
+                mPercentage = new double[other.mPercentage.length];
+                System.arraycopy(other.mPercentage, 0, mPercentage, 0, other.mPercentage.length);
+            } else {
+                mPercentage = null;
             }
             return this;
         }
@@ -864,9 +1015,14 @@
 
         double getUsagePowerMah(@BatteryConsumer.ProcessState int processState) {
             switch (processState) {
-                case PROCESS_STATE_FOREGROUND: return mUsage[1];
-                case PROCESS_STATE_BACKGROUND: return mUsage[2];
-                case PROCESS_STATE_FOREGROUND_SERVICE: return mUsage[3];
+                case PROCESS_STATE_FOREGROUND:
+                    return mUsage[BATTERY_USAGE_INDEX_FOREGROUND];
+                case PROCESS_STATE_BACKGROUND:
+                    return mUsage[BATTERY_USAGE_INDEX_BACKGROUND];
+                case PROCESS_STATE_FOREGROUND_SERVICE:
+                    return mUsage[BATTERY_USAGE_INDEX_FOREGROUND_SERVICE];
+                case PROCESS_STATE_CACHED:
+                    return mUsage[BATTERY_USAGE_INDEX_CACHED];
             }
             return 0;
         }
@@ -1282,11 +1438,12 @@
 
         /**
          * List of the packages with significant background battery usage, key is the UID of
-         * the package and value is an array of the timestamps when the UID is found guilty and
-         * should be moved to the next level of restriction.
+         * the package and value is the pair of {timestamp[], battery usage snapshot[]}
+         * when the UID is found guilty and should be moved to the next level of restriction.
          */
         @GuardedBy("mLock")
-        private final SparseArray<long[]> mHighBgBatteryPackages = new SparseArray<>();
+        private final SparseArray<Pair<long[], ImmutableBatteryUsage[]>> mHighBgBatteryPackages =
+                new SparseArray<>();
 
         @NonNull
         private final Object mLock;
@@ -1516,8 +1673,9 @@
                 return RESTRICTION_LEVEL_UNKNOWN;
             }
             synchronized (mLock) {
-                final long[] ts = mHighBgBatteryPackages.get(uid);
-                if (ts != null) {
+                final Pair<long[], ImmutableBatteryUsage[]> pair = mHighBgBatteryPackages.get(uid);
+                if (pair != null) {
+                    final long[] ts = pair.first;
                     final int restrictedLevel = ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET] > 0
                             ? RESTRICTION_LEVEL_RESTRICTED_BUCKET
                             : RESTRICTION_LEVEL_ADAPTIVE_BUCKET;
@@ -1586,10 +1744,10 @@
             return sb.toString();
         }
 
-        void handleUidBatteryUsage(final int uid, final BatteryUsage usage) {
+        void handleUidBatteryUsage(final int uid, final ImmutableBatteryUsage usage) {
             final @ReasonCode int reason = shouldExemptUid(uid);
             if (reason != REASON_DENIED) {
-                if (DEBUG_BACKGROUND_BATTERY_TRACKER) {
+                if (DEBUG_BACKGROUND_BATTERY_TRACKER_VERBOSE && !BATTERY_USAGE_NONE.equals(usage)) {
                     Slog.i(TAG, "Exempting battery usage in " + UserHandle.formatUid(uid)
                             + " " + PowerExemptionManager.reasonCodeToString(reason));
                 }
@@ -1597,6 +1755,7 @@
             }
             boolean notifyController = false;
             boolean excessive = false;
+            int index = 0;
             final double rbPercentage = sumPercentageOfTypes(usage.getPercentage(),
                     mBgCurrentDrainRestrictedBucketTypes);
             final double brPercentage = sumPercentageOfTypes(usage.getPercentage(),
@@ -1610,33 +1769,41 @@
                 final long now = SystemClock.elapsedRealtime();
                 final int thresholdIndex = getCurrentDrainThresholdIndex(uid, now,
                         mBgCurrentDrainWindowMs);
-                final int index = mHighBgBatteryPackages.indexOfKey(uid);
+                index = mHighBgBatteryPackages.indexOfKey(uid);
                 final boolean decoupleThresholds = mBgCurrentDrainDecoupleThresholds;
                 final double rbThreshold = mBgCurrentDrainRestrictedBucketThreshold[thresholdIndex];
                 final double brThreshold = mBgCurrentDrainBgRestrictedThreshold[thresholdIndex];
                 if (index < 0) {
                     long[] ts = null;
+                    ImmutableBatteryUsage[] usages = null;
                     if (rbPercentage >= rbThreshold) {
                         // New findings to us, track it and let the controller know.
                         ts = new long[TIME_STAMP_INDEX_LAST];
                         ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = now;
-                        mHighBgBatteryPackages.put(uid, ts);
+                        usages = new ImmutableBatteryUsage[TIME_STAMP_INDEX_LAST];
+                        usages[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = usage;
+                        mHighBgBatteryPackages.put(uid, Pair.create(ts, usages));
                         notifyController = excessive = true;
                     }
                     if (decoupleThresholds && brPercentage >= brThreshold) {
                         if (ts == null) {
                             ts = new long[TIME_STAMP_INDEX_LAST];
-                            mHighBgBatteryPackages.put(uid, ts);
+                            usages = new ImmutableBatteryUsage[TIME_STAMP_INDEX_LAST];
+                            mHighBgBatteryPackages.put(uid, Pair.create(ts, usages));
                         }
                         ts[TIME_STAMP_INDEX_BG_RESTRICTED] = now;
+                        usages[TIME_STAMP_INDEX_BG_RESTRICTED] = usage;
                         notifyController = excessive = true;
                     }
                 } else {
-                    final long[] ts = mHighBgBatteryPackages.valueAt(index);
+                    final Pair<long[], ImmutableBatteryUsage[]> pair =
+                            mHighBgBatteryPackages.valueAt(index);
+                    final long[] ts = pair.first;
                     final long lastRestrictBucketTs = ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET];
                     if (rbPercentage >= rbThreshold) {
                         if (lastRestrictBucketTs == 0) {
                             ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = now;
+                            pair.second[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = usage;
                         }
                         notifyController = excessive = true;
                     } else {
@@ -1655,6 +1822,7 @@
                                 && (now > lastRestrictBucketTs + mBgCurrentDrainWindowMs));
                         if (notifyController) {
                             ts[TIME_STAMP_INDEX_BG_RESTRICTED] = now;
+                            pair.second[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = usage;
                         }
                         excessive = true;
                     } else {
@@ -1662,13 +1830,14 @@
                         // user consent to unrestrict it; or if it's in restricted bucket level,
                         // resetting this won't lift it from that level.
                         ts[TIME_STAMP_INDEX_BG_RESTRICTED] = 0;
+                        pair.second[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = null;
                         // Now need to notify the controller.
                     }
                 }
             }
 
             if (excessive) {
-                if (DEBUG_BACKGROUND_BATTERY_TRACKER) {
+                if (DEBUG_BACKGROUND_BATTERY_TRACKER_VERBOSE) {
                     Slog.i(TAG, "Excessive background current drain " + uid + " "
                             + usage + " (" + usage.percentageToString() + " ) over "
                             + TimeUtils.formatDuration(mBgCurrentDrainWindowMs));
@@ -1679,7 +1848,7 @@
                             REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE, true);
                 }
             } else {
-                if (DEBUG_BACKGROUND_BATTERY_TRACKER) {
+                if (DEBUG_BACKGROUND_BATTERY_TRACKER_VERBOSE && index >= 0) {
                     Slog.i(TAG, "Background current drain backs to normal " + uid + " "
                             + usage + " (" + usage.percentageToString() + " ) over "
                             + TimeUtils.formatDuration(mBgCurrentDrainWindowMs));
@@ -1749,9 +1918,10 @@
             synchronized (mLock) {
                 // User has explicitly removed it from background restricted level,
                 // clear the timestamp of the background-restricted
-                final long[] ts = mHighBgBatteryPackages.get(uid);
-                if (ts != null) {
-                    ts[TIME_STAMP_INDEX_BG_RESTRICTED] = 0;
+                final Pair<long[], ImmutableBatteryUsage[]> pair = mHighBgBatteryPackages.get(uid);
+                if (pair != null) {
+                    pair.first[TIME_STAMP_INDEX_BG_RESTRICTED] = 0;
+                    pair.second[TIME_STAMP_INDEX_BG_RESTRICTED] = null;
                 }
             }
         }
@@ -1826,6 +1996,10 @@
                 pw.print(KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_BY_BG_LOCATION);
                 pw.print('=');
                 pw.println(mBgCurrentDrainHighThresholdByBgLocation);
+                pw.print(prefix);
+                pw.print("Full charge capacity=");
+                pw.print(mBatteryFullChargeMah);
+                pw.println(" mAh");
 
                 pw.print(prefix);
                 pw.println("Excessive current drain detected:");
@@ -1836,18 +2010,24 @@
                         final long now = SystemClock.elapsedRealtime();
                         for (int i = 0; i < size; i++) {
                             final int uid = mHighBgBatteryPackages.keyAt(i);
-                            final long[] ts = mHighBgBatteryPackages.valueAt(i);
+                            final Pair<long[], ImmutableBatteryUsage[]> pair =
+                                    mHighBgBatteryPackages.valueAt(i);
+                            final long[] ts = pair.first;
+                            final ImmutableBatteryUsage[] usages = pair.second;
                             final int thresholdIndex = getCurrentDrainThresholdIndex(uid, now,
                                     mBgCurrentDrainWindowMs);
-                            pw.format("%s%s: (threshold=%4.2f%%/%4.2f%%) %s/%s\n",
+                            pw.format("%s%s: (threshold=%4.2f%%/%4.2f%%) %s / %s\n",
                                     prefix,
                                     UserHandle.formatUid(uid),
                                     mBgCurrentDrainRestrictedBucketThreshold[thresholdIndex],
                                     mBgCurrentDrainBgRestrictedThreshold[thresholdIndex],
-                                    ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET] == 0 ? "0"
-                                        : formatTime(ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET], now),
-                                    ts[TIME_STAMP_INDEX_BG_RESTRICTED] == 0 ? "0"
-                                        : formatTime(ts[TIME_STAMP_INDEX_BG_RESTRICTED], now));
+                                    formatHighBgBatteryRecord(
+                                            ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET], now,
+                                            usages[TIME_STAMP_INDEX_RESTRICTED_BUCKET]),
+                                    formatHighBgBatteryRecord(
+                                            ts[TIME_STAMP_INDEX_BG_RESTRICTED], now,
+                                            usages[TIME_STAMP_INDEX_BG_RESTRICTED])
+                            );
                         }
                     } else {
                         pw.print(prefix);
@@ -1856,5 +2036,14 @@
                 }
             }
         }
+
+        private String formatHighBgBatteryRecord(long ts, long now, ImmutableBatteryUsage usage) {
+            if (ts > 0 && usage != null) {
+                return String.format("%s %s (%s)",
+                        formatTime(ts, now), usage.toString(), usage.percentageToString());
+            } else {
+                return "0";
+            }
+        }
     }
 }
diff --git a/services/core/java/com/android/server/am/AppBindServiceEventsTracker.java b/services/core/java/com/android/server/am/AppBindServiceEventsTracker.java
index 9e3cae6..9bed077 100644
--- a/services/core/java/com/android/server/am/AppBindServiceEventsTracker.java
+++ b/services/core/java/com/android/server/am/AppBindServiceEventsTracker.java
@@ -26,6 +26,7 @@
 import android.content.Context;
 
 import com.android.server.am.AppBindServiceEventsTracker.AppBindServiceEventsPolicy;
+import com.android.server.am.AppRestrictionController.TrackerType;
 import com.android.server.am.BaseAppStateTimeSlotEventsTracker.SimpleAppStateTimeslotEvents;
 import com.android.server.am.BaseAppStateTracker.Injector;
 
@@ -59,6 +60,11 @@
     }
 
     @Override
+    @TrackerType int getType() {
+        return AppRestrictionController.TRACKER_TYPE_BIND_SERVICE_EVENTS;
+    }
+
+    @Override
     void onSystemReady() {
         super.onSystemReady();
         mInjector.getActivityManagerInternal().addBindServiceEventListener(this);
diff --git a/services/core/java/com/android/server/am/AppBroadcastEventsTracker.java b/services/core/java/com/android/server/am/AppBroadcastEventsTracker.java
index cafae40..a9155a1 100644
--- a/services/core/java/com/android/server/am/AppBroadcastEventsTracker.java
+++ b/services/core/java/com/android/server/am/AppBroadcastEventsTracker.java
@@ -26,6 +26,7 @@
 import android.content.Context;
 
 import com.android.server.am.AppBroadcastEventsTracker.AppBroadcastEventsPolicy;
+import com.android.server.am.AppRestrictionController.TrackerType;
 import com.android.server.am.BaseAppStateTimeSlotEventsTracker.SimpleAppStateTimeslotEvents;
 import com.android.server.am.BaseAppStateTracker.Injector;
 
@@ -58,6 +59,11 @@
     }
 
     @Override
+    @TrackerType int getType() {
+        return AppRestrictionController.TRACKER_TYPE_BROADCAST_EVENTS;
+    }
+
+    @Override
     void onSystemReady() {
         super.onSystemReady();
         mInjector.getActivityManagerInternal().addBroadcastEventListener(this);
diff --git a/services/core/java/com/android/server/am/AppFGSTracker.java b/services/core/java/com/android/server/am/AppFGSTracker.java
index 246725e..ddd2764 100644
--- a/services/core/java/com/android/server/am/AppFGSTracker.java
+++ b/services/core/java/com/android/server/am/AppFGSTracker.java
@@ -52,8 +52,10 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.SomeArgs;
 import com.android.server.am.AppFGSTracker.AppFGSPolicy;
 import com.android.server.am.AppFGSTracker.PackageDurations;
+import com.android.server.am.AppRestrictionController.TrackerType;
 import com.android.server.am.BaseAppStateEventsTracker.BaseAppStateEventsPolicy;
 import com.android.server.am.BaseAppStateTimeEvents.BaseTimeEvent;
 import com.android.server.am.BaseAppStateTracker.Injector;
@@ -111,9 +113,14 @@
 
     @Override
     public void onForegroundServiceNotificationUpdated(String packageName, int uid,
-            int foregroundId) {
-        mHandler.obtainMessage(MyHandler.MSG_FOREGROUND_SERVICES_NOTIFICATION_UPDATED,
-                uid, foregroundId, packageName).sendToTarget();
+            int foregroundId, boolean canceling) {
+        final SomeArgs args = SomeArgs.obtain();
+        args.argi1 = uid;
+        args.argi2 = foregroundId;
+        args.arg1 = packageName;
+        args.arg2 = canceling ? Boolean.TRUE : Boolean.FALSE;
+        mHandler.obtainMessage(MyHandler.MSG_FOREGROUND_SERVICES_NOTIFICATION_UPDATED, args)
+                .sendToTarget();
     }
 
     private static class MyHandler extends Handler {
@@ -148,8 +155,10 @@
                             (String) msg.obj, msg.arg1, msg.arg2);
                     break;
                 case MSG_FOREGROUND_SERVICES_NOTIFICATION_UPDATED:
+                    final SomeArgs args = (SomeArgs) msg.obj;
                     mTracker.handleForegroundServiceNotificationUpdated(
-                            (String) msg.obj, msg.arg1, msg.arg2);
+                            (String) args.arg1, args.argi1, args.argi2, (Boolean) args.arg2);
+                    args.recycle();
                     break;
                 case MSG_CHECK_LONG_RUNNING_FGS:
                     mTracker.checkLongRunningFgs();
@@ -176,6 +185,11 @@
     }
 
     @Override
+    @TrackerType int getType() {
+        return AppRestrictionController.TRACKER_TYPE_FGS;
+    }
+
+    @Override
     void onSystemReady() {
         super.onSystemReady();
         mInjector.getActivityManagerInternal().addForegroundServiceStateListener(this);
@@ -235,18 +249,18 @@
     }
 
     private void handleForegroundServiceNotificationUpdated(String packageName, int uid,
-            int notificationId) {
+            int notificationId, boolean canceling) {
         synchronized (mLock) {
             SparseBooleanArray notificationIDs = mFGSNotificationIDs.get(uid, packageName);
-            if (notificationId > 0) {
+            if (!canceling) {
                 if (notificationIDs == null) {
                     notificationIDs = new SparseBooleanArray();
                     mFGSNotificationIDs.put(uid, packageName, notificationIDs);
                 }
                 notificationIDs.put(notificationId, false);
-            } else if (notificationId < 0) {
+            } else {
                 if (notificationIDs != null) {
-                    final int indexOfKey = notificationIDs.indexOfKey(-notificationId);
+                    final int indexOfKey = notificationIDs.indexOfKey(notificationId);
                     if (indexOfKey >= 0) {
                         final boolean wasVisible = notificationIDs.valueAt(indexOfKey);
                         notificationIDs.removeAt(indexOfKey);
diff --git a/services/core/java/com/android/server/am/AppMediaSessionTracker.java b/services/core/java/com/android/server/am/AppMediaSessionTracker.java
index 4ce23f7..7daa93e 100644
--- a/services/core/java/com/android/server/am/AppMediaSessionTracker.java
+++ b/services/core/java/com/android/server/am/AppMediaSessionTracker.java
@@ -36,6 +36,7 @@
 
 import com.android.internal.app.ProcessMap;
 import com.android.server.am.AppMediaSessionTracker.AppMediaSessionPolicy;
+import com.android.server.am.AppRestrictionController.TrackerType;
 import com.android.server.am.BaseAppStateDurationsTracker.SimplePackageDurations;
 import com.android.server.am.BaseAppStateEventsTracker.BaseAppStateEventsPolicy;
 
@@ -161,6 +162,11 @@
     }
 
     @Override
+    @TrackerType int getType() {
+        return AppRestrictionController.TRACKER_TYPE_MEDIA_SESSION;
+    }
+
+    @Override
     void dump(PrintWriter pw, String prefix) {
         pw.print(prefix);
         pw.println("APP MEDIA SESSION TRACKER:");
diff --git a/services/core/java/com/android/server/am/AppPermissionTracker.java b/services/core/java/com/android/server/am/AppPermissionTracker.java
index 622d746..722d0d4 100644
--- a/services/core/java/com/android/server/am/AppPermissionTracker.java
+++ b/services/core/java/com/android/server/am/AppPermissionTracker.java
@@ -58,6 +58,7 @@
 import com.android.internal.app.IAppOpsCallback;
 import com.android.internal.app.IAppOpsService;
 import com.android.server.am.AppPermissionTracker.AppPermissionPolicy;
+import com.android.server.am.AppRestrictionController.TrackerType;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
 
 import java.io.PrintWriter;
@@ -104,6 +105,11 @@
     }
 
     @Override
+    @TrackerType int getType() {
+        return AppRestrictionController.TRACKER_TYPE_PERMISSION;
+    }
+
+    @Override
     public void onPermissionsChanged(int uid) {
         mHandler.obtainMessage(MyHandler.MSG_PERMISSIONS_CHANGED, uid, 0).sendToTarget();
     }
diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java
index c15deac..6f74359 100644
--- a/services/core/java/com/android/server/am/AppRestrictionController.java
+++ b/services/core/java/com/android/server/am/AppRestrictionController.java
@@ -27,6 +27,7 @@
 import static android.app.ActivityManager.RESTRICTION_LEVEL_MAX;
 import static android.app.ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET;
 import static android.app.ActivityManager.RESTRICTION_LEVEL_UNKNOWN;
+import static android.app.ActivityManager.RESTRICTION_LEVEL_UNRESTRICTED;
 import static android.app.ActivityManager.UID_OBSERVER_ACTIVE;
 import static android.app.ActivityManager.UID_OBSERVER_GONE;
 import static android.app.ActivityManager.UID_OBSERVER_IDLE;
@@ -70,6 +71,7 @@
 import static android.os.PowerExemptionManager.REASON_SYSTEM_ALLOW_LISTED;
 import static android.os.PowerExemptionManager.REASON_SYSTEM_MODULE;
 import static android.os.PowerExemptionManager.REASON_SYSTEM_UID;
+import static android.os.PowerExemptionManager.getExemptionReasonForStatsd;
 import static android.os.PowerExemptionManager.reasonCodeToString;
 import static android.os.Process.SYSTEM_UID;
 import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
@@ -114,12 +116,14 @@
 import android.database.ContentObserver;
 import android.graphics.drawable.Icon;
 import android.net.Uri;
+import android.os.Build;
 import android.os.Environment;
 import android.os.Handler;
 import android.os.HandlerExecutor;
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
+import android.os.PowerExemptionManager.ExemptionReason;
 import android.os.PowerExemptionManager.ReasonCode;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -134,6 +138,7 @@
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.AtomicFile;
+import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseArrayMap;
@@ -147,6 +152,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.function.TriConsumer;
 import com.android.server.AppStateTracker;
 import com.android.server.LocalServices;
@@ -297,6 +303,26 @@
 
     final ActivityManagerService mActivityManagerService;
 
+    static final int TRACKER_TYPE_UNKNOWN = 0;
+    static final int TRACKER_TYPE_BATTERY = 1;
+    static final int TRACKER_TYPE_BATTERY_EXEMPTION = 2;
+    static final int TRACKER_TYPE_FGS = 3;
+    static final int TRACKER_TYPE_MEDIA_SESSION = 4;
+    static final int TRACKER_TYPE_PERMISSION = 5;
+    static final int TRACKER_TYPE_BROADCAST_EVENTS = 6;
+    static final int TRACKER_TYPE_BIND_SERVICE_EVENTS = 7;
+    @IntDef(prefix = { "TRACKER_TYPE_" }, value = {
+            TRACKER_TYPE_UNKNOWN,
+            TRACKER_TYPE_BATTERY,
+            TRACKER_TYPE_BATTERY_EXEMPTION,
+            TRACKER_TYPE_FGS,
+            TRACKER_TYPE_MEDIA_SESSION,
+            TRACKER_TYPE_PERMISSION,
+            TRACKER_TYPE_BROADCAST_EVENTS,
+            TRACKER_TYPE_BIND_SERVICE_EVENTS,
+    })
+    @interface TrackerType {}
+
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -883,8 +909,8 @@
                 final int curBucket = mInjector.getAppStandbyInternal().getAppStandbyBucket(
                         packageName, UserHandle.getUserId(uid), now, false);
                 if (applyLevel) {
-                    applyRestrictionLevel(packageName, uid, curLevel, curBucket, true,
-                            reason & REASON_MAIN_MASK, reason & REASON_SUB_MASK);
+                    applyRestrictionLevel(packageName, uid, curLevel, TRACKER_TYPE_UNKNOWN,
+                            curBucket, true, reason & REASON_MAIN_MASK, reason & REASON_SUB_MASK);
                 } else {
                     pkgSettings.update(curLevel,
                             reason & REASON_MAIN_MASK, reason & REASON_SUB_MASK);
@@ -1413,11 +1439,13 @@
             refreshAppRestrictionLevelForUser(userId, REASON_MAIN_FORCED_BY_USER,
                     REASON_SUB_FORCED_USER_FLAG_INTERACTION);
         }
-        // Load the previously saved levels and update the current levels if needed.
-        mRestrictionSettings.scheduleLoadFromXml();
-        // Also save the current levels right away.
-        for (int userId : allUsers) {
-            mRestrictionSettings.schedulePersistToXml(userId);
+        if (!mInjector.isTest()) {
+            // Load the previously saved levels and update the current levels if needed.
+            mRestrictionSettings.scheduleLoadFromXml();
+            // Also save the current levels right away.
+            for (int userId : allUsers) {
+                mRestrictionSettings.schedulePersistToXml(userId);
+            }
         }
     }
 
@@ -1509,15 +1537,15 @@
                 Slog.e(TAG, "Unable to find " + info.mPackageName + "/u" + userId);
                 continue;
             }
-            final @RestrictionLevel int level = calcAppRestrictionLevel(
+            final Pair<Integer, Integer> levelTypePair = calcAppRestrictionLevel(
                     userId, uid, info.mPackageName, info.mStandbyBucket, false, false);
             if (DEBUG_BG_RESTRICTION_CONTROLLER) {
                 Slog.i(TAG, "Proposed restriction level of " + info.mPackageName + "/"
                         + UserHandle.formatUid(uid) + ": "
-                        + ActivityManager.restrictionLevelToName(level)
+                        + ActivityManager.restrictionLevelToName(levelTypePair.first)
                         + " " + info.mStandbyBucket);
             }
-            applyRestrictionLevel(info.mPackageName, uid, level,
+            applyRestrictionLevel(info.mPackageName, uid, levelTypePair.first, levelTypePair.second,
                     info.mStandbyBucket, true, reason, subReason);
         }
     }
@@ -1533,24 +1561,26 @@
         final long now = SystemClock.elapsedRealtime();
         for (String pkg: packages) {
             final int curBucket = appStandbyInternal.getAppStandbyBucket(pkg, userId, now, false);
-            final @RestrictionLevel int level = calcAppRestrictionLevel(userId, uid, pkg,
+            final Pair<Integer, Integer> levelTypePair = calcAppRestrictionLevel(userId, uid, pkg,
                     curBucket, allowRequestBgRestricted, true);
             if (DEBUG_BG_RESTRICTION_CONTROLLER) {
                 Slog.i(TAG, "Proposed restriction level of " + pkg + "/"
                         + UserHandle.formatUid(uid) + ": "
-                        + ActivityManager.restrictionLevelToName(level));
+                        + ActivityManager.restrictionLevelToName(levelTypePair.first));
             }
-            applyRestrictionLevel(pkg, uid, level, curBucket, true, reason, subReason);
+            applyRestrictionLevel(pkg, uid, levelTypePair.first, levelTypePair.second,
+                    curBucket, true, reason, subReason);
         }
     }
 
-    private @RestrictionLevel int calcAppRestrictionLevel(@UserIdInt int userId, int uid,
+    private Pair<Integer, Integer> calcAppRestrictionLevel(@UserIdInt int userId, int uid,
             String packageName, @UsageStatsManager.StandbyBuckets int standbyBucket,
             boolean allowRequestBgRestricted, boolean calcTrackers) {
         if (mInjector.getAppHibernationInternal().isHibernatingForUser(packageName, userId)) {
-            return RESTRICTION_LEVEL_HIBERNATION;
+            return new Pair<>(RESTRICTION_LEVEL_HIBERNATION, TRACKER_TYPE_UNKNOWN);
         }
         @RestrictionLevel int level;
+        @TrackerType int trackerType = TRACKER_TYPE_UNKNOWN;
         switch (standbyBucket) {
             case STANDBY_BUCKET_EXEMPTED:
                 level = RESTRICTION_LEVEL_EXEMPTED;
@@ -1566,19 +1596,23 @@
             default:
                 if (mInjector.getAppStateTracker()
                         .isAppBackgroundRestricted(uid, packageName)) {
-                    return RESTRICTION_LEVEL_BACKGROUND_RESTRICTED;
+                    return new Pair<>(RESTRICTION_LEVEL_BACKGROUND_RESTRICTED, trackerType);
                 }
                 level = mConstantsObserver.mRestrictedBucketEnabled
                         && standbyBucket == STANDBY_BUCKET_RESTRICTED
                         ? RESTRICTION_LEVEL_RESTRICTED_BUCKET
                         : RESTRICTION_LEVEL_ADAPTIVE_BUCKET;
                 if (calcTrackers) {
-                    @RestrictionLevel int l = calcAppRestrictionLevelFromTackers(uid, packageName,
-                            RESTRICTION_LEVEL_MAX);
+                    Pair<Integer, Integer> levelTypePair = calcAppRestrictionLevelFromTackers(
+                            uid, packageName, RESTRICTION_LEVEL_MAX);
+                    @RestrictionLevel int l = levelTypePair.first;
                     if (l == RESTRICTION_LEVEL_EXEMPTED) {
-                        return RESTRICTION_LEVEL_EXEMPTED;
+                        return new Pair<>(RESTRICTION_LEVEL_EXEMPTED, levelTypePair.second);
                     }
                     level = Math.max(l, level);
+                    if (l == level) {
+                        trackerType = levelTypePair.second;
+                    }
                     if (level == RESTRICTION_LEVEL_BACKGROUND_RESTRICTED) {
                         // This level can't be entered without user consent
                         if (allowRequestBgRestricted) {
@@ -1586,27 +1620,32 @@
                                     uid, 0, packageName).sendToTarget();
                         }
                         // Lower the level.
-                        level = calcAppRestrictionLevelFromTackers(uid, packageName,
+                        levelTypePair = calcAppRestrictionLevelFromTackers(uid, packageName,
                                 RESTRICTION_LEVEL_BACKGROUND_RESTRICTED);
+                        level = levelTypePair.first;
+                        trackerType = levelTypePair.second;
                     }
                 }
                 break;
         }
-        return level;
+        return new Pair<>(level, trackerType);
     }
 
     /**
      * Ask each of the trackers for their proposed restriction levels for the given uid/package,
-     * and return the most restrictive level.
+     * and return the most restrictive level along with the type of tracker which applied this
+     * restriction level as a {@code Pair<@RestrictionLevel, @TrackerType>}.
      *
      * <p>Note, it's different from the {@link #getRestrictionLevel} where it returns the least
      * restrictive level. We're returning the most restrictive level here because each tracker
      * monitors certain dimensions of the app, the abusive behaviors could be detected in one or
      * more of these dimensions, but not necessarily all of them. </p>
      */
-    private @RestrictionLevel int calcAppRestrictionLevelFromTackers(int uid, String packageName,
+    private Pair<Integer, Integer> calcAppRestrictionLevelFromTackers(int uid, String packageName,
             @RestrictionLevel int maxLevel) {
         @RestrictionLevel int level = RESTRICTION_LEVEL_UNKNOWN;
+        @RestrictionLevel int prevLevel = level;
+        @TrackerType int trackerType = TRACKER_TYPE_UNKNOWN;
         final boolean isRestrictedBucketEnabled = mConstantsObserver.mRestrictedBucketEnabled;
         for (int i = mAppStateTrackers.size() - 1; i >= 0; i--) {
             @RestrictionLevel int l = mAppStateTrackers.get(i).getPolicy()
@@ -1615,8 +1654,12 @@
                 l = RESTRICTION_LEVEL_ADAPTIVE_BUCKET;
             }
             level = Math.max(level, l);
+            if (level != prevLevel) {
+                trackerType = mAppStateTrackers.get(i).getType();
+                prevLevel = level;
+            }
         }
-        return level;
+        return new Pair<>(level, trackerType);
     }
 
     private static @RestrictionLevel int standbyBucketToRestrictionLevel(
@@ -1829,7 +1872,129 @@
         }
     }
 
-    private void applyRestrictionLevel(String pkgName, int uid, @RestrictionLevel int level,
+    private int getRestrictionLevelStatsd(@RestrictionLevel int level) {
+        switch (level) {
+            case RESTRICTION_LEVEL_UNKNOWN:
+                return FrameworkStatsLog
+                        .APP_BACKGROUND_RESTRICTIONS_INFO__RESTRICTION_LEVEL__LEVEL_UNKNOWN;
+            case RESTRICTION_LEVEL_UNRESTRICTED:
+                return FrameworkStatsLog
+                        .APP_BACKGROUND_RESTRICTIONS_INFO__RESTRICTION_LEVEL__LEVEL_UNRESTRICTED;
+            case RESTRICTION_LEVEL_EXEMPTED:
+                return FrameworkStatsLog
+                        .APP_BACKGROUND_RESTRICTIONS_INFO__RESTRICTION_LEVEL__LEVEL_EXEMPTED;
+            case RESTRICTION_LEVEL_ADAPTIVE_BUCKET:
+                return FrameworkStatsLog
+                        .APP_BACKGROUND_RESTRICTIONS_INFO__RESTRICTION_LEVEL__LEVEL_ADAPTIVE_BUCKET;
+            case RESTRICTION_LEVEL_RESTRICTED_BUCKET:
+                return FrameworkStatsLog
+                        .APP_BACKGROUND_RESTRICTIONS_INFO__RESTRICTION_LEVEL__LEVEL_RESTRICTED_BUCKET;
+            case RESTRICTION_LEVEL_BACKGROUND_RESTRICTED:
+                return FrameworkStatsLog
+                        .APP_BACKGROUND_RESTRICTIONS_INFO__RESTRICTION_LEVEL__LEVEL_BACKGROUND_RESTRICTED;
+            case RESTRICTION_LEVEL_HIBERNATION:
+                return FrameworkStatsLog
+                        .APP_BACKGROUND_RESTRICTIONS_INFO__RESTRICTION_LEVEL__LEVEL_HIBERNATION;
+            default:
+                return FrameworkStatsLog
+                        .APP_BACKGROUND_RESTRICTIONS_INFO__RESTRICTION_LEVEL__LEVEL_UNKNOWN;
+        }
+    }
+
+    private int getThresholdStatsd(int reason) {
+        switch (reason) {
+            case REASON_MAIN_FORCED_BY_SYSTEM:
+                return FrameworkStatsLog
+                        .APP_BACKGROUND_RESTRICTIONS_INFO__THRESHOLD__THRESHOLD_RESTRICTED;
+            case REASON_MAIN_FORCED_BY_USER:
+                return FrameworkStatsLog
+                        .APP_BACKGROUND_RESTRICTIONS_INFO__THRESHOLD__THRESHOLD_USER;
+            default:
+                return FrameworkStatsLog
+                        .APP_BACKGROUND_RESTRICTIONS_INFO__THRESHOLD__THRESHOLD_UNKNOWN;
+        }
+    }
+
+    private int getTrackerTypeStatsd(@TrackerType int type) {
+        switch (type) {
+            case TRACKER_TYPE_BATTERY:
+                return FrameworkStatsLog.APP_BACKGROUND_RESTRICTIONS_INFO__TRACKER__BATTERY_TRACKER;
+            case TRACKER_TYPE_BATTERY_EXEMPTION:
+                return FrameworkStatsLog
+                        .APP_BACKGROUND_RESTRICTIONS_INFO__TRACKER__BATTERY_EXEMPTION_TRACKER;
+            case TRACKER_TYPE_FGS:
+                return FrameworkStatsLog.APP_BACKGROUND_RESTRICTIONS_INFO__TRACKER__FGS_TRACKER;
+            case TRACKER_TYPE_MEDIA_SESSION:
+                return FrameworkStatsLog
+                        .APP_BACKGROUND_RESTRICTIONS_INFO__TRACKER__MEDIA_SESSION_TRACKER;
+            case TRACKER_TYPE_PERMISSION:
+                return FrameworkStatsLog
+                        .APP_BACKGROUND_RESTRICTIONS_INFO__TRACKER__PERMISSION_TRACKER;
+            case TRACKER_TYPE_BROADCAST_EVENTS:
+                return FrameworkStatsLog
+                        .APP_BACKGROUND_RESTRICTIONS_INFO__TRACKER__BROADCAST_EVENTS_TRACKER;
+            case TRACKER_TYPE_BIND_SERVICE_EVENTS:
+                return FrameworkStatsLog
+                        .APP_BACKGROUND_RESTRICTIONS_INFO__TRACKER__BIND_SERVICE_EVENTS_TRACKER;
+            default:
+                return FrameworkStatsLog.APP_BACKGROUND_RESTRICTIONS_INFO__TRACKER__UNKNOWN_TRACKER;
+        }
+    }
+
+    private @ExemptionReason int getExemptionReasonStatsd(int uid, @RestrictionLevel int level) {
+        if (level != RESTRICTION_LEVEL_EXEMPTED) {
+            return FrameworkStatsLog
+                    .APP_BACKGROUND_RESTRICTIONS_INFO__EXEMPTION_REASON__REASON_DENIED;
+        }
+
+        @ReasonCode final int reasonCode = getBackgroundRestrictionExemptionReason(uid);
+        return getExemptionReasonForStatsd(reasonCode);
+    }
+
+    private int getOptimizationLevelStatsd(@RestrictionLevel int level) {
+        switch (level) {
+            case RESTRICTION_LEVEL_UNKNOWN:
+                return FrameworkStatsLog.APP_BACKGROUND_RESTRICTIONS_INFO__OPT_LEVEL__UNKNOWN;
+            case RESTRICTION_LEVEL_UNRESTRICTED:
+                return FrameworkStatsLog.APP_BACKGROUND_RESTRICTIONS_INFO__OPT_LEVEL__NOT_OPTIMIZED;
+            case RESTRICTION_LEVEL_ADAPTIVE_BUCKET:
+                return FrameworkStatsLog.APP_BACKGROUND_RESTRICTIONS_INFO__OPT_LEVEL__OPTIMIZED;
+            case RESTRICTION_LEVEL_BACKGROUND_RESTRICTED:
+                return FrameworkStatsLog
+                        .APP_BACKGROUND_RESTRICTIONS_INFO__OPT_LEVEL__BACKGROUND_RESTRICTED;
+            default:
+                return FrameworkStatsLog.APP_BACKGROUND_RESTRICTIONS_INFO__OPT_LEVEL__UNKNOWN;
+        }
+    }
+
+    @SuppressWarnings("AndroidFrameworkCompatChange")
+    private int getTargetSdkStatsd(String packageName) {
+        final PackageManager pm = mInjector.getPackageManager();
+        if (pm == null) {
+            return FrameworkStatsLog.APP_BACKGROUND_RESTRICTIONS_INFO__TARGET_SDK__SDK_UNKNOWN;
+        }
+        try {
+            final PackageInfo pkg = pm.getPackageInfo(packageName, 0 /* flags */);
+            if (pkg == null || pkg.applicationInfo == null) {
+                return FrameworkStatsLog.APP_BACKGROUND_RESTRICTIONS_INFO__TARGET_SDK__SDK_UNKNOWN;
+            }
+            final int targetSdk = pkg.applicationInfo.targetSdkVersion;
+            if (targetSdk < Build.VERSION_CODES.S) {
+                return FrameworkStatsLog.APP_BACKGROUND_RESTRICTIONS_INFO__TARGET_SDK__SDK_PRE_S;
+            } else if (targetSdk < Build.VERSION_CODES.TIRAMISU) {
+                return FrameworkStatsLog.APP_BACKGROUND_RESTRICTIONS_INFO__TARGET_SDK__SDK_S;
+            } else if (targetSdk == Build.VERSION_CODES.TIRAMISU) {
+                return FrameworkStatsLog.APP_BACKGROUND_RESTRICTIONS_INFO__TARGET_SDK__SDK_T;
+            } else {
+                return FrameworkStatsLog.APP_BACKGROUND_RESTRICTIONS_INFO__TARGET_SDK__SDK_UNKNOWN;
+            }
+        } catch (PackageManager.NameNotFoundException ignored) {
+        }
+        return FrameworkStatsLog.APP_BACKGROUND_RESTRICTIONS_INFO__TARGET_SDK__SDK_UNKNOWN;
+    }
+
+    private void applyRestrictionLevel(String pkgName, int uid,
+            @RestrictionLevel int level, @TrackerType int trackerType,
             int curBucket, boolean allowUpdateBucket, int reason, int subReason) {
         int curLevel;
         int prevReason;
@@ -1909,6 +2074,19 @@
                     prevReason & REASON_MAIN_MASK, prevReason & REASON_SUB_MASK,
                     reason, subReason);
         }
+
+        FrameworkStatsLog.write(FrameworkStatsLog.APP_BACKGROUND_RESTRICTIONS_INFO, uid,
+                getRestrictionLevelStatsd(level),
+                getThresholdStatsd(reason),
+                getTrackerTypeStatsd(trackerType),
+                null, // FgsTrackerInfo
+                null, // BatteryTrackerInfo
+                null, // BroadcastEventsTrackerInfo
+                null, // BindServiceEventsTrackerInfo
+                getExemptionReasonStatsd(uid, level),
+                getOptimizationLevelStatsd(level),
+                getTargetSdkStatsd(pkgName),
+                ActivityManager.isLowRamDeviceStatic());
     }
 
     private void handleBackgroundRestrictionChanged(int uid, String pkgName, boolean restricted) {
@@ -1926,7 +2104,7 @@
             // The app could fall into the background restricted with user consent only,
             // so set the reason to it.
             applyRestrictionLevel(pkgName, uid, RESTRICTION_LEVEL_BACKGROUND_RESTRICTED,
-                    curBucket, true, REASON_MAIN_FORCED_BY_USER,
+                    TRACKER_TYPE_UNKNOWN, curBucket, true, REASON_MAIN_FORCED_BY_USER,
                     REASON_SUB_FORCED_USER_FLAG_INTERACTION);
             mBgHandler.obtainMessage(BgHandler.MSG_CANCEL_REQUEST_BG_RESTRICTED, uid, 0, pkgName)
                     .sendToTarget();
@@ -1939,11 +2117,11 @@
                     ? STANDBY_BUCKET_EXEMPTED
                     : (lastLevel == RESTRICTION_LEVEL_RESTRICTED_BUCKET
                             ? STANDBY_BUCKET_RESTRICTED : STANDBY_BUCKET_RARE);
-            final @RestrictionLevel int level = calcAppRestrictionLevel(
+            final Pair<Integer, Integer> levelTypePair = calcAppRestrictionLevel(
                     UserHandle.getUserId(uid), uid, pkgName, tentativeBucket, false, true);
 
-            applyRestrictionLevel(pkgName, uid, level, curBucket, true,
-                    REASON_MAIN_USAGE, REASON_SUB_USAGE_USER_INTERACTION);
+            applyRestrictionLevel(pkgName, uid, levelTypePair.first, levelTypePair.second,
+                    curBucket, true, REASON_MAIN_USAGE, REASON_SUB_USAGE_USER_INTERACTION);
         }
     }
 
@@ -1983,10 +2161,10 @@
             @UserIdInt int userId) {
         final int uid = mInjector.getPackageManagerInternal().getPackageUid(
                 packageName, STOCK_PM_FLAGS, userId);
-        final @RestrictionLevel int level = calcAppRestrictionLevel(
+        final Pair<Integer, Integer> levelTypePair = calcAppRestrictionLevel(
                 userId, uid, packageName, bucket, false, false);
-        applyRestrictionLevel(packageName, uid, level, bucket, false,
-                REASON_MAIN_DEFAULT, REASON_SUB_DEFAULT_UNDEFINED);
+        applyRestrictionLevel(packageName, uid, levelTypePair.first, levelTypePair.second,
+                bucket, false, REASON_MAIN_DEFAULT, REASON_SUB_DEFAULT_UNDEFINED);
     }
 
     void handleRequestBgRestricted(String packageName, int uid) {
@@ -2822,6 +3000,10 @@
         @CurrentTimeMillisLong long currentTimeMillis() {
             return System.currentTimeMillis();
         }
+
+        boolean isTest() {
+            return false;
+        }
     }
 
     private void registerForSystemBroadcasts() {
diff --git a/services/core/java/com/android/server/am/BaseAppStateTracker.java b/services/core/java/com/android/server/am/BaseAppStateTracker.java
index cb21a4b..5afceca 100644
--- a/services/core/java/com/android/server/am/BaseAppStateTracker.java
+++ b/services/core/java/com/android/server/am/BaseAppStateTracker.java
@@ -40,6 +40,7 @@
 import com.android.internal.app.IAppOpsService;
 import com.android.server.DeviceIdleInternal;
 import com.android.server.LocalServices;
+import com.android.server.am.AppRestrictionController.TrackerType;
 import com.android.server.notification.NotificationManagerInternal;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
@@ -161,6 +162,13 @@
     }
 
     /**
+     * Return the type of tracker (as defined by AppRestrictionController.TrackerType)
+     */
+    @TrackerType int getType() {
+        return AppRestrictionController.TRACKER_TYPE_UNKNOWN;
+    }
+
+    /**
      * Return the policy holder of this tracker.
      */
     T getPolicy() {
diff --git a/services/core/java/com/android/server/am/DropboxRateLimiter.java b/services/core/java/com/android/server/am/DropboxRateLimiter.java
new file mode 100644
index 0000000..c517023
--- /dev/null
+++ b/services/core/java/com/android/server/am/DropboxRateLimiter.java
@@ -0,0 +1,125 @@
+/*
+ * 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.am;
+
+import android.os.SystemClock;
+import android.text.format.DateUtils;
+import android.util.ArrayMap;
+
+import com.android.internal.annotations.GuardedBy;
+
+/** Rate limiter for adding errors into dropbox. */
+public class DropboxRateLimiter {
+    private static final long RATE_LIMIT_BUFFER_EXPIRY = 15 * DateUtils.SECOND_IN_MILLIS;
+    private static final long RATE_LIMIT_BUFFER_DURATION = 10 * DateUtils.SECOND_IN_MILLIS;
+    private static final int RATE_LIMIT_ALLOWED_ENTRIES = 5;
+
+    @GuardedBy("mErrorClusterRecords")
+    private final ArrayMap<String, ErrorRecord> mErrorClusterRecords = new ArrayMap<>();
+    private final Clock mClock;
+
+    private long mLastMapCleanUp = 0L;
+
+    public DropboxRateLimiter() {
+        this(new DefaultClock());
+    }
+
+    public DropboxRateLimiter(Clock clock) {
+        mClock = clock;
+    }
+
+    /** The interface clock to use for tracking the time elapsed. */
+    public interface Clock {
+        /** How long in millis has passed since the device came online. */
+        long uptimeMillis();
+    }
+
+    /** Determines whether dropbox entries of a specific tag and process should be rate limited. */
+    public boolean shouldRateLimit(String eventType, String processName) {
+        // Rate-limit how often we're willing to do the heavy lifting to collect and record logs.
+        final long now = mClock.uptimeMillis();
+        synchronized (mErrorClusterRecords) {
+            // Remove expired records if enough time has passed since the last cleanup.
+            maybeRemoveExpiredRecords(now);
+
+            ErrorRecord errRecord = mErrorClusterRecords.get(errorKey(eventType, processName));
+            if (errRecord == null) {
+                errRecord = new ErrorRecord(now, 1);
+                mErrorClusterRecords.put(errorKey(eventType, processName), errRecord);
+            } else if (now - errRecord.getStartTime() > RATE_LIMIT_BUFFER_DURATION) {
+                errRecord.setStartTime(now);
+                errRecord.setCount(1);
+            } else {
+                errRecord.incrementCount();
+                if (errRecord.getCount() > RATE_LIMIT_ALLOWED_ENTRIES) return true;
+            }
+        }
+        return false;
+    }
+
+    private void maybeRemoveExpiredRecords(long now) {
+        if (now - mLastMapCleanUp <= RATE_LIMIT_BUFFER_EXPIRY) return;
+
+        for (int i = mErrorClusterRecords.size() - 1; i >= 0; i--) {
+            if (now - mErrorClusterRecords.valueAt(i).getStartTime() > RATE_LIMIT_BUFFER_EXPIRY) {
+                mErrorClusterRecords.removeAt(i);
+            }
+        }
+
+        mLastMapCleanUp = now;
+    }
+
+    String errorKey(String eventType, String processName) {
+        return eventType + processName;
+    }
+
+    private class ErrorRecord {
+        long mStartTime;
+        int mCount;
+
+        ErrorRecord(long startTime, int count) {
+            mStartTime = startTime;
+            mCount = count;
+        }
+
+        public void setStartTime(long startTime) {
+            mStartTime = startTime;
+        }
+
+        public void setCount(int count) {
+            mCount = count;
+        }
+
+        public void incrementCount() {
+            mCount++;
+        }
+
+        public long getStartTime() {
+            return mStartTime;
+        }
+
+        public int getCount() {
+            return mCount;
+        }
+    }
+
+    private static class DefaultClock implements Clock {
+        public long uptimeMillis() {
+            return SystemClock.uptimeMillis();
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 6629a30..bad7782 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -423,9 +423,8 @@
      * Having a global counter ensures that seq numbers are monotonically increasing for a
      * particular uid even when the uidRecord is re-created.
      */
-    @GuardedBy("mService")
     @VisibleForTesting
-    long mProcStateSeqCounter = 0;
+    volatile long mProcStateSeqCounter = 0;
 
     /**
      * A global counter for generating sequence numbers to uniquely identify pending process starts.
@@ -4860,6 +4859,10 @@
         }
     }
 
+    long getProcStateSeqCounter() {
+        return mProcStateSeqCounter;
+    }
+
     /**
      * Create a server socket in system_server, zygote will connect to it
      * in order to send unsolicited messages to system_server.
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 639f56c..5a55b8b 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -1112,7 +1112,7 @@
                         foregroundNoti = localForegroundNoti; // save it for amending next time
 
                         signalForegroundServiceNotification(packageName, appInfo.uid,
-                                localForegroundId);
+                                localForegroundId, false /* canceling */);
 
                     } catch (RuntimeException e) {
                         Slog.w(TAG, "Error showing notification for service", e);
@@ -1147,17 +1147,18 @@
                 } catch (RuntimeException e) {
                     Slog.w(TAG, "Error canceling notification for service", e);
                 }
-                signalForegroundServiceNotification(packageName, appInfo.uid, -localForegroundId);
+                signalForegroundServiceNotification(packageName, appInfo.uid, localForegroundId,
+                        true /* canceling */);
             }
         });
     }
 
     private void signalForegroundServiceNotification(String packageName, int uid,
-            int foregroundId) {
+            int foregroundId, boolean canceling) {
         synchronized (ams) {
             for (int i = ams.mForegroundServiceStateListeners.size() - 1; i >= 0; i--) {
                 ams.mForegroundServiceStateListeners.get(i).onForegroundServiceNotificationUpdated(
-                        packageName, appInfo.uid, foregroundId);
+                        packageName, appInfo.uid, foregroundId, canceling);
             }
         }
     }
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 361629b..752e17e 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -2367,7 +2367,8 @@
         ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class);
         boolean isSelfRequest = (filter & FILTER_BY_UID) != 0 && uid == Binder.getCallingUid();
         if (!isSelfRequest) {
-            boolean isCallerInstrumented = ami.isUidCurrentlyInstrumented(Binder.getCallingUid());
+            boolean isCallerInstrumented =
+                    ami.getInstrumentationSourceUid(Binder.getCallingUid()) != Process.INVALID_UID;
             boolean isCallerSystem = Binder.getCallingPid() == Process.myPid();
             boolean isCallerPermissionController;
             try {
@@ -6894,7 +6895,8 @@
     @Override
     public @Nullable RuntimeAppOpAccessMessage collectRuntimeAppOpAccessMessage() {
         ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class);
-        boolean isCallerInstrumented = ami.isUidCurrentlyInstrumented(Binder.getCallingUid());
+        boolean isCallerInstrumented =
+                ami.getInstrumentationSourceUid(Binder.getCallingUid()) != Process.INVALID_UID;
         boolean isCallerSystem = Binder.getCallingPid() == Process.myPid();
         if (!isCallerSystem && !isCallerInstrumented) {
             return null;
diff --git a/services/core/java/com/android/server/audio/AudioEventLogger.java b/services/core/java/com/android/server/audio/AudioEventLogger.java
index af0e978..259990c 100644
--- a/services/core/java/com/android/server/audio/AudioEventLogger.java
+++ b/services/core/java/com/android/server/audio/AudioEventLogger.java
@@ -16,9 +16,12 @@
 
 package com.android.server.audio;
 
+import android.annotation.IntDef;
 import android.util.Log;
 
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.text.SimpleDateFormat;
 import java.util.Date;
 import java.util.LinkedList;
@@ -63,6 +66,16 @@
             return printLog(ALOGI, tag);
         }
 
+        /** @hide */
+        @IntDef(flag = false, value = {
+                ALOGI,
+                ALOGE,
+                ALOGW,
+                ALOGV }
+        )
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface LogType {}
+
         public static final int ALOGI = 0;
         public static final int ALOGE = 1;
         public static final int ALOGW = 2;
@@ -74,7 +87,7 @@
          * @param tag
          * @return
          */
-        public Event printLog(int type, String tag) {
+        public Event printLog(@LogType int type, String tag) {
             switch (type) {
                 case ALOGI:
                     Log.i(tag, eventToString());
@@ -135,6 +148,27 @@
         mEvents.add(evt);
     }
 
+    /**
+     * Add a string-based event to the log, and print it to logcat as info.
+     * @param msg the message for the logs
+     * @param tag the logcat tag to use
+     */
+    public synchronized void loglogi(String msg, String tag) {
+        final Event event = new StringEvent(msg);
+        log(event.printLog(tag));
+    }
+
+    /**
+     * Same as {@link #loglogi(String, String)} but specifying the logcat type
+     * @param msg the message for the logs
+     * @param logType the type of logcat entry
+     * @param tag the logcat tag to use
+     */
+    public synchronized void loglog(String msg, @Event.LogType int logType, String tag) {
+        final Event event = new StringEvent(msg);
+        log(event.printLog(logType, tag));
+    }
+
     public synchronized void dump(PrintWriter pw) {
         pw.println("Audio event log: " + mTitle);
         for (Event evt : mEvents) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 517ff82..465e5e9 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -340,7 +340,8 @@
     private static final int MSG_DISPATCH_AUDIO_MODE = 40;
     private static final int MSG_ROUTING_UPDATED = 41;
     private static final int MSG_INIT_HEADTRACKING_SENSORS = 42;
-    private static final int MSG_PERSIST_SPATIAL_AUDIO_ENABLED = 43;
+    // commented out for now, will be reused for other SA persisting
+    //private static final int MSG_PERSIST_SPATIAL_AUDIO_ENABLED = 43;
     private static final int MSG_ADD_ASSISTANT_SERVICE_UID = 44;
     private static final int MSG_REMOVE_ASSISTANT_SERVICE_UID = 45;
     private static final int MSG_UPDATE_ACTIVE_ASSISTANT_SERVICE_UID = 46;
@@ -1543,9 +1544,7 @@
             }
         }
 
-        if (mHasSpatializerEffect) {
-            mSpatializerHelper.reset(/* featureEnabled */ isSpatialAudioEnabled());
-        }
+        mSpatializerHelper.reset(/* featureEnabled */ mHasSpatializerEffect);
 
         onIndicateSystemReady();
         // indicate the end of reconfiguration phase to audio HAL
@@ -8114,9 +8113,7 @@
 
                 case MSG_INIT_SPATIALIZER:
                     mSpatializerHelper.init(/*effectExpected*/ mHasSpatializerEffect);
-                    if (mHasSpatializerEffect) {
-                        mSpatializerHelper.setFeatureEnabled(isSpatialAudioEnabled());
-                    }
+                    mSpatializerHelper.setFeatureEnabled(mHasSpatializerEffect);
                     mAudioEventWakeLock.release();
                     break;
 
@@ -8257,10 +8254,6 @@
                     onRoutingUpdatedFromAudioThread();
                     break;
 
-                case MSG_PERSIST_SPATIAL_AUDIO_ENABLED:
-                    onPersistSpatialAudioEnabled(msg.arg1 == 1);
-                    break;
-
                 case MSG_ADD_ASSISTANT_SERVICE_UID:
                     onAddAssistantServiceUids(new int[]{msg.arg1});
                     break;
@@ -8864,31 +8857,6 @@
      */
     private static final boolean SPATIAL_AUDIO_ENABLED_DEFAULT = true;
 
-    /**
-     * persist in user settings whether the feature is enabled.
-     * Can change when {@link Spatializer#setEnabled(boolean)} is called and successfully
-     * changes the state of the feature
-     * @param featureEnabled
-     */
-    void persistSpatialAudioEnabled(boolean featureEnabled) {
-        sendMsg(mAudioHandler,
-                MSG_PERSIST_SPATIAL_AUDIO_ENABLED,
-                SENDMSG_REPLACE, featureEnabled ? 1 : 0, 0, null,
-                /*delay ms*/ 100);
-    }
-
-    void onPersistSpatialAudioEnabled(boolean enabled) {
-        mSettings.putSecureIntForUser(mContentResolver,
-                Settings.Secure.SPATIAL_AUDIO_ENABLED, enabled ? 1 : 0,
-                UserHandle.USER_CURRENT);
-    }
-
-    boolean isSpatialAudioEnabled() {
-        return mSettings.getSecureIntForUser(mContentResolver,
-                Settings.Secure.SPATIAL_AUDIO_ENABLED, SPATIAL_AUDIO_ENABLED_DEFAULT ? 1 : 0,
-                UserHandle.USER_CURRENT) == 1;
-    }
-
     private void enforceModifyDefaultAudioEffectsPermission() {
         if (mContext.checkCallingOrSelfPermission(
                 android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
@@ -9748,6 +9716,7 @@
     static final int LOG_NB_EVENTS_FORCE_USE = 20;
     static final int LOG_NB_EVENTS_VOLUME = 40;
     static final int LOG_NB_EVENTS_DYN_POLICY = 10;
+    static final int LOG_NB_EVENTS_SPATIAL = 30;
 
     static final AudioEventLogger sLifecycleLogger = new AudioEventLogger(LOG_NB_EVENTS_LIFECYCLE,
             "audio services lifecycle");
@@ -9768,6 +9737,9 @@
     static final AudioEventLogger sVolumeLogger = new AudioEventLogger(LOG_NB_EVENTS_VOLUME,
             "volume changes (logged when command received by AudioService)");
 
+    static final AudioEventLogger sSpatialLogger = new AudioEventLogger(LOG_NB_EVENTS_SPATIAL,
+            "spatial audio");
+
     final private AudioEventLogger mDynPolicyLogger = new AudioEventLogger(LOG_NB_EVENTS_DYN_POLICY,
             "dynamic policy events (logged when command received by AudioService)");
 
@@ -9906,10 +9878,10 @@
 
         pw.println("\n");
         pw.println("\nSpatial audio:");
-        pw.println("mHasSpatializerEffect:" + mHasSpatializerEffect);
-        pw.println("isSpatializerEnabled:" + isSpatializerEnabled());
-        pw.println("isSpatialAudioEnabled:" + isSpatialAudioEnabled());
+        pw.println("mHasSpatializerEffect:" + mHasSpatializerEffect + " (effect present)");
+        pw.println("isSpatializerEnabled:" + isSpatializerEnabled() + " (routing dependent)");
         mSpatializerHelper.dump(pw);
+        sSpatialLogger.dump(pw);
 
         mAudioSystem.dump(pw);
     }
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 40a6350..04fcda7 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -177,20 +177,20 @@
     }
 
     synchronized void init(boolean effectExpected) {
-        Log.i(TAG, "Initializing");
+        loglogi("init effectExpected=" + effectExpected);
         if (!effectExpected) {
-            Log.i(TAG, "Setting state to STATE_NOT_SUPPORTED due to effect not expected");
+            loglogi("init(): setting state to STATE_NOT_SUPPORTED due to effect not expected");
             mState = STATE_NOT_SUPPORTED;
             return;
         }
         if (mState != STATE_UNINITIALIZED) {
-            throw new IllegalStateException(("init() called in state:" + mState));
+            throw new IllegalStateException(logloge("init() called in state " + mState));
         }
         // is there a spatializer?
         mSpatCallback = new SpatializerCallback();
         final ISpatializer spat = AudioSystem.getSpatializer(mSpatCallback);
         if (spat == null) {
-            Log.i(TAG, "init(): No Spatializer found");
+            loglogi("init(): No Spatializer found");
             mState = STATE_NOT_SUPPORTED;
             return;
         }
@@ -201,14 +201,14 @@
                     || levels.length == 0
                     || (levels.length == 1
                     && levels[0] == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE)) {
-                Log.e(TAG, "Spatializer is useless");
+                logloge("init(): found Spatializer is useless");
                 mState = STATE_NOT_SUPPORTED;
                 return;
             }
             for (byte level : levels) {
-                logd("found support for level: " + level);
+                loglogi("init(): found support for level: " + level);
                 if (level == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL) {
-                    logd("Setting capable level to LEVEL_MULTICHANNEL");
+                    loglogi("init(): setting capable level to LEVEL_MULTICHANNEL");
                     mCapableSpatLevel = level;
                     break;
                 }
@@ -223,7 +223,7 @@
                         mTransauralSupported = true;
                         break;
                     default:
-                        Log.e(TAG, "Spatializer reports unknown supported mode:" + mode);
+                        logloge("init(): Spatializer reports unknown supported mode:" + mode);
                         break;
                 }
             }
@@ -277,7 +277,7 @@
      * @param featureEnabled
      */
     synchronized void reset(boolean featureEnabled) {
-        Log.i(TAG, "Resetting");
+        loglogi("Resetting featureEnabled=" + featureEnabled);
         releaseSpat();
         mState = STATE_UNINITIALIZED;
         mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
@@ -318,23 +318,38 @@
         if (enabledAvailable.second) {
             // available for Spatial audio, check w/ effect
             able = canBeSpatializedOnDevice(DEFAULT_ATTRIBUTES, DEFAULT_FORMAT, ROUTING_DEVICES);
-            Log.i(TAG, "onRoutingUpdated: can spatialize media 5.1:" + able
+            loglogi("onRoutingUpdated: can spatialize media 5.1:" + able
                     + " on device:" + ROUTING_DEVICES[0]);
             setDispatchAvailableState(able);
         } else {
-            Log.i(TAG, "onRoutingUpdated: device:" + ROUTING_DEVICES[0]
+            loglogi("onRoutingUpdated: device:" + ROUTING_DEVICES[0]
                     + " not available for Spatial Audio");
             setDispatchAvailableState(false);
         }
 
-        if (able && enabledAvailable.first) {
-            Log.i(TAG, "Enabling Spatial Audio since enabled for media device:"
+        boolean enabled = able && enabledAvailable.first;
+        if (enabled) {
+            loglogi("Enabling Spatial Audio since enabled for media device:"
                     + ROUTING_DEVICES[0]);
         } else {
-            Log.i(TAG, "Disabling Spatial Audio since disabled for media device:"
+            loglogi("Disabling Spatial Audio since disabled for media device:"
                     + ROUTING_DEVICES[0]);
         }
-        setDispatchFeatureEnabledState(able && enabledAvailable.first);
+        if (mSpat != null) {
+            byte level = enabled ? (byte) Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL
+                    : (byte) Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
+            loglogi("Setting spatialization level to: " + level);
+            try {
+                mSpat.setLevel(level);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Can't set spatializer level", e);
+                mState = STATE_NOT_SUPPORTED;
+                mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
+                enabled = false;
+            }
+        }
+
+        setDispatchFeatureEnabledState(enabled, "onRoutingUpdated");
 
         if (mDesiredHeadTrackingMode != Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED
                 && mDesiredHeadTrackingMode != Spatializer.HEAD_TRACKING_MODE_DISABLED) {
@@ -347,7 +362,7 @@
     private final class SpatializerCallback extends INativeSpatializerCallback.Stub {
 
         public void onLevelChanged(byte level) {
-            logd("SpatializerCallback.onLevelChanged level:" + level);
+            loglogi("SpatializerCallback.onLevelChanged level:" + level);
             synchronized (SpatializerHelper.this) {
                 mSpatLevel = spatializationLevelToSpatializerInt(level);
             }
@@ -358,7 +373,7 @@
         }
 
         public void onOutputChanged(int output) {
-            logd("SpatializerCallback.onOutputChanged output:" + output);
+            loglogi("SpatializerCallback.onOutputChanged output:" + output);
             int oldOutput;
             synchronized (SpatializerHelper.this) {
                 oldOutput = mSpatOutput;
@@ -374,20 +389,21 @@
     // spatializer head tracking callback from native
     private final class SpatializerHeadTrackingCallback
             extends ISpatializerHeadTrackingCallback.Stub {
-        public void onHeadTrackingModeChanged(byte mode)  {
-            logd("SpatializerHeadTrackingCallback.onHeadTrackingModeChanged mode:" + mode);
+        public void onHeadTrackingModeChanged(byte mode) {
             int oldMode, newMode;
             synchronized (this) {
                 oldMode = mActualHeadTrackingMode;
                 mActualHeadTrackingMode = headTrackingModeTypeToSpatializerInt(mode);
                 newMode = mActualHeadTrackingMode;
             }
+            loglogi("SpatializerHeadTrackingCallback.onHeadTrackingModeChanged mode:"
+                    + Spatializer.headtrackingModeToString(newMode));
             if (oldMode != newMode) {
                 dispatchActualHeadTrackingMode(newMode);
             }
         }
 
-        public void onHeadToSoundStagePoseUpdated(float[] headToStage)  {
+        public void onHeadToSoundStagePoseUpdated(float[] headToStage) {
             if (headToStage == null) {
                 Log.e(TAG, "SpatializerHeadTrackingCallback.onHeadToStagePoseUpdated"
                         + "null transform");
@@ -404,7 +420,8 @@
                 for (float val : headToStage) {
                     t.append("[").append(String.format(Locale.ENGLISH, "%.3f", val)).append("]");
                 }
-                logd("SpatializerHeadTrackingCallback.onHeadToStagePoseUpdated headToStage:" + t);
+                loglogi("SpatializerHeadTrackingCallback.onHeadToStagePoseUpdated headToStage:"
+                        + t);
             }
             dispatchPoseUpdate(headToStage);
         }
@@ -444,10 +461,9 @@
     }
 
     synchronized void addCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) {
-        // TODO add log
+        loglogi("addCompatibleAudioDevice: dev=" + ada);
         final int deviceType = ada.getType();
         final boolean wireless = isWireless(deviceType);
-        boolean updateRouting = false;
         boolean isInList = false;
 
         for (SADeviceState deviceState : mSADevices) {
@@ -455,8 +471,6 @@
                     && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress))
                     || !wireless) {
                 isInList = true;
-                // state change?
-                updateRouting = !deviceState.mEnabled;
                 deviceState.mEnabled = true;
                 break;
             }
@@ -466,32 +480,24 @@
                     wireless ? ada.getAddress() : null);
             dev.mEnabled = true;
             mSADevices.add(dev);
-            updateRouting = true;
         }
-        if (updateRouting) {
-            onRoutingUpdated();
-        }
+        onRoutingUpdated();
     }
 
     synchronized void removeCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) {
-        // TODO add log
+        loglogi("removeCompatibleAudioDevice: dev=" + ada);
         final int deviceType = ada.getType();
         final boolean wireless = isWireless(deviceType);
-        boolean updateRouting = false;
 
         for (SADeviceState deviceState : mSADevices) {
             if (deviceType == deviceState.mDeviceType
                     && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress))
                     || !wireless) {
-                // state change?
-                updateRouting = deviceState.mEnabled;
                 deviceState.mEnabled = false;
                 break;
             }
         }
-        if (updateRouting) {
-            onRoutingUpdated();
-        }
+        onRoutingUpdated();
     }
 
     /**
@@ -629,6 +635,7 @@
     }
 
     synchronized void setFeatureEnabled(boolean enabled) {
+        loglogi("setFeatureEnabled(" + enabled + ") was featureEnabled:" + mFeatureEnabled);
         if (mFeatureEnabled == enabled) {
             return;
         }
@@ -642,7 +649,6 @@
                 init(true);
             }
             setSpatializerEnabledInt(true);
-            onRoutingUpdated();
         } else {
             setSpatializerEnabledInt(false);
         }
@@ -652,7 +658,7 @@
         switch (mState) {
             case STATE_UNINITIALIZED:
                 if (enabled) {
-                    throw(new IllegalStateException("Can't enable when uninitialized"));
+                    throw (new IllegalStateException("Can't enable when uninitialized"));
                 }
                 return;
             case STATE_NOT_SUPPORTED:
@@ -664,6 +670,7 @@
             case STATE_DISABLED_AVAILABLE:
                 if (enabled) {
                     createSpat();
+                    onRoutingUpdated();
                     break;
                 } else {
                     // already in disabled state
@@ -679,7 +686,7 @@
                     return;
                 }
         }
-        setDispatchFeatureEnabledState(enabled);
+        setDispatchFeatureEnabledState(enabled, "setSpatializerEnabledInt");
     }
 
     synchronized int getCapableImmersiveAudioLevel() {
@@ -703,7 +710,8 @@
      * Update the feature state, no-op if no change
      * @param featureEnabled
      */
-    private synchronized void setDispatchFeatureEnabledState(boolean featureEnabled) {
+    private synchronized void setDispatchFeatureEnabledState(boolean featureEnabled, String source)
+    {
         if (featureEnabled) {
             switch (mState) {
                 case STATE_DISABLED_UNAVAILABLE:
@@ -715,9 +723,12 @@
                 case STATE_ENABLED_AVAILABLE:
                 case STATE_ENABLED_UNAVAILABLE:
                     // already enabled: no-op
+                    loglogi("setDispatchFeatureEnabledState(" + featureEnabled
+                            + ") no dispatch: mState:"
+                            + spatStateString(mState) + " src:" + source);
                     return;
                 default:
-                    throw(new IllegalStateException("Invalid mState:" + mState
+                    throw (new IllegalStateException("Invalid mState:" + mState
                             + " for enabled true"));
             }
         } else {
@@ -731,12 +742,17 @@
                 case STATE_DISABLED_AVAILABLE:
                 case STATE_DISABLED_UNAVAILABLE:
                     // already disabled: no-op
+                    loglogi("setDispatchFeatureEnabledState(" + featureEnabled
+                            + ") no dispatch: mState:" + spatStateString(mState)
+                            + " src:" + source);
                     return;
                 default:
                     throw (new IllegalStateException("Invalid mState:" + mState
                             + " for enabled false"));
             }
         }
+        loglogi("setDispatchFeatureEnabledState(" + featureEnabled
+                + ") mState:" + spatStateString(mState));
         final int nbCallbacks = mStateCallbacks.beginBroadcast();
         for (int i = 0; i < nbCallbacks; i++) {
             try {
@@ -747,14 +763,13 @@
             }
         }
         mStateCallbacks.finishBroadcast();
-        mAudioService.persistSpatialAudioEnabled(featureEnabled);
     }
 
     private synchronized void setDispatchAvailableState(boolean available) {
         switch (mState) {
             case STATE_UNINITIALIZED:
             case STATE_NOT_SUPPORTED:
-                throw(new IllegalStateException(
+                throw (new IllegalStateException(
                         "Should not update available state in state:" + mState));
             case STATE_DISABLED_UNAVAILABLE:
                 if (available) {
@@ -762,6 +777,8 @@
                     break;
                 } else {
                     // already in unavailable state
+                    loglogi("setDispatchAvailableState(" + available
+                            + ") no dispatch: mState:" + spatStateString(mState));
                     return;
                 }
             case STATE_ENABLED_UNAVAILABLE:
@@ -770,11 +787,15 @@
                     break;
                 } else {
                     // already in unavailable state
+                    loglogi("setDispatchAvailableState(" + available
+                            + ") no dispatch: mState:" + spatStateString(mState));
                     return;
                 }
             case STATE_DISABLED_AVAILABLE:
                 if (available) {
                     // already in available state
+                    loglogi("setDispatchAvailableState(" + available
+                            + ") no dispatch: mState:" + spatStateString(mState));
                     return;
                 } else {
                     mState = STATE_DISABLED_UNAVAILABLE;
@@ -783,12 +804,15 @@
             case STATE_ENABLED_AVAILABLE:
                 if (available) {
                     // already in available state
+                    loglogi("setDispatchAvailableState(" + available
+                            + ") no dispatch: mState:" + spatStateString(mState));
                     return;
                 } else {
                     mState = STATE_ENABLED_UNAVAILABLE;
                     break;
                 }
         }
+        loglogi("setDispatchAvailableState(" + available + ") mState:" + spatStateString(mState));
         final int nbCallbacks = mStateCallbacks.beginBroadcast();
         for (int i = 0; i < nbCallbacks; i++) {
             try {
@@ -813,14 +837,13 @@
             mSpatHeadTrackingCallback = new SpatializerHeadTrackingCallback();
             mSpat = AudioSystem.getSpatializer(mSpatCallback);
             try {
-                mSpat.setLevel((byte)  Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL);
                 mIsHeadTrackingSupported = mSpat.isHeadTrackingSupported();
                 //TODO: register heatracking callback only when sensors are registered
                 if (mIsHeadTrackingSupported) {
                     mSpat.registerHeadTrackingCallback(mSpatHeadTrackingCallback);
                 }
             } catch (RemoteException e) {
-                Log.e(TAG, "Can't set spatializer level", e);
+                Log.e(TAG, "Can't configure head tracking", e);
                 mState = STATE_NOT_SUPPORTED;
                 mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
             }
@@ -851,8 +874,6 @@
     // virtualization capabilities
     synchronized boolean canBeSpatialized(
             @NonNull AudioAttributes attributes, @NonNull AudioFormat format) {
-        logd("canBeSpatialized usage:" + attributes.getUsage()
-                + " format:" + format.toLogFriendlyString());
         switch (mState) {
             case STATE_UNINITIALIZED:
             case STATE_NOT_SUPPORTED:
@@ -879,7 +900,8 @@
         mASA.getDevicesForAttributes(
                 attributes, false /* forVolume */).toArray(devices);
         final boolean able = canBeSpatializedOnDevice(attributes, format, devices);
-        logd("canBeSpatialized returning " + able);
+        logd("canBeSpatialized usage:" + attributes.getUsage()
+                + " format:" + format.toLogFriendlyString() + " returning " + able);
         return able;
     }
 
@@ -1316,11 +1338,11 @@
         final boolean init = mFeatureEnabled && (mSpatLevel != SpatializationLevel.NONE);
         final String action = init ? "initializing" : "releasing";
         if (mSpat == null) {
-            Log.e(TAG, "not " + action + " sensors, null spatializer");
+            logloge("not " + action + " sensors, null spatializer");
             return;
         }
         if (!mIsHeadTrackingSupported) {
-            Log.e(TAG, "not " + action + " sensors, spatializer doesn't support headtracking");
+            logloge("not " + action + " sensors, spatializer doesn't support headtracking");
             return;
         }
         int headHandle = -1;
@@ -1345,7 +1367,7 @@
             //     does this happen before routing is updated?
             //     avoid by supporting adding device here AND in onRoutingUpdated()
             headHandle = getHeadSensorHandleUpdateTracker();
-            Log.i(TAG, "head tracker sensor handle initialized to " + headHandle);
+            loglogi("head tracker sensor handle initialized to " + headHandle);
             screenHandle = getScreenSensorHandle();
             Log.i(TAG, "found screen sensor handle initialized to " + screenHandle);
         } else {
@@ -1388,7 +1410,7 @@
             case SpatializerHeadTrackingMode.RELATIVE_SCREEN:
                 return Spatializer.HEAD_TRACKING_MODE_RELATIVE_DEVICE;
             default:
-                throw(new IllegalArgumentException("Unexpected head tracking mode:" + mode));
+                throw (new IllegalArgumentException("Unexpected head tracking mode:" + mode));
         }
     }
 
@@ -1403,7 +1425,7 @@
             case Spatializer.HEAD_TRACKING_MODE_RELATIVE_DEVICE:
                 return SpatializerHeadTrackingMode.RELATIVE_SCREEN;
             default:
-                throw(new IllegalArgumentException("Unexpected head tracking mode:" + sdkMode));
+                throw (new IllegalArgumentException("Unexpected head tracking mode:" + sdkMode));
         }
     }
 
@@ -1416,7 +1438,7 @@
             case SpatializationLevel.SPATIALIZER_MCHAN_BED_PLUS_OBJECTS:
                 return Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MCHAN_BED_PLUS_OBJECTS;
             default:
-                throw(new IllegalArgumentException("Unexpected spatializer level:" + level));
+                throw (new IllegalArgumentException("Unexpected spatializer level:" + level));
         }
     }
 
@@ -1429,18 +1451,19 @@
                 + Spatializer.headtrackingModeToString(mActualHeadTrackingMode));
         pw.println("\tmDesiredHeadTrackingMode:"
                 + Spatializer.headtrackingModeToString(mDesiredHeadTrackingMode));
-        String modesString = "";
+        pw.println("\tsupports binaural:" + mBinauralSupported + " / transaural:"
+                + mTransauralSupported);
+        StringBuilder modesString = new StringBuilder();
         int[] modes = getSupportedHeadTrackingModes();
         for (int mode : modes) {
-            modesString += Spatializer.headtrackingModeToString(mode) + " ";
+            modesString.append(Spatializer.headtrackingModeToString(mode)).append(" ");
         }
-        pw.println("\tsupports binaural:" + mBinauralSupported + " / transaural"
-                + mTransauralSupported);
         pw.println("\tsupported head tracking modes:" + modesString);
+        pw.println("\theadtracker available:" + mHeadTrackerAvailable);
         pw.println("\tmSpatOutput:" + mSpatOutput);
-        pw.println("\tdevices:\n");
+        pw.println("\tdevices:");
         for (SADeviceState device : mSADevices) {
-            pw.println("\t\t" + device + "\n");
+            pw.println("\t\t" + device);
         }
     }
 
@@ -1463,6 +1486,25 @@
         }
     }
 
+    private static String spatStateString(int state) {
+        switch (state) {
+            case STATE_UNINITIALIZED:
+                return "STATE_UNINITIALIZED";
+            case STATE_NOT_SUPPORTED:
+                return "STATE_NOT_SUPPORTED";
+            case STATE_DISABLED_UNAVAILABLE:
+                return "STATE_DISABLED_UNAVAILABLE";
+            case STATE_ENABLED_UNAVAILABLE:
+                return "STATE_ENABLED_UNAVAILABLE";
+            case STATE_ENABLED_AVAILABLE:
+                return "STATE_ENABLED_AVAILABLE";
+            case STATE_DISABLED_AVAILABLE:
+                return "STATE_DISABLED_AVAILABLE";
+            default:
+                return "invalid state";
+        }
+    }
+
     private static boolean isWireless(int deviceType) {
         for (int type : WIRELESS_TYPES) {
             if (type == deviceType) {
@@ -1473,7 +1515,7 @@
     }
 
     private static boolean isWirelessSpeaker(int deviceType) {
-        for (int type: WIRELESS_SPEAKER_TYPES) {
+        for (int type : WIRELESS_SPEAKER_TYPES) {
             if (type == deviceType) {
                 return true;
             }
@@ -1515,4 +1557,14 @@
         }
         return screenHandle;
     }
+
+
+    private static void loglogi(String msg) {
+        AudioService.sSpatialLogger.loglogi(msg, TAG);
+    }
+
+    private static String logloge(String msg) {
+        AudioService.sSpatialLogger.loglog(msg, AudioEventLogger.Event.ALOGE, TAG);
+        return msg;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index bf69284..cc49f07 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -462,7 +462,7 @@
                     mState = STATE_SHOWING_DEVICE_CREDENTIAL;
                     mStatusBarService.onBiometricError(modality, error, vendorCode);
                 } else if (error == BiometricConstants.BIOMETRIC_ERROR_CANCELED) {
-                    mStatusBarService.hideAuthenticationDialog();
+                    mStatusBarService.hideAuthenticationDialog(mRequestId);
                     // TODO: If multiple authenticators are simultaneously running, this will
                     // need to be modified. Send the error to the client here, instead of doing
                     // a round trip to SystemUI.
@@ -480,7 +480,7 @@
                 // the client and clean up. The only error we should get here is
                 // ERROR_CANCELED due to another client kicking us out.
                 mClientReceiver.onError(modality, error, vendorCode);
-                mStatusBarService.hideAuthenticationDialog();
+                mStatusBarService.hideAuthenticationDialog(mRequestId);
                 return true;
             }
 
@@ -489,7 +489,7 @@
                 break;
 
             case STATE_CLIENT_DIED_CANCELLING:
-                mStatusBarService.hideAuthenticationDialog();
+                mStatusBarService.hideAuthenticationDialog(mRequestId);
                 return true;
 
             default:
@@ -665,7 +665,7 @@
                     cancelAllSensors();
                     return false;
                 default:
-                    mStatusBarService.hideAuthenticationDialog();
+                    mStatusBarService.hideAuthenticationDialog(mRequestId);
                     return true;
             }
         } catch (RemoteException e) {
@@ -832,7 +832,7 @@
                         BiometricConstants.BIOMETRIC_ERROR_CANCELED,
                         0 /* vendorCode */
                 );
-                mStatusBarService.hideAuthenticationDialog();
+                mStatusBarService.hideAuthenticationDialog(mRequestId);
                 return true;
             } catch (RemoteException e) {
                 Slog.e(TAG, "Remote exception", e);
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
index 2a8d9f1..ad24cf0 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
@@ -53,14 +53,26 @@
     private boolean mShouldLogMetrics = true;
 
     private class ALSProbe implements Probe {
+        private boolean mDestroyed = false;
+
         @Override
-        public void enable() {
-            setLightSensorLoggingEnabled(getAmbientLightSensor(mSensorManager));
+        public synchronized void enable() {
+            if (!mDestroyed) {
+                setLightSensorLoggingEnabled(getAmbientLightSensor(mSensorManager));
+            }
         }
 
         @Override
-        public void disable() {
-            setLightSensorLoggingEnabled(null);
+        public synchronized void disable() {
+            if (!mDestroyed) {
+                setLightSensorLoggingEnabled(null);
+            }
+        }
+
+        @Override
+        public synchronized void destroy() {
+            disable();
+            mDestroyed = true;
         }
     }
 
diff --git a/services/core/java/com/android/server/biometrics/log/CallbackWithProbe.java b/services/core/java/com/android/server/biometrics/log/CallbackWithProbe.java
index f7b73688..93ff2a8 100644
--- a/services/core/java/com/android/server/biometrics/log/CallbackWithProbe.java
+++ b/services/core/java/com/android/server/biometrics/log/CallbackWithProbe.java
@@ -46,7 +46,7 @@
 
     @Override
     public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) {
-        mProbe.disable();
+        mProbe.destroy();
     }
 
     @NonNull
diff --git a/services/core/java/com/android/server/biometrics/log/Probe.java b/services/core/java/com/android/server/biometrics/log/Probe.java
index 9e6fc6b..d1056a4 100644
--- a/services/core/java/com/android/server/biometrics/log/Probe.java
+++ b/services/core/java/com/android/server/biometrics/log/Probe.java
@@ -27,4 +27,6 @@
     void enable();
     /** Stop sampling data. */
     void disable();
+    /** Same as {@link #disable()} and ignores all future calls to this probe. */
+    void destroy();
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/LockoutCache.java b/services/core/java/com/android/server/biometrics/sensors/LockoutCache.java
index 0aba2955..95c4903 100644
--- a/services/core/java/com/android/server/biometrics/sensors/LockoutCache.java
+++ b/services/core/java/com/android/server/biometrics/sensors/LockoutCache.java
@@ -16,12 +16,14 @@
 
 package com.android.server.biometrics.sensors;
 
+import android.util.Slog;
 import android.util.SparseIntArray;
 
 /**
  * For a single sensor, caches lockout states for all users.
  */
 public class LockoutCache implements LockoutTracker {
+    private static final String TAG = "LockoutCache";
 
     // Map of userId to LockoutMode
     private final SparseIntArray mUserLockoutStates;
@@ -31,6 +33,7 @@
     }
 
     public void setLockoutModeForUser(int userId, @LockoutMode int mode) {
+        Slog.d(TAG, "Lockout for user: " + userId +  " is " + mode);
         synchronized (this) {
             mUserLockoutStates.put(userId, mode);
         }
diff --git a/services/core/java/com/android/server/biometrics/sensors/LockoutTracker.java b/services/core/java/com/android/server/biometrics/sensors/LockoutTracker.java
index fa386d0..4a59c9d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/LockoutTracker.java
+++ b/services/core/java/com/android/server/biometrics/sensors/LockoutTracker.java
@@ -17,6 +17,7 @@
 package com.android.server.biometrics.sensors;
 
 import android.annotation.IntDef;
+import android.hardware.biometrics.BiometricConstants;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -25,9 +26,9 @@
  * Interface for retrieval of current user's lockout state.
  */
 public interface LockoutTracker {
-    int LOCKOUT_NONE = 0;
-    int LOCKOUT_TIMED = 1;
-    int LOCKOUT_PERMANENT = 2;
+    int LOCKOUT_NONE = BiometricConstants.BIOMETRIC_LOCKOUT_NONE;
+    int LOCKOUT_TIMED = BiometricConstants.BIOMETRIC_LOCKOUT_TIMED;
+    int LOCKOUT_PERMANENT = BiometricConstants.BIOMETRIC_LOCKOUT_PERMANENT;
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({LOCKOUT_NONE, LOCKOUT_TIMED, LOCKOUT_PERMANENT})
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
index b69c760..800d4b8 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
@@ -18,6 +18,9 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.SynchronousUserSwitchObserver;
+import android.app.UserSwitchObserver;
 import android.content.Context;
 import android.content.pm.UserInfo;
 import android.hardware.biometrics.BiometricsProtoEnums;
@@ -91,6 +94,14 @@
     @NonNull private final Supplier<AidlSession> mLazySession;
     @Nullable private AidlSession mCurrentSession;
 
+    private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() {
+        @Override
+        public void onUserSwitching(int newUserId) {
+            mProvider.scheduleInternalCleanup(
+                    mSensorProperties.sensorId, newUserId, null /* callback */);
+        }
+    };
+
     @VisibleForTesting
     public static class HalSessionCallback extends ISessionCallback.Stub {
         /**
@@ -537,6 +548,12 @@
         mLockoutCache = new LockoutCache();
         mAuthenticatorIds = new HashMap<>();
         mLazySession = () -> mCurrentSession != null ? mCurrentSession : null;
+
+        try {
+            ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, mTag);
+        } catch (RemoteException e) {
+            Slog.e(mTag, "Unable to register user switch observer");
+        }
     }
 
     @NonNull Supplier<AidlSession> getLazySession() {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
index 63e345e..024d611 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
@@ -18,6 +18,9 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.SynchronousUserSwitchObserver;
+import android.app.UserSwitchObserver;
 import android.content.Context;
 import android.content.pm.UserInfo;
 import android.hardware.biometrics.BiometricsProtoEnums;
@@ -92,6 +95,14 @@
     @Nullable private AidlSession mCurrentSession;
     @NonNull private final Supplier<AidlSession> mLazySession;
 
+    private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() {
+        @Override
+        public void onUserSwitching(int newUserId) {
+            mProvider.scheduleInternalCleanup(
+                    mSensorProperties.sensorId, newUserId, null /* callback */);
+        }
+    };
+
     @VisibleForTesting
     public static class HalSessionCallback extends ISessionCallback.Stub {
 
@@ -491,6 +502,12 @@
                 });
         mAuthenticatorIds = new HashMap<>();
         mLazySession = () -> mCurrentSession != null ? mCurrentSession : null;
+
+        try {
+            ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, mTag);
+        } catch (RemoteException e) {
+            Slog.e(mTag, "Unable to register user switch observer");
+        }
     }
 
     @NonNull Supplier<AidlSession> getLazySession() {
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 162eb0e..54e83ec 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -535,11 +535,11 @@
         pw.println("  Idle mode active=" + mCurrentBrightnessMapper.isForIdleMode());
 
         pw.println();
-        pw.println("  mActiveMapper=");
-        mInteractiveModeBrightnessMapper.dump(pw);
+        pw.println("  mInteractiveMapper=");
+        mInteractiveModeBrightnessMapper.dump(pw, mHbmController.getNormalBrightnessMax());
         if (mIdleModeBrightnessMapper != null) {
             pw.println("  mIdleMapper=");
-            mIdleModeBrightnessMapper.dump(pw);
+            mIdleModeBrightnessMapper.dump(pw, mHbmController.getNormalBrightnessMax());
         }
 
         pw.println();
diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
index c46ae85..38de9a7 100644
--- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
+++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
@@ -358,7 +358,10 @@
      */
     public abstract long getShortTermModelTimeout();
 
-    public abstract void dump(PrintWriter pw);
+    /**
+     * Prints dump output for display dumpsys.
+     */
+    public abstract void dump(PrintWriter pw, float hbmTransition);
 
     /**
      * We can designate a mapping strategy to be used for idle screen brightness mode.
@@ -714,7 +717,7 @@
         }
 
         @Override
-        public void dump(PrintWriter pw) {
+        public void dump(PrintWriter pw, float hbmTransition) {
             pw.println("SimpleMappingStrategy");
             pw.println("  mSpline=" + mSpline);
             pw.println("  mMaxGamma=" + mMaxGamma);
@@ -947,7 +950,7 @@
         }
 
         @Override
-        public void dump(PrintWriter pw) {
+        public void dump(PrintWriter pw, float hbmTransition) {
             pw.println("PhysicalMappingStrategy");
             pw.println("  mConfig=" + mConfig);
             pw.println("  mBrightnessSpline=" + mBrightnessSpline);
@@ -959,6 +962,8 @@
             pw.println("  mUserBrightness=" + mUserBrightness);
             pw.println("  mDefaultConfig=" + mDefaultConfig);
             pw.println("  mBrightnessRangeAdjustmentApplied=" + mBrightnessRangeAdjustmentApplied);
+
+            dumpConfigDiff(pw, hbmTransition);
         }
 
         @Override
@@ -966,6 +971,117 @@
             return mIsForIdleMode;
         }
 
+        /**
+         * Prints out the default curve and how it differs from the long-term curve
+         * and the current curve (in case the current curve includes short-term adjustments).
+         *
+         * @param pw The print-writer to write to.
+         */
+        private void dumpConfigDiff(PrintWriter pw, float hbmTransition) {
+            pw.println("  Difference between current config and default: ");
+
+            Pair<float[], float[]> currentCurve = mConfig.getCurve();
+            Spline currSpline = Spline.createSpline(currentCurve.first, currentCurve.second);
+
+            Pair<float[], float[]> defaultCurve = mDefaultConfig.getCurve();
+            Spline defaultSpline = Spline.createSpline(defaultCurve.first, defaultCurve.second);
+
+            // Add the short-term curve lux point if present
+            float[] luxes = currentCurve.first;
+            if (mUserLux >= 0) {
+                luxes = Arrays.copyOf(currentCurve.first, currentCurve.first.length + 1);
+                luxes[luxes.length - 1] = mUserLux;
+                Arrays.sort(luxes);
+            }
+
+            StringBuffer sbLux = null;
+            StringBuffer sbNits = null;
+            StringBuffer sbLong = null;
+            StringBuffer sbShort = null;
+            StringBuffer sbBrightness = null;
+            StringBuffer sbPercent = null;
+            StringBuffer sbPercentHbm = null;
+            boolean needsHeaders = true;
+            String separator = "";
+            for (int i = 0; i < luxes.length; i++) {
+                float lux = luxes[i];
+                if (needsHeaders) {
+                    sbLux = new StringBuffer("           lux: ");
+                    sbNits = new StringBuffer("       default: ");
+                    sbLong = new StringBuffer("     long-term: ");
+                    sbShort = new StringBuffer("       current: ");
+                    sbBrightness = new StringBuffer("   current(bl): ");
+                    sbPercent = new StringBuffer("    current(%): ");
+                    sbPercentHbm = new StringBuffer(" current(%hbm): ");
+                    needsHeaders = false;
+                }
+
+                float defaultNits = defaultSpline.interpolate(lux);
+                float longTermNits = currSpline.interpolate(lux);
+                float shortTermNits = mBrightnessSpline.interpolate(lux);
+                float brightness = mNitsToBrightnessSpline.interpolate(shortTermNits);
+
+                String luxPrefix = (lux == mUserLux ? "^" : "");
+                String strLux = luxPrefix + toStrFloatForDump(lux);
+                String strNits = toStrFloatForDump(defaultNits);
+                String strLong = toStrFloatForDump(longTermNits);
+                String strShort = toStrFloatForDump(shortTermNits);
+                String strBrightness = toStrFloatForDump(brightness);
+                String strPercent = String.valueOf(
+                        Math.round(100.0f * BrightnessUtils.convertLinearToGamma(
+                            (brightness / hbmTransition))));
+                String strPercentHbm = String.valueOf(
+                        Math.round(100.0f * BrightnessUtils.convertLinearToGamma(brightness)));
+
+                int maxLen = Math.max(strLux.length(),
+                        Math.max(strNits.length(),
+                        Math.max(strBrightness.length(),
+                        Math.max(strPercent.length(),
+                        Math.max(strPercentHbm.length(),
+                        Math.max(strLong.length(), strShort.length()))))));
+                String format = separator + "%" + maxLen + "s";
+                separator = ", ";
+
+                sbLux.append(String.format(format, strLux));
+                sbNits.append(String.format(format, strNits));
+                sbLong.append(String.format(format, strLong));
+                sbShort.append(String.format(format, strShort));
+                sbBrightness.append(String.format(format, strBrightness));
+                sbPercent.append(String.format(format, strPercent));
+                sbPercentHbm.append(String.format(format, strPercentHbm));
+
+                // At 80 chars, start another row
+                if (sbLux.length() > 80 || (i == luxes.length - 1)) {
+                    pw.println(sbLux);
+                    pw.println(sbNits);
+                    pw.println(sbLong);
+                    pw.println(sbShort);
+                    pw.println(sbBrightness);
+                    pw.println(sbPercent);
+                    if (hbmTransition < PowerManager.BRIGHTNESS_MAX) {
+                        pw.println(sbPercentHbm);
+                    }
+                    pw.println("");
+                    needsHeaders = true;
+                    separator = "";
+                }
+            }
+        }
+
+        private String toStrFloatForDump(float value) {
+            if (value == 0.0f) {
+                return "0";
+            } else if (value < 0.1f) {
+                return String.format("%.3f", value);
+            } else if (value < 1) {
+                return String.format("%.2f", value);
+            } else if (value < 10) {
+                return String.format("%.1f", value);
+            } else {
+                return String.format("%d", Math.round(value));
+            }
+        }
+
         private void computeNitsBrightnessSplines(float[] nits) {
             mNitsToBrightnessSpline = Spline.createSpline(nits, mBrightness);
             mBrightnessToNitsSpline = Spline.createSpline(mBrightness, nits);
diff --git a/services/core/java/com/android/server/display/DensityMap.java b/services/core/java/com/android/server/display/DensityMapping.java
similarity index 80%
rename from services/core/java/com/android/server/display/DensityMap.java
rename to services/core/java/com/android/server/display/DensityMapping.java
index 4aafd14..deca987 100644
--- a/services/core/java/com/android/server/display/DensityMap.java
+++ b/services/core/java/com/android/server/display/DensityMapping.java
@@ -23,31 +23,32 @@
  * Class which can compute the logical density for a display resolution. It holds a collection
  * of pre-configured densities, which are used for look-up and interpolation.
  */
-public class DensityMap {
+public class DensityMapping {
 
     // Instead of resolutions we store the squared diagonal size. Diagonals make the map
     // keys invariant to rotations and are useful for interpolation because they're scalars.
     // Squared diagonals have the same properties as diagonals (the square function is monotonic)
     // but also allow us to use integer types and avoid floating point arithmetics.
-    private final Entry[] mSortedDensityMapEntries;
+    private final Entry[] mSortedDensityMappingEntries;
 
     /**
-     * Creates a density map. The newly created object takes ownership of the passed array.
+     * Creates a density mapping. The newly created object takes ownership of the passed array.
      */
-    static DensityMap createByOwning(Entry[] densityMapEntries) {
-        return new DensityMap(densityMapEntries);
+    static DensityMapping createByOwning(Entry[] densityMappingEntries) {
+        return new DensityMapping(densityMappingEntries);
     }
 
-    private DensityMap(Entry[] densityMapEntries) {
-        Arrays.sort(densityMapEntries, Comparator.comparingInt(entry -> entry.squaredDiagonal));
-        mSortedDensityMapEntries = densityMapEntries;
-        verifyDensityMap(mSortedDensityMapEntries);
+    private DensityMapping(Entry[] densityMappingEntries) {
+        Arrays.sort(densityMappingEntries, Comparator.comparingInt(
+                entry -> entry.squaredDiagonal));
+        mSortedDensityMappingEntries = densityMappingEntries;
+        verifyDensityMapping(mSortedDensityMappingEntries);
     }
 
     /**
      * Returns the logical density for the given resolution.
      *
-     * If the resolution matches one of the entries in the map, the corresponding density is
+     * If the resolution matches one of the entries in the mapping, the corresponding density is
      * returned. Otherwise the return value is interpolated using the closest entries in the map.
      */
     public int getDensityForResolution(int width, int height) {
@@ -61,7 +62,7 @@
         Entry left = Entry.ZEROES;
         Entry right = null;
 
-        for (Entry entry : mSortedDensityMapEntries) {
+        for (Entry entry : mSortedDensityMappingEntries) {
             if (entry.squaredDiagonal <= squaredDiagonal) {
                 left = entry;
             } else {
@@ -90,7 +91,7 @@
                 / (rightDiagonal - leftDiagonal) + left.density);
     }
 
-    private static void verifyDensityMap(Entry[] sortedEntries) {
+    private static void verifyDensityMapping(Entry[] sortedEntries) {
         for (int i = 1; i < sortedEntries.length; i++) {
             Entry prev = sortedEntries[i - 1];
             Entry curr = sortedEntries[i];
@@ -100,10 +101,10 @@
                 // resolution (AxB and AxB) or rotated resolution (AxB and BxA), but it can also
                 // happen in the very rare cases when two different resolutions happen to have
                 // the same diagonal (e.g. 100x700 and 500x500).
-                throw new IllegalStateException("Found two entries in the density map with"
+                throw new IllegalStateException("Found two entries in the density mapping with"
                         + " the same diagonal: " + prev + ", " + curr);
             } else if (prev.density > curr.density) {
-                throw new IllegalStateException("Found two entries in the density map with"
+                throw new IllegalStateException("Found two entries in the density mapping with"
                         + " increasing diagonal but decreasing density: " + prev + ", " + curr);
             }
         }
@@ -111,8 +112,8 @@
 
     @Override
     public String toString() {
-        return "DensityMap{"
-                + "mDensityMapEntries=" + Arrays.toString(mSortedDensityMapEntries)
+        return "DensityMapping{"
+                + "mDensityMappingEntries=" + Arrays.toString(mSortedDensityMappingEntries)
                 + '}';
     }
 
@@ -129,7 +130,7 @@
 
         @Override
         public String toString() {
-            return "DensityMapEntry{"
+            return "DensityMappingEntry{"
                     + "squaredDiagonal=" + squaredDiagonal
                     + ", density=" + density + '}';
         }
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index d12c621..ed05aa6 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -272,7 +272,7 @@
     private List<String> mQuirks;
     private boolean mIsHighBrightnessModeEnabled = false;
     private HighBrightnessModeData mHbmData;
-    private DensityMap mDensityMap;
+    private DensityMapping mDensityMapping;
     private String mLoadedFrom = null;
 
     private BrightnessThrottlingData mBrightnessThrottlingData;
@@ -592,8 +592,8 @@
         return mRefreshRateLimitations;
     }
 
-    public DensityMap getDensityMap() {
-        return mDensityMap;
+    public DensityMapping getDensityMapping() {
+        return mDensityMapping;
     }
 
     /**
@@ -637,7 +637,7 @@
                 + ", mAmbientLightSensor=" + mAmbientLightSensor
                 + ", mProximitySensor=" + mProximitySensor
                 + ", mRefreshRateLimitations= " + Arrays.toString(mRefreshRateLimitations.toArray())
-                + ", mDensityMap= " + mDensityMap
+                + ", mDensityMapping= " + mDensityMapping
                 + "}";
     }
 
@@ -681,7 +681,7 @@
         try (InputStream in = new BufferedInputStream(new FileInputStream(configFile))) {
             final DisplayConfiguration config = XmlParser.read(in);
             if (config != null) {
-                loadDensityMap(config);
+                loadDensityMapping(config);
                 loadBrightnessDefaultFromDdcXml(config);
                 loadBrightnessConstraintsFromConfigXml();
                 loadBrightnessMap(config);
@@ -735,28 +735,28 @@
             return;
         }
 
-        if (mDensityMap == null) {
-            loadDensityMap(defaultConfig);
+        if (mDensityMapping == null) {
+            loadDensityMapping(defaultConfig);
         }
     }
 
-    private void loadDensityMap(DisplayConfiguration config) {
-        if (config.getDensityMap() == null) {
+    private void loadDensityMapping(DisplayConfiguration config) {
+        if (config.getDensityMapping() == null) {
             return;
         }
 
-        final List<Density> entriesFromXml = config.getDensityMap().getDensity();
+        final List<Density> entriesFromXml = config.getDensityMapping().getDensity();
 
-        final DensityMap.Entry[] entries =
-                new DensityMap.Entry[entriesFromXml.size()];
+        final DensityMapping.Entry[] entries =
+                new DensityMapping.Entry[entriesFromXml.size()];
         for (int i = 0; i < entriesFromXml.size(); i++) {
             final Density density = entriesFromXml.get(i);
-            entries[i] = new DensityMap.Entry(
+            entries[i] = new DensityMapping.Entry(
                     density.getWidth().intValue(),
                     density.getHeight().intValue(),
                     density.getDensity().intValue());
         }
-        mDensityMap = DensityMap.createByOwning(entries);
+        mDensityMapping = DensityMapping.createByOwning(entries);
     }
 
     private void loadBrightnessDefaultFromDdcXml(DisplayConfiguration config) {
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 932717a..5fcdc8a 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -70,6 +70,8 @@
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.display.DisplayManagerInternal.DisplayGroupListener;
 import android.hardware.display.DisplayManagerInternal.DisplayTransactionListener;
+import android.hardware.display.DisplayManagerInternal.RefreshRateLimitation;
+import android.hardware.display.DisplayManagerInternal.RefreshRateRange;
 import android.hardware.display.DisplayViewport;
 import android.hardware.display.DisplayedContentSample;
 import android.hardware.display.DisplayedContentSamplingAttributes;
@@ -708,9 +710,6 @@
         synchronized (mSyncRoot) {
             final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId);
             if (display != null) {
-                // Do not let constrain be overwritten by override from WindowManager.
-                info.shouldConstrainMetricsForLauncher =
-                        display.getDisplayInfoLocked().shouldConstrainMetricsForLauncher;
                 if (display.setDisplayInfoOverrideFromWindowManagerLocked(info)) {
                     handleLogicalDisplayChangedLocked(display);
                 }
@@ -2084,6 +2083,7 @@
     }
 
     private SurfaceControl.ScreenshotHardwareBuffer systemScreenshotInternal(int displayId) {
+        final SurfaceControl.DisplayCaptureArgs captureArgs;
         synchronized (mSyncRoot) {
             final IBinder token = getDisplayToken(displayId);
             if (token == null) {
@@ -2095,15 +2095,14 @@
             }
 
             final DisplayInfo displayInfo = logicalDisplay.getDisplayInfoLocked();
-            final SurfaceControl.DisplayCaptureArgs captureArgs =
-                    new SurfaceControl.DisplayCaptureArgs.Builder(token)
-                            .setSize(displayInfo.getNaturalWidth(), displayInfo.getNaturalHeight())
-                            .setUseIdentityTransform(true)
-                            .setCaptureSecureLayers(true)
-                            .setAllowProtected(true)
-                            .build();
-            return SurfaceControl.captureDisplay(captureArgs);
+            captureArgs = new SurfaceControl.DisplayCaptureArgs.Builder(token)
+                    .setSize(displayInfo.getNaturalWidth(), displayInfo.getNaturalHeight())
+                    .setUseIdentityTransform(true)
+                    .setCaptureSecureLayers(true)
+                    .setAllowProtected(true)
+                    .build();
         }
+        return SurfaceControl.captureDisplay(captureArgs);
     }
 
     private SurfaceControl.ScreenshotHardwareBuffer userScreenshotInternal(int displayId) {
@@ -2212,21 +2211,6 @@
         }
     }
 
-    void setShouldConstrainMetricsForLauncher(boolean constrain) {
-        // Apply constrain for every display.
-        synchronized (mSyncRoot) {
-            int[] displayIds = mLogicalDisplayMapper.getDisplayIdsLocked(Process.myUid());
-            for (int i : displayIds) {
-                final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(i);
-                if (display == null) {
-                    return;
-                }
-                display.getDisplayInfoLocked().shouldConstrainMetricsForLauncher = constrain;
-                setDisplayInfoOverrideFromWindowManagerInternal(i, display.getDisplayInfoLocked());
-            }
-        }
-    }
-
     void setDockedAndIdleEnabled(boolean enabled, int displayId) {
         synchronized (mSyncRoot) {
             final DisplayPowerController displayPowerController = mDisplayPowerControllers.get(
diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
index bfdac57..7dce238 100644
--- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
+++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
@@ -60,8 +60,6 @@
                 return setDisplayModeDirectorLoggingEnabled(false);
             case "dwb-set-cct":
                 return setAmbientColorTemperatureOverride();
-            case "constrain-launcher-metrics":
-                return setConstrainLauncherMetrics();
             case "set-user-preferred-display-mode":
                 return setUserPreferredDisplayMode();
             case "clear-user-preferred-display-mode":
@@ -112,9 +110,6 @@
         pw.println("    Disable display mode director logging.");
         pw.println("  dwb-set-cct CCT");
         pw.println("    Sets the ambient color temperature override to CCT (use -1 to disable).");
-        pw.println("  constrain-launcher-metrics [true|false]");
-        pw.println("    Sets if Display#getRealSize and getRealMetrics should be constrained for ");
-        pw.println("    Launcher.");
         pw.println("  set-user-preferred-display-mode WIDTH HEIGHT REFRESH-RATE "
                 + "DISPLAY_ID (optional)");
         pw.println("    Sets the user preferred display mode which has fields WIDTH, HEIGHT and "
@@ -205,17 +200,6 @@
         return 0;
     }
 
-    private int setConstrainLauncherMetrics() {
-        String constrainText = getNextArg();
-        if (constrainText == null) {
-            getErrPrintWriter().println("Error: no value specified");
-            return 1;
-        }
-        boolean constrain = Boolean.parseBoolean(constrainText);
-        mService.setShouldConstrainMetricsForLauncher(constrain);
-        return 0;
-    }
-
     private int setUserPreferredDisplayMode() {
         final String widthText = getNextArg();
         if (widthText == null) {
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index efd2f6f..ac72b17 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -2098,7 +2098,6 @@
 
     private class UdfpsObserver extends IUdfpsHbmListener.Stub {
         private final SparseBooleanArray mLocalHbmEnabled = new SparseBooleanArray();
-        private final SparseBooleanArray mGlobalHbmEnabled = new SparseBooleanArray();
 
         public void observe() {
             StatusBarManagerInternal statusBar =
@@ -2109,39 +2108,27 @@
         }
 
         @Override
-        public void onHbmEnabled(int hbmType, int displayId) {
+        public void onHbmEnabled(int displayId) {
             synchronized (mLock) {
-                updateHbmStateLocked(hbmType, displayId, true /*enabled*/);
+                updateHbmStateLocked(displayId, true /*enabled*/);
             }
         }
 
         @Override
-        public void onHbmDisabled(int hbmType, int displayId) {
+        public void onHbmDisabled(int displayId) {
             synchronized (mLock) {
-                updateHbmStateLocked(hbmType, displayId, false /*enabled*/);
+                updateHbmStateLocked(displayId, false /*enabled*/);
             }
         }
 
-        private void updateHbmStateLocked(int hbmType, int displayId, boolean enabled) {
-            switch (hbmType) {
-                case UdfpsObserver.LOCAL_HBM:
-                    mLocalHbmEnabled.put(displayId, enabled);
-                    break;
-                case UdfpsObserver.GLOBAL_HBM:
-                    mGlobalHbmEnabled.put(displayId, enabled);
-                    break;
-                default:
-                    Slog.w(TAG, "Unknown HBM type reported. Ignoring.");
-                    return;
-            }
+        private void updateHbmStateLocked(int displayId, boolean enabled) {
+            mLocalHbmEnabled.put(displayId, enabled);
             updateVoteLocked(displayId);
         }
 
         private void updateVoteLocked(int displayId) {
             final Vote vote;
-            if (mGlobalHbmEnabled.get(displayId)) {
-                vote = Vote.forRefreshRates(60f, 60f);
-            } else if (mLocalHbmEnabled.get(displayId)) {
+            if (mLocalHbmEnabled.get(displayId)) {
                 Display.Mode[] modes = mSupportedModesByDisplay.get(displayId);
                 float maxRefreshRate = 0f;
                 for (Display.Mode mode : modes) {
@@ -2165,13 +2152,6 @@
                 final String enabled = mLocalHbmEnabled.valueAt(i) ? "enabled" : "disabled";
                 pw.println("      Display " + displayId + ": " + enabled);
             }
-            pw.println("    mGlobalHbmEnabled: ");
-            for (int i = 0; i < mGlobalHbmEnabled.size(); i++) {
-                final int displayId = mGlobalHbmEnabled.keyAt(i);
-                final String enabled = mGlobalHbmEnabled.valueAt(i) ? "enabled" : "disabled";
-                pw.println("      Display " + displayId + ": " + enabled);
-            }
-
         }
     }
 
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 633cb24..a155095 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -469,12 +469,12 @@
         }
 
         private int getLogicalDensity() {
-            DensityMap densityMap = getDisplayDeviceConfig().getDensityMap();
-            if (densityMap == null) {
+            DensityMapping densityMapping = getDisplayDeviceConfig().getDensityMapping();
+            if (densityMapping == null) {
                 return (int) (mStaticDisplayInfo.density * 160 + 0.5);
             }
 
-            return densityMap.getDensityForResolution(mInfo.width, mInfo.height);
+            return densityMapping.getDensityForResolution(mInfo.width, mInfo.height);
         }
 
         private void loadDisplayDeviceConfig() {
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index b7ad4ed..a640497 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -233,8 +233,6 @@
                 info.displayCutout = mOverrideDisplayInfo.displayCutout;
                 info.logicalDensityDpi = mOverrideDisplayInfo.logicalDensityDpi;
                 info.roundedCorners = mOverrideDisplayInfo.roundedCorners;
-                info.shouldConstrainMetricsForLauncher =
-                        mOverrideDisplayInfo.shouldConstrainMetricsForLauncher;
             }
             mInfo.set(info);
         }
diff --git a/services/core/java/com/android/server/hdmi/AbsoluteVolumeAudioStatusAction.java b/services/core/java/com/android/server/hdmi/AbsoluteVolumeAudioStatusAction.java
new file mode 100644
index 0000000..d7563e0
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/AbsoluteVolumeAudioStatusAction.java
@@ -0,0 +1,103 @@
+/*
+ * 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.hdmi;
+
+/**
+ * Action to query and track the audio status of the System Audio device when enabling or using
+ * Absolute Volume Control. Must be removed when AVC is disabled. Performs two main functions:
+ * 1. When enabling AVC: queries the starting audio status of the System Audio device and
+ *    enables the feature upon receiving a response.
+ * 2. While AVC is enabled: monitors <Report Audio Status> messages from the System Audio device and
+ *    notifies AudioService if the audio status changes.
+ */
+final class AbsoluteVolumeAudioStatusAction extends HdmiCecFeatureAction {
+    private static final String TAG = "AbsoluteVolumeAudioStatusAction";
+
+    private int mInitialAudioStatusRetriesLeft = 2;
+
+    private static final int STATE_WAIT_FOR_INITIAL_AUDIO_STATUS = 1;
+    private static final int STATE_MONITOR_AUDIO_STATUS = 2;
+
+    private final int mTargetAddress;
+
+    private AudioStatus mLastAudioStatus;
+
+    AbsoluteVolumeAudioStatusAction(HdmiCecLocalDevice source, int targetAddress) {
+        super(source);
+        mTargetAddress = targetAddress;
+    }
+
+    @Override
+    boolean start() {
+        mState = STATE_WAIT_FOR_INITIAL_AUDIO_STATUS;
+        sendGiveAudioStatus();
+        return true;
+    }
+
+    void updateVolume(int volumeIndex) {
+        mLastAudioStatus = new AudioStatus(volumeIndex, mLastAudioStatus.getMute());
+    }
+
+    private void sendGiveAudioStatus() {
+        addTimer(mState, HdmiConfig.TIMEOUT_MS);
+        sendCommand(HdmiCecMessageBuilder.buildGiveAudioStatus(getSourceAddress(), mTargetAddress));
+    }
+
+    @Override
+    boolean processCommand(HdmiCecMessage cmd) {
+        switch (cmd.getOpcode()) {
+            case Constants.MESSAGE_REPORT_AUDIO_STATUS:
+                return handleReportAudioStatus(cmd);
+        }
+
+        return false;
+    }
+
+    private boolean handleReportAudioStatus(HdmiCecMessage cmd) {
+        if (mTargetAddress != cmd.getSource() || cmd.getParams().length == 0) {
+            return false;
+        }
+
+        boolean mute = HdmiUtils.isAudioStatusMute(cmd);
+        int volume = HdmiUtils.getAudioStatusVolume(cmd);
+        AudioStatus audioStatus = new AudioStatus(volume, mute);
+        if (mState == STATE_WAIT_FOR_INITIAL_AUDIO_STATUS) {
+            localDevice().getService().enableAbsoluteVolumeControl(audioStatus);
+            mState = STATE_MONITOR_AUDIO_STATUS;
+        } else if (mState == STATE_MONITOR_AUDIO_STATUS) {
+            if (audioStatus.getVolume() != mLastAudioStatus.getVolume()) {
+                localDevice().getService().notifyAvcVolumeChange(audioStatus.getVolume());
+            }
+            if (audioStatus.getMute() != mLastAudioStatus.getMute()) {
+                localDevice().getService().notifyAvcMuteChange(audioStatus.getMute());
+            }
+        }
+        mLastAudioStatus = audioStatus;
+
+        return true;
+    }
+
+    @Override
+    void handleTimerEvent(int state) {
+        if (mState != state) {
+            return;
+        } else if (mInitialAudioStatusRetriesLeft > 0) {
+            mInitialAudioStatusRetriesLeft--;
+            sendGiveAudioStatus();
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapper.java b/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapper.java
new file mode 100644
index 0000000..438c1ea
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapper.java
@@ -0,0 +1,67 @@
+/*
+ * 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.hdmi;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceVolumeManager;
+import android.media.VolumeInfo;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Wrapper for {@link AudioDeviceVolumeManager}. Creates an instance of the class and directly
+ * passes method calls to that instance.
+ */
+public class AudioDeviceVolumeManagerWrapper
+        implements AudioDeviceVolumeManagerWrapperInterface {
+
+    private static final String TAG = "AudioDeviceVolumeManagerWrapper";
+
+    private final AudioDeviceVolumeManager mAudioDeviceVolumeManager;
+
+    public AudioDeviceVolumeManagerWrapper(Context context) {
+        mAudioDeviceVolumeManager = new AudioDeviceVolumeManager(context);
+    }
+
+    @Override
+    public void addOnDeviceVolumeBehaviorChangedListener(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull AudioDeviceVolumeManager.OnDeviceVolumeBehaviorChangedListener listener)
+            throws SecurityException {
+        mAudioDeviceVolumeManager.addOnDeviceVolumeBehaviorChangedListener(executor, listener);
+    }
+
+    @Override
+    public void removeOnDeviceVolumeBehaviorChangedListener(
+            @NonNull AudioDeviceVolumeManager.OnDeviceVolumeBehaviorChangedListener listener) {
+        mAudioDeviceVolumeManager.removeOnDeviceVolumeBehaviorChangedListener(listener);
+    }
+
+    @Override
+    public void setDeviceAbsoluteVolumeBehavior(
+            @NonNull AudioDeviceAttributes device,
+            @NonNull VolumeInfo volume,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener vclistener,
+            boolean handlesVolumeAdjustment) {
+        mAudioDeviceVolumeManager.setDeviceAbsoluteVolumeBehavior(device, volume, executor,
+                vclistener, handlesVolumeAdjustment);
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapperInterface.java b/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapperInterface.java
new file mode 100644
index 0000000..1a1d4c1
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapperInterface.java
@@ -0,0 +1,61 @@
+/*
+ * 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.hdmi;
+
+import static android.media.AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener;
+import static android.media.AudioDeviceVolumeManager.OnDeviceVolumeBehaviorChangedListener;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceVolumeManager;
+import android.media.VolumeInfo;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Interface with the methods from {@link AudioDeviceVolumeManager} used by the HDMI framework.
+ * Allows the class to be faked for tests.
+ */
+public interface AudioDeviceVolumeManagerWrapperInterface {
+
+    /**
+     * Wrapper for {@link AudioDeviceVolumeManager#addOnDeviceVolumeBehaviorChangedListener(
+     * Executor, OnDeviceVolumeBehaviorChangedListener)}
+     */
+    void addOnDeviceVolumeBehaviorChangedListener(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull AudioDeviceVolumeManager.OnDeviceVolumeBehaviorChangedListener listener);
+
+    /**
+     * Wrapper for {@link AudioDeviceVolumeManager#removeOnDeviceVolumeBehaviorChangedListener(
+     * OnDeviceVolumeBehaviorChangedListener)}
+     */
+    void removeOnDeviceVolumeBehaviorChangedListener(
+            @NonNull AudioDeviceVolumeManager.OnDeviceVolumeBehaviorChangedListener listener);
+
+    /**
+     * Wrapper for {@link AudioDeviceVolumeManager#setDeviceAbsoluteVolumeBehavior(
+     * AudioDeviceAttributes, VolumeInfo, Executor, OnAudioDeviceVolumeChangedListener, boolean)}
+     */
+    void setDeviceAbsoluteVolumeBehavior(
+            @NonNull AudioDeviceAttributes device,
+            @NonNull VolumeInfo volume,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener vclistener,
+            boolean handlesVolumeAdjustment);
+}
diff --git a/services/core/java/com/android/server/hdmi/AudioStatus.java b/services/core/java/com/android/server/hdmi/AudioStatus.java
new file mode 100644
index 0000000..a884ffb
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/AudioStatus.java
@@ -0,0 +1,67 @@
+/*
+ * 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.hdmi;
+
+import android.annotation.Nullable;
+
+import java.util.Objects;
+
+/**
+ * Immutable representation of the information in the [Audio Status] operand:
+ * volume status (0 <= N <= 100) and mute status (muted or unmuted).
+ */
+public class AudioStatus {
+    public static final int MAX_VOLUME = 100;
+    public static final int MIN_VOLUME = 0;
+
+    int mVolume;
+    boolean mMute;
+
+    public AudioStatus(int volume, boolean mute) {
+        mVolume = volume;
+        mMute = mute;
+    }
+
+    public int getVolume() {
+        return mVolume;
+    }
+
+    public boolean getMute() {
+        return mMute;
+    }
+
+    @Override
+    public boolean equals(@Nullable Object obj) {
+        if (!(obj instanceof AudioStatus)) {
+            return false;
+        }
+
+        AudioStatus other = (AudioStatus) obj;
+        return mVolume == other.mVolume
+                && mMute == other.mMute;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mVolume, mMute);
+    }
+
+    @Override
+    public String toString() {
+        return "AudioStatus mVolume:" + mVolume + " mMute:" + mMute;
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java
index 751f2db..157057d 100644
--- a/services/core/java/com/android/server/hdmi/Constants.java
+++ b/services/core/java/com/android/server/hdmi/Constants.java
@@ -118,6 +118,7 @@
             MESSAGE_SYSTEM_AUDIO_MODE_REQUEST,
             MESSAGE_GIVE_AUDIO_STATUS,
             MESSAGE_SET_SYSTEM_AUDIO_MODE,
+            MESSAGE_SET_AUDIO_VOLUME_LEVEL,
             MESSAGE_REPORT_AUDIO_STATUS,
             MESSAGE_GIVE_SYSTEM_AUDIO_MODE_STATUS,
             MESSAGE_SYSTEM_AUDIO_MODE_STATUS,
@@ -197,9 +198,9 @@
     static final int MESSAGE_SYSTEM_AUDIO_MODE_REQUEST = 0x70;
     static final int MESSAGE_GIVE_AUDIO_STATUS = 0x71;
     static final int MESSAGE_SET_SYSTEM_AUDIO_MODE = 0x72;
+    static final int MESSAGE_SET_AUDIO_VOLUME_LEVEL = 0x73;
     static final int MESSAGE_REPORT_AUDIO_STATUS = 0x7A;
     static final int MESSAGE_GIVE_SYSTEM_AUDIO_MODE_STATUS = 0x7D;
-    static final int MESSAGE_SET_AUDIO_VOLUME_LEVEL = 0x73;
     static final int MESSAGE_SYSTEM_AUDIO_MODE_STATUS = 0x7E;
     static final int MESSAGE_ROUTING_CHANGE = 0x80;
     static final int MESSAGE_ROUTING_INFORMATION = 0x81;
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index 26a1613..fb2d2ee 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -303,6 +303,13 @@
         if (dispatchMessageToAction(message)) {
             return Constants.HANDLED;
         }
+
+        // If a message type has its own class, all valid messages of that type
+        // will be represented by an instance of that class.
+        if (message instanceof SetAudioVolumeLevelMessage) {
+            return handleSetAudioVolumeLevel((SetAudioVolumeLevelMessage) message);
+        }
+
         switch (message.getOpcode()) {
             case Constants.MESSAGE_ACTIVE_SOURCE:
                 return handleActiveSource(message);
@@ -637,6 +644,11 @@
         return Constants.NOT_HANDLED;
     }
 
+    @Constants.HandleMessageResult
+    protected int handleSetAudioVolumeLevel(SetAudioVolumeLevelMessage message) {
+        return Constants.NOT_HANDLED;
+    }
+
     @Constants.RcProfile
     protected abstract int getRcProfile();
 
@@ -1002,6 +1014,57 @@
         action.start();
     }
 
+    void addAvcAudioStatusAction(int targetAddress) {
+        if (!hasAction(AbsoluteVolumeAudioStatusAction.class)) {
+            addAndStartAction(new AbsoluteVolumeAudioStatusAction(this, targetAddress));
+        }
+    }
+
+    void removeAvcAudioStatusAction() {
+        removeAction(AbsoluteVolumeAudioStatusAction.class);
+    }
+
+    void updateAvcVolume(int volumeIndex) {
+        for (AbsoluteVolumeAudioStatusAction action :
+                getActions(AbsoluteVolumeAudioStatusAction.class)) {
+            action.updateVolume(volumeIndex);
+        }
+    }
+
+    /**
+     * Determines whether {@code targetAddress} supports <Set Audio Volume Level>. Does two things
+     * in parallel: send <Give Features> (to get <Report Features> in response),
+     * and send <Set Audio Volume Level> (to see if it gets a <Feature Abort> in response).
+     */
+    @ServiceThreadOnly
+    void queryAvcSupport(int targetAddress) {
+        assertRunOnServiceThread();
+
+        // Send <Give Features> if using CEC 2.0 or above.
+        if (mService.getCecVersion() >= HdmiControlManager.HDMI_CEC_VERSION_2_0) {
+            synchronized (mLock) {
+                mService.sendCecCommand(HdmiCecMessageBuilder.buildGiveFeatures(
+                        getDeviceInfo().getLogicalAddress(), targetAddress));
+            }
+        }
+
+        // If we don't already have a {@link SetAudioVolumeLevelDiscoveryAction} for the target
+        // device, start one.
+        List<SetAudioVolumeLevelDiscoveryAction> savlDiscoveryActions =
+                getActions(SetAudioVolumeLevelDiscoveryAction.class);
+        if (savlDiscoveryActions.stream().noneMatch(a -> a.getTargetAddress() == targetAddress)) {
+            addAndStartAction(new SetAudioVolumeLevelDiscoveryAction(this, targetAddress,
+                    new IHdmiControlCallback.Stub() {
+                            @Override
+                            public void onComplete(int result) {
+                                if (result == HdmiControlManager.RESULT_SUCCESS) {
+                                    getService().checkAndUpdateAbsoluteVolumeControlState();
+                                }
+                            }
+                        }));
+        }
+    }
+
     @ServiceThreadOnly
     void startQueuedActions() {
         assertRunOnServiceThread();
@@ -1205,6 +1268,9 @@
      */
     protected void disableDevice(
             boolean initiatedByCec, final PendingActionClearedCallback originalCallback) {
+        removeAction(AbsoluteVolumeAudioStatusAction.class);
+        removeAction(SetAudioVolumeLevelDiscoveryAction.class);
+
         mPendingActionClearedCallback =
                 new PendingActionClearedCallback() {
                     @Override
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
index 90b4f76..c0c0202 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
@@ -307,6 +307,7 @@
     protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
         removeAction(OneTouchPlayAction.class);
         removeAction(DevicePowerStatusAction.class);
+        removeAction(AbsoluteVolumeAudioStatusAction.class);
 
         super.disableDevice(initiatedByCec, callback);
     }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 9212fb6..1ea1457 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -1166,6 +1166,19 @@
         return Constants.HANDLED;
     }
 
+    @Override
+    @Constants.HandleMessageResult
+    protected int handleSetAudioVolumeLevel(SetAudioVolumeLevelMessage message) {
+        // <Set Audio Volume Level> should only be sent to the System Audio device, so we don't
+        // handle it when System Audio Mode is enabled.
+        if (mService.isSystemAudioActivated()) {
+            return Constants.ABORT_NOT_IN_CORRECT_MODE;
+        } else {
+            mService.setStreamMusicVolume(message.getAudioVolumeLevel(), 0);
+            return Constants.HANDLED;
+        }
+    }
+
     void announceOneTouchRecordResult(int recorderAddress, int result) {
         mService.invokeOneTouchRecordResult(recorderAddress, result);
     }
@@ -1202,6 +1215,13 @@
         return mService.getHdmiCecNetwork().getSafeCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM);
     }
 
+    /**
+     * Returns the audio output device used for System Audio Mode.
+     */
+    AudioDeviceAttributes getSystemAudioOutputDevice() {
+        return HdmiControlService.AUDIO_OUTPUT_DEVICE_HDMI_ARC;
+    }
+
 
     @ServiceThreadOnly
     void handleRemoveActiveRoutingPath(int path) {
@@ -1296,6 +1316,7 @@
         removeAction(OneTouchRecordAction.class);
         removeAction(TimerRecordingAction.class);
         removeAction(NewDeviceAction.class);
+        removeAction(AbsoluteVolumeAudioStatusAction.class);
 
         disableSystemAudioIfExist();
         disableArcIfExist();
@@ -1318,7 +1339,6 @@
         removeAction(SystemAudioActionFromAvr.class);
         removeAction(SystemAudioActionFromTv.class);
         removeAction(SystemAudioAutoInitiationAction.class);
-        removeAction(SystemAudioStatusAction.class);
         removeAction(VolumeControlAction.class);
 
         if (!mService.isControlEnabled()) {
@@ -1589,6 +1609,7 @@
         return DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder()
                 .setRecordTvScreenSupport(FEATURE_SUPPORTED)
                 .setArcTxSupport(hasArcPort ? FEATURE_SUPPORTED : FEATURE_NOT_SUPPORTED)
+                .setSetAudioVolumeLevelSupport(FEATURE_SUPPORTED)
                 .build();
     }
 
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessage.java b/services/core/java/com/android/server/hdmi/HdmiCecMessage.java
index 290cae5..2b84225 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessage.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessage.java
@@ -262,6 +262,8 @@
                 return "Give Audio Status";
             case Constants.MESSAGE_SET_SYSTEM_AUDIO_MODE:
                 return "Set System Audio Mode";
+            case Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL:
+                return "Set Audio Volume Level";
             case Constants.MESSAGE_REPORT_AUDIO_STATUS:
                 return "Report Audio Status";
             case Constants.MESSAGE_GIVE_SYSTEM_AUDIO_MODE_STATUS:
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java b/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java
index 8b6d16a..caaf800 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java
@@ -259,6 +259,7 @@
             // The addition of a local device should not notify listeners
             return;
         }
+        mHdmiControlService.checkAndUpdateAbsoluteVolumeControlState();
         if (info.getPhysicalAddress() == HdmiDeviceInfo.PATH_INVALID) {
             // Don't notify listeners of devices that haven't reported their physical address yet
             return;
@@ -383,7 +384,7 @@
     final void removeCecDevice(HdmiCecLocalDevice localDevice, int address) {
         assertRunOnServiceThread();
         HdmiDeviceInfo info = removeDeviceInfo(HdmiDeviceInfo.idForCecDevice(address));
-
+        mHdmiControlService.checkAndUpdateAbsoluteVolumeControlState();
         localDevice.mCecMessageCache.flushMessagesFrom(address);
         if (info.getPhysicalAddress() == HdmiDeviceInfo.PATH_INVALID) {
             // Don't notify listeners of devices that haven't reported their physical address yet
@@ -586,6 +587,8 @@
                 .build();
 
         updateCecDevice(newDeviceInfo);
+
+        mHdmiControlService.checkAndUpdateAbsoluteVolumeControlState();
     }
 
     @ServiceThreadOnly
@@ -617,6 +620,8 @@
                     )
                     .build();
             updateCecDevice(newDeviceInfo);
+
+            mHdmiControlService.checkAndUpdateAbsoluteVolumeControlState();
         }
     }
 
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 12380ab..9824b4e 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -30,6 +30,7 @@
 import static com.android.server.power.ShutdownThread.SHUTDOWN_ACTION_PROPERTY;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
@@ -38,6 +39,7 @@
 import android.content.IntentFilter;
 import android.database.ContentObserver;
 import android.hardware.display.DisplayManager;
+import android.hardware.hdmi.DeviceFeatures;
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.hardware.hdmi.HdmiHotplugEvent;
@@ -56,7 +58,12 @@
 import android.hardware.hdmi.IHdmiVendorCommandListener;
 import android.hardware.tv.cec.V1_0.OptionKey;
 import android.hardware.tv.cec.V1_0.SendMessageResult;
+import android.media.AudioAttributes;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
+import android.media.AudioDeviceVolumeManager;
 import android.media.AudioManager;
+import android.media.VolumeInfo;
 import android.media.session.MediaController;
 import android.media.session.MediaSessionManager;
 import android.media.tv.TvInputManager;
@@ -83,6 +90,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.Display;
+import android.view.KeyEvent;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -101,6 +109,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
@@ -202,6 +211,27 @@
     public @interface WakeReason {
     }
 
+    @VisibleForTesting
+    static final AudioDeviceAttributes AUDIO_OUTPUT_DEVICE_HDMI = new AudioDeviceAttributes(
+            AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_HDMI, "");
+    @VisibleForTesting
+    static final AudioDeviceAttributes AUDIO_OUTPUT_DEVICE_HDMI_ARC =
+            new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT,
+                    AudioDeviceInfo.TYPE_HDMI_ARC, "");
+    static final AudioDeviceAttributes AUDIO_OUTPUT_DEVICE_HDMI_EARC =
+            new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT,
+                    AudioDeviceInfo.TYPE_HDMI_EARC, "");
+
+    // Audio output devices used for Absolute Volume Control
+    private static final List<AudioDeviceAttributes> AVC_AUDIO_OUTPUT_DEVICES =
+            Collections.unmodifiableList(Arrays.asList(AUDIO_OUTPUT_DEVICE_HDMI,
+                    AUDIO_OUTPUT_DEVICE_HDMI_ARC, AUDIO_OUTPUT_DEVICE_HDMI_EARC));
+
+    // AudioAttributes for STREAM_MUSIC
+    @VisibleForTesting
+    static final AudioAttributes STREAM_MUSIC_ATTRIBUTES =
+            new AudioAttributes.Builder().setLegacyStreamType(AudioManager.STREAM_MUSIC).build();
+
     private final Executor mServiceThreadExecutor = new Executor() {
         @Override
         public void execute(Runnable r) {
@@ -209,6 +239,10 @@
         }
     };
 
+    Executor getServiceThreadExecutor() {
+        return mServiceThreadExecutor;
+    }
+
     // Logical address of the active source.
     @GuardedBy("mLock")
     protected final ActiveSource mActiveSource = new ActiveSource();
@@ -222,6 +256,13 @@
     @HdmiControlManager.VolumeControl
     private int mHdmiCecVolumeControl;
 
+    // Caches the volume behaviors of all audio output devices in AVC_AUDIO_OUTPUT_DEVICES.
+    @GuardedBy("mLock")
+    private Map<AudioDeviceAttributes, Integer> mAudioDeviceVolumeBehaviors = new HashMap<>();
+
+    // Maximum volume of AudioManager.STREAM_MUSIC. Set upon gaining access to system services.
+    private int mStreamMusicMaxVolume;
+
     // Make sure HdmiCecConfig is instantiated and the XMLs are read.
     private HdmiCecConfig mHdmiCecConfig;
 
@@ -411,6 +452,12 @@
     private PowerManagerInternalWrapper mPowerManagerInternal;
 
     @Nullable
+    private AudioManager mAudioManager;
+
+    @Nullable
+    private AudioDeviceVolumeManagerWrapperInterface mAudioDeviceVolumeManager;
+
+    @Nullable
     private Looper mIoLooper;
 
     @Nullable
@@ -439,11 +486,21 @@
 
     private final SelectRequestBuffer mSelectRequestBuffer = new SelectRequestBuffer();
 
-    @VisibleForTesting HdmiControlService(Context context, List<Integer> deviceTypes) {
+    /**
+     * Constructor for testing.
+     *
+     * It's critical to use a fake AudioDeviceVolumeManager because a normally instantiated
+     * AudioDeviceVolumeManager can access the "real" AudioService on the DUT.
+     *
+     * @see FakeAudioDeviceVolumeManagerWrapper
+     */
+    @VisibleForTesting HdmiControlService(Context context, List<Integer> deviceTypes,
+            AudioDeviceVolumeManagerWrapperInterface audioDeviceVolumeManager) {
         super(context);
         mLocalDevices = deviceTypes;
         mSettingsObserver = new SettingsObserver(mHandler);
         mHdmiCecConfig = new HdmiCecConfig(context);
+        mAudioDeviceVolumeManager = audioDeviceVolumeManager;
     }
 
     public HdmiControlService(Context context) {
@@ -744,6 +801,14 @@
                     Context.TV_INPUT_SERVICE);
             mPowerManager = new PowerManagerWrapper(getContext());
             mPowerManagerInternal = new PowerManagerInternalWrapper();
+            mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
+            mStreamMusicMaxVolume = getAudioManager().getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+            if (mAudioDeviceVolumeManager == null) {
+                mAudioDeviceVolumeManager =
+                        new AudioDeviceVolumeManagerWrapper(getContext());
+            }
+            getAudioDeviceVolumeManager().addOnDeviceVolumeBehaviorChangedListener(
+                    mServiceThreadExecutor, this::onDeviceVolumeBehaviorChanged);
         } else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
             runOnServiceThread(this::bootCompleted);
         }
@@ -2419,6 +2484,7 @@
             pw.println("mPowerStatus: " + mPowerStatusController.getPowerStatus());
             pw.println("mIsCecAvailable: " + mIsCecAvailable);
             pw.println("mCecVersion: " + mCecVersion);
+            pw.println("mIsAbsoluteVolumeControlEnabled: " + isAbsoluteVolumeControlEnabled());
 
             // System settings
             pw.println("System_settings:");
@@ -2578,6 +2644,7 @@
             @HdmiControlManager.VolumeControl int hdmiCecVolumeControl) {
         mHdmiCecVolumeControl = hdmiCecVolumeControl;
         announceHdmiCecVolumeControlFeatureChange(hdmiCecVolumeControl);
+        runOnServiceThread(this::checkAndUpdateAbsoluteVolumeControlState);
     }
 
     // Get the source address to send out commands to devices connected to the current device
@@ -3086,15 +3153,17 @@
     private void announceHdmiCecVolumeControlFeatureChange(
             @HdmiControlManager.VolumeControl int hdmiCecVolumeControl) {
         assertRunOnServiceThread();
-        mHdmiCecVolumeControlFeatureListenerRecords.broadcast(listener -> {
-            try {
-                listener.onHdmiCecVolumeControlFeature(hdmiCecVolumeControl);
-            } catch (RemoteException e) {
-                Slog.e(TAG,
-                        "Failed to report HdmiControlVolumeControlStatusChange: "
-                                + hdmiCecVolumeControl);
-            }
-        });
+        synchronized (mLock) {
+            mHdmiCecVolumeControlFeatureListenerRecords.broadcast(listener -> {
+                try {
+                    listener.onHdmiCecVolumeControlFeature(hdmiCecVolumeControl);
+                } catch (RemoteException e) {
+                    Slog.e(TAG,
+                            "Failed to report HdmiControlVolumeControlStatusChange: "
+                                    + hdmiCecVolumeControl);
+                }
+            });
+        }
     }
 
     public HdmiCecLocalDeviceTv tv() {
@@ -3131,8 +3200,20 @@
                 HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
     }
 
+    /**
+     * Returns null before the boot phase {@link SystemService#PHASE_SYSTEM_SERVICES_READY}.
+     */
+    @Nullable
     AudioManager getAudioManager() {
-        return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
+        return mAudioManager;
+    }
+
+    /**
+     * Returns null before the boot phase {@link SystemService#PHASE_SYSTEM_SERVICES_READY}.
+     */
+    @Nullable
+    private AudioDeviceVolumeManagerWrapperInterface getAudioDeviceVolumeManager() {
+        return mAudioDeviceVolumeManager;
     }
 
     boolean isControlEnabled() {
@@ -3486,6 +3567,7 @@
         synchronized (mLock) {
             mSystemAudioActivated = on;
         }
+        runOnServiceThread(this::checkAndUpdateAbsoluteVolumeControlState);
     }
 
     @ServiceThreadOnly
@@ -3599,6 +3681,8 @@
             device.addActiveSourceHistoryItem(new ActiveSource(logicalAddress, physicalAddress),
                     deviceIsActiveSource, caller);
         }
+
+        runOnServiceThread(this::checkAndUpdateAbsoluteVolumeControlState);
     }
 
     // This method should only be called when the device can be the active source
@@ -3791,4 +3875,332 @@
             Slog.e(TAG, "Failed to report setting change", e);
         }
     }
+
+    /**
+     * Listener for changes to the volume behavior of an audio output device. Caches the
+     * volume behavior of devices used for Absolute Volume Control.
+     */
+    @VisibleForTesting
+    @ServiceThreadOnly
+    void onDeviceVolumeBehaviorChanged(AudioDeviceAttributes device, int volumeBehavior) {
+        assertRunOnServiceThread();
+        if (AVC_AUDIO_OUTPUT_DEVICES.contains(device)) {
+            synchronized (mLock) {
+                mAudioDeviceVolumeBehaviors.put(device, volumeBehavior);
+            }
+            checkAndUpdateAbsoluteVolumeControlState();
+        }
+    }
+
+    /**
+     * Wrapper for {@link AudioManager#getDeviceVolumeBehavior} that takes advantage of cached
+     * results for the volume behaviors of HDMI audio devices.
+     */
+    @AudioManager.DeviceVolumeBehavior
+    private int getDeviceVolumeBehavior(AudioDeviceAttributes device) {
+        if (AVC_AUDIO_OUTPUT_DEVICES.contains(device)) {
+            synchronized (mLock) {
+                if (mAudioDeviceVolumeBehaviors.containsKey(device)) {
+                    return mAudioDeviceVolumeBehaviors.get(device);
+                }
+            }
+        }
+        return getAudioManager().getDeviceVolumeBehavior(device);
+    }
+
+    /**
+     * Returns whether Absolute Volume Control is enabled or not. This is determined by the
+     * volume behavior of the relevant HDMI audio output device(s) for this device's type.
+     */
+    public boolean isAbsoluteVolumeControlEnabled() {
+        if (!isTvDevice() && !isPlaybackDevice()) {
+            return false;
+        }
+        AudioDeviceAttributes avcAudioOutputDevice = getAvcAudioOutputDevice();
+        if (avcAudioOutputDevice == null) {
+            return false;
+        }
+        return getDeviceVolumeBehavior(avcAudioOutputDevice)
+                    == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE;
+    }
+
+    private AudioDeviceAttributes getAvcAudioOutputDevice() {
+        if (isTvDevice()) {
+            return tv().getSystemAudioOutputDevice();
+        } else if (isPlaybackDevice()) {
+            return AUDIO_OUTPUT_DEVICE_HDMI;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Checks the conditions for Absolute Volume Control (AVC), and enables or disables the feature
+     * if necessary. AVC is enabled precisely when a specific audio output device
+     * (HDMI for playback devices, and HDMI_ARC or HDMI_EARC for TVs) is using absolute volume
+     * behavior.
+     *
+     * AVC must be enabled on a Playback device or TV precisely when it is playing
+     * audio on an external device (the System Audio device) that supports the feature.
+     * This reduces to these conditions:
+     *
+     * 1. If the System Audio Device is an Audio System: System Audio Mode is active
+     * 2. Our HDMI audio output device is using full volume behavior
+     * 3. CEC volume is enabled
+     * 4. The System Audio device supports AVC (i.e. it supports <Set Audio Volume Level>)
+     *
+     * If not all of these conditions are met, this method disables AVC if necessary.
+     *
+     * If all of these conditions are met, this method starts an action to query the System Audio
+     * device's audio status, which enables AVC upon obtaining the audio status.
+     */
+    @ServiceThreadOnly
+    void checkAndUpdateAbsoluteVolumeControlState() {
+        assertRunOnServiceThread();
+
+        // Can't enable or disable AVC before we have access to system services
+        if (getAudioManager() == null) {
+            return;
+        }
+
+        HdmiCecLocalDevice localCecDevice;
+        if (isTvDevice() && tv() != null) {
+            localCecDevice = tv();
+            // Condition 1: TVs need System Audio Mode to be active
+            // (Doesn't apply to Playback Devices, where if SAM isn't active, we assume the
+            // TV is the System Audio Device instead.)
+            if (!isSystemAudioActivated()) {
+                disableAbsoluteVolumeControl();
+                return;
+            }
+        } else if (isPlaybackDevice() && playback() != null) {
+            localCecDevice = playback();
+        } else {
+            // Either this device type doesn't support AVC, or it hasn't fully initialized yet
+            return;
+        }
+
+        HdmiDeviceInfo systemAudioDeviceInfo = getHdmiCecNetwork().getSafeCecDeviceInfo(
+                localCecDevice.findAudioReceiverAddress());
+        @AudioManager.DeviceVolumeBehavior int currentVolumeBehavior =
+                        getDeviceVolumeBehavior(getAvcAudioOutputDevice());
+
+        // Condition 2: Already using full or absolute volume behavior
+        boolean alreadyUsingFullOrAbsoluteVolume =
+                currentVolumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL
+                        || currentVolumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE;
+        // Condition 3: CEC volume is enabled
+        boolean cecVolumeEnabled =
+                getHdmiCecVolumeControl() == HdmiControlManager.VOLUME_CONTROL_ENABLED;
+
+        if (!cecVolumeEnabled || !alreadyUsingFullOrAbsoluteVolume) {
+            disableAbsoluteVolumeControl();
+            return;
+        }
+
+        // Check for safety: if the System Audio device is a candidate for AVC, we should already
+        // have received messages from it to trigger the other conditions.
+        if (systemAudioDeviceInfo == null) {
+            disableAbsoluteVolumeControl();
+            return;
+        }
+        // Condition 4: The System Audio device supports AVC (i.e. <Set Audio Volume Level>).
+        switch (systemAudioDeviceInfo.getDeviceFeatures().getSetAudioVolumeLevelSupport()) {
+            case DeviceFeatures.FEATURE_SUPPORTED:
+                if (!isAbsoluteVolumeControlEnabled()) {
+                    // Start an action that will call {@link #enableAbsoluteVolumeControl}
+                    // once the System Audio device sends <Report Audio Status>
+                    localCecDevice.addAvcAudioStatusAction(
+                            systemAudioDeviceInfo.getLogicalAddress());
+                }
+                return;
+            case DeviceFeatures.FEATURE_NOT_SUPPORTED:
+                disableAbsoluteVolumeControl();
+                return;
+            case DeviceFeatures.FEATURE_SUPPORT_UNKNOWN:
+                disableAbsoluteVolumeControl();
+                localCecDevice.queryAvcSupport(systemAudioDeviceInfo.getLogicalAddress());
+                return;
+            default:
+                return;
+        }
+    }
+
+    private void disableAbsoluteVolumeControl() {
+        if (isPlaybackDevice()) {
+            playback().removeAvcAudioStatusAction();
+        } else if (isTvDevice()) {
+            tv().removeAvcAudioStatusAction();
+        }
+        AudioDeviceAttributes device = getAvcAudioOutputDevice();
+        if (getDeviceVolumeBehavior(device) == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE) {
+            getAudioManager().setDeviceVolumeBehavior(device,
+                    AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+        }
+    }
+
+    /**
+     * Enables Absolute Volume Control. Should only be called when all the conditions for
+     * AVC are met (see {@link #checkAndUpdateAbsoluteVolumeControlState}).
+     * @param audioStatus The initial audio status to set the audio output device to
+     */
+    void enableAbsoluteVolumeControl(AudioStatus audioStatus) {
+        HdmiCecLocalDevice localDevice = isPlaybackDevice() ? playback() : tv();
+        HdmiDeviceInfo systemAudioDevice = getHdmiCecNetwork().getDeviceInfo(
+                localDevice.findAudioReceiverAddress());
+        VolumeInfo volumeInfo = new VolumeInfo.Builder(AudioManager.STREAM_MUSIC)
+                .setMuted(audioStatus.getMute())
+                .setVolumeIndex(audioStatus.getVolume())
+                .setMaxVolumeIndex(AudioStatus.MAX_VOLUME)
+                .setMinVolumeIndex(AudioStatus.MIN_VOLUME)
+                .build();
+        mAbsoluteVolumeChangedListener = new AbsoluteVolumeChangedListener(
+                localDevice, systemAudioDevice);
+
+        // AudioService sets the volume of the stream and device based on the input VolumeInfo
+        // when enabling absolute volume behavior, but not the mute state
+        notifyAvcMuteChange(audioStatus.getMute());
+        getAudioDeviceVolumeManager().setDeviceAbsoluteVolumeBehavior(
+                getAvcAudioOutputDevice(), volumeInfo, mServiceThreadExecutor,
+                mAbsoluteVolumeChangedListener, true);
+    }
+
+    private AbsoluteVolumeChangedListener mAbsoluteVolumeChangedListener;
+
+    @VisibleForTesting
+    AbsoluteVolumeChangedListener getAbsoluteVolumeChangedListener() {
+        return mAbsoluteVolumeChangedListener;
+    }
+
+    /**
+     * Listeners for changes reported by AudioService to the state of an audio output device using
+     * absolute volume behavior.
+     */
+    @VisibleForTesting
+    class AbsoluteVolumeChangedListener implements
+            AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener {
+        private HdmiCecLocalDevice mLocalDevice;
+        private HdmiDeviceInfo mSystemAudioDevice;
+
+        private AbsoluteVolumeChangedListener(HdmiCecLocalDevice localDevice,
+                HdmiDeviceInfo systemAudioDevice) {
+            mLocalDevice = localDevice;
+            mSystemAudioDevice = systemAudioDevice;
+        }
+
+        /**
+         * Called when AudioService sets the volume level of an absolute volume audio output device
+         * to a numeric value.
+         */
+        @Override
+        public void onAudioDeviceVolumeChanged(
+                @NonNull AudioDeviceAttributes audioDevice,
+                @NonNull VolumeInfo volumeInfo) {
+            int localDeviceAddress;
+            synchronized (mLocalDevice.mLock) {
+                localDeviceAddress = mLocalDevice.getDeviceInfo().getLogicalAddress();
+            }
+            sendCecCommand(SetAudioVolumeLevelMessage.build(
+                            localDeviceAddress,
+                            mSystemAudioDevice.getLogicalAddress(),
+                            volumeInfo.getVolumeIndex()),
+                    // If sending the message fails, ask the System Audio device for its
+                    // audio status so that we can update AudioService
+                    (int errorCode) -> {
+                        if (errorCode == SendMessageResult.SUCCESS) {
+                            // Update the volume tracked in our AbsoluteVolumeAudioStatusAction
+                            // so it correctly processes incoming <Report Audio Status> messages
+                            HdmiCecLocalDevice avcDevice = isTvDevice() ? tv() : playback();
+                            avcDevice.updateAvcVolume(volumeInfo.getVolumeIndex());
+                        } else {
+                            sendCecCommand(HdmiCecMessageBuilder.buildGiveAudioStatus(
+                                    localDeviceAddress,
+                                    mSystemAudioDevice.getLogicalAddress()
+                            ));
+                        }
+                    });
+        }
+
+        /**
+         * Called when AudioService adjusts the volume or mute state of an absolute volume
+         * audio output device
+         */
+        @Override
+        public void onAudioDeviceVolumeAdjusted(
+                @NonNull AudioDeviceAttributes audioDevice,
+                @NonNull VolumeInfo volumeInfo,
+                @AudioManager.VolumeAdjustment int direction,
+                @AudioDeviceVolumeManager.VolumeAdjustmentMode int mode
+        ) {
+            int keyCode;
+            switch (direction) {
+                case AudioManager.ADJUST_RAISE:
+                    keyCode = KeyEvent.KEYCODE_VOLUME_UP;
+                    break;
+                case AudioManager.ADJUST_LOWER:
+                    keyCode = KeyEvent.KEYCODE_VOLUME_DOWN;
+                    break;
+                case AudioManager.ADJUST_TOGGLE_MUTE:
+                case AudioManager.ADJUST_MUTE:
+                case AudioManager.ADJUST_UNMUTE:
+                    // Many CEC devices only support toggle mute. Therefore, we send the
+                    // same keycode for all three mute options.
+                    keyCode = KeyEvent.KEYCODE_VOLUME_MUTE;
+                    break;
+                default:
+                    return;
+            }
+            switch (mode) {
+                case AudioDeviceVolumeManager.ADJUST_MODE_NORMAL:
+                    mLocalDevice.sendVolumeKeyEvent(keyCode, true);
+                    mLocalDevice.sendVolumeKeyEvent(keyCode, false);
+                    break;
+                case AudioDeviceVolumeManager.ADJUST_MODE_START:
+                    mLocalDevice.sendVolumeKeyEvent(keyCode, true);
+                    break;
+                case AudioDeviceVolumeManager.ADJUST_MODE_END:
+                    mLocalDevice.sendVolumeKeyEvent(keyCode, false);
+                    break;
+                default:
+                    return;
+            }
+        }
+    }
+
+    /**
+     * Notifies AudioService of a change in the volume of the System Audio device. Has no effect if
+     * AVC is disabled, or the audio output device for AVC is not playing for STREAM_MUSIC
+     */
+    void notifyAvcVolumeChange(int volume) {
+        if (!isAbsoluteVolumeControlEnabled()) return;
+        List<AudioDeviceAttributes> streamMusicDevices =
+                getAudioManager().getDevicesForAttributes(STREAM_MUSIC_ATTRIBUTES);
+        if (streamMusicDevices.contains(getAvcAudioOutputDevice())) {
+            setStreamMusicVolume(volume, AudioManager.FLAG_ABSOLUTE_VOLUME);
+        }
+    }
+
+    /**
+     * Notifies AudioService of a change in the mute status of the System Audio device. Has no
+     * effect if AVC is disabled, or the audio output device for AVC is not playing for STREAM_MUSIC
+     */
+    void notifyAvcMuteChange(boolean mute) {
+        if (!isAbsoluteVolumeControlEnabled()) return;
+        List<AudioDeviceAttributes> streamMusicDevices =
+                getAudioManager().getDevicesForAttributes(STREAM_MUSIC_ATTRIBUTES);
+        if (streamMusicDevices.contains(getAvcAudioOutputDevice())) {
+            int direction = mute ? AudioManager.ADJUST_MUTE : AudioManager.ADJUST_UNMUTE;
+            getAudioManager().adjustStreamVolume(AudioManager.STREAM_MUSIC, direction,
+                    AudioManager.FLAG_ABSOLUTE_VOLUME);
+        }
+    }
+
+    /**
+     * Sets the volume index of {@link AudioManager#STREAM_MUSIC}. Rescales the input volume index
+     * from HDMI-CEC volume range to STREAM_MUSIC's.
+     */
+    void setStreamMusicVolume(int volume, int flags) {
+        getAudioManager().setStreamVolume(AudioManager.STREAM_MUSIC,
+                volume * mStreamMusicMaxVolume / AudioStatus.MAX_VOLUME, flags);
+    }
 }
diff --git a/services/core/java/com/android/server/hdmi/SendKeyAction.java b/services/core/java/com/android/server/hdmi/SendKeyAction.java
index adcef66..7daeaf1 100644
--- a/services/core/java/com/android/server/hdmi/SendKeyAction.java
+++ b/services/core/java/com/android/server/hdmi/SendKeyAction.java
@@ -172,8 +172,19 @@
     }
 
     private void sendKeyUp() {
-        sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(getSourceAddress(),
-                mTargetAddress));
+        // When using Absolute Volume Control, query audio status after a volume key is released.
+        // This allows us to notify AudioService of the resulting volume or mute status changes.
+        if (HdmiCecKeycode.isVolumeKeycode(mLastKeycode)
+                && localDevice().getService().isAbsoluteVolumeControlEnabled()) {
+            sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(getSourceAddress(),
+                    mTargetAddress),
+                    __ -> sendCommand(HdmiCecMessageBuilder.buildGiveAudioStatus(
+                            getSourceAddress(),
+                            localDevice().findAudioReceiverAddress())));
+        } else {
+            sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(getSourceAddress(),
+                    mTargetAddress));
+        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryAction.java b/services/core/java/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryAction.java
index 96fd003..eb3b33d 100644
--- a/services/core/java/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryAction.java
+++ b/services/core/java/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryAction.java
@@ -122,4 +122,11 @@
             return true;
         }
     }
+
+    /**
+     * Returns the logical address of this action's target device.
+     */
+    public int getTargetAddress() {
+        return mTargetAddress;
+    }
 }
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioAction.java b/services/core/java/com/android/server/hdmi/SystemAudioAction.java
index 978c25d..e7a3db7 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioAction.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioAction.java
@@ -153,7 +153,7 @@
                 boolean receivedStatus = HdmiUtils.parseCommandParamSystemAudioStatus(cmd);
                 if (receivedStatus == mTargetAudioStatus) {
                     setSystemAudioMode(receivedStatus);
-                    startAudioStatusAction();
+                    finish();
                     return true;
                 } else {
                     HdmiLogger.debug("Unexpected system audio mode request:" + receivedStatus);
@@ -168,11 +168,6 @@
         }
     }
 
-    protected void startAudioStatusAction() {
-        addAndStartAction(new SystemAudioStatusAction(tv(), mAvrLogicalAddress, mCallbacks));
-        finish();
-    }
-
     protected void removeSystemAudioActionInProgress() {
         removeActionExcept(SystemAudioActionFromTv.class, this);
         removeActionExcept(SystemAudioActionFromAvr.class, this);
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java b/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java
index 6ddff91..99148c4 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java
@@ -16,8 +16,8 @@
 
 package com.android.server.hdmi;
 
-import android.hardware.hdmi.HdmiDeviceInfo;
 import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
 import android.hardware.hdmi.IHdmiControlCallback;
 
 /**
@@ -65,7 +65,7 @@
 
         if (mTargetAudioStatus) {
             setSystemAudioMode(true);
-            startAudioStatusAction();
+            finish();
         } else {
             setSystemAudioMode(false);
             finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java b/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java
deleted file mode 100644
index b4af540..0000000
--- a/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (C) 2014 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.hdmi;
-
-import android.hardware.hdmi.HdmiControlManager;
-import android.hardware.hdmi.IHdmiControlCallback;
-import android.hardware.tv.cec.V1_0.SendMessageResult;
-
-import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
-
-import java.util.List;
-
-/**
- * Action to update audio status (volume or mute) of audio amplifier
- */
-final class SystemAudioStatusAction extends HdmiCecFeatureAction {
-    private static final String TAG = "SystemAudioStatusAction";
-
-    // State that waits for <ReportAudioStatus>.
-    private static final int STATE_WAIT_FOR_REPORT_AUDIO_STATUS = 1;
-
-    private final int mAvrAddress;
-
-    SystemAudioStatusAction(
-            HdmiCecLocalDevice source, int avrAddress, List<IHdmiControlCallback> callbacks) {
-        super(source, callbacks);
-        mAvrAddress = avrAddress;
-    }
-
-    SystemAudioStatusAction(HdmiCecLocalDevice source, int avrAddress,
-            IHdmiControlCallback callback) {
-        super(source, callback);
-        mAvrAddress = avrAddress;
-    }
-
-    @Override
-    boolean start() {
-        mState = STATE_WAIT_FOR_REPORT_AUDIO_STATUS;
-        addTimer(mState, HdmiConfig.TIMEOUT_MS);
-        sendGiveAudioStatus();
-        return true;
-    }
-
-    private void sendGiveAudioStatus() {
-        sendCommand(HdmiCecMessageBuilder.buildGiveAudioStatus(getSourceAddress(), mAvrAddress),
-                new SendMessageCallback() {
-            @Override
-            public void onSendCompleted(int error) {
-                if (error != SendMessageResult.SUCCESS) {
-                    handleSendGiveAudioStatusFailure();
-                }
-            }
-        });
-    }
-
-    private void handleSendGiveAudioStatusFailure() {
-
-        // Still return SUCCESS to callback.
-        finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
-    }
-
-    @Override
-    boolean processCommand(HdmiCecMessage cmd) {
-        if (mState != STATE_WAIT_FOR_REPORT_AUDIO_STATUS || mAvrAddress != cmd.getSource()) {
-            return false;
-        }
-
-        switch (cmd.getOpcode()) {
-            case Constants.MESSAGE_REPORT_AUDIO_STATUS:
-                handleReportAudioStatus(cmd);
-                return true;
-        }
-
-        return false;
-    }
-
-    private void handleReportAudioStatus(HdmiCecMessage cmd) {
-        byte[] params = cmd.getParams();
-        boolean mute = HdmiUtils.isAudioStatusMute(cmd);
-        int volume = HdmiUtils.getAudioStatusVolume(cmd);
-        tv().setAudioStatus(mute, volume);
-
-        if (!(tv().isSystemAudioActivated() ^ mute)) {
-            // Toggle AVR's mute status to match with the system audio status.
-            sendUserControlPressedAndReleased(mAvrAddress, HdmiCecKeycode.CEC_KEYCODE_MUTE);
-        }
-        finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
-    }
-
-    @Override
-    void handleTimerEvent(int state) {
-        if (mState != state) {
-            return;
-        }
-
-        handleSendGiveAudioStatusFailure();
-    }
-}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 140a28f..603d012 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -20,6 +20,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.ActivityManagerInternal;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
@@ -73,7 +74,6 @@
 import android.os.LocaleList;
 import android.os.Looper;
 import android.os.Message;
-import android.os.MessageQueue;
 import android.os.Process;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
@@ -111,6 +111,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.os.SomeArgs;
@@ -146,9 +147,7 @@
 import java.util.Objects;
 import java.util.OptionalInt;
 
-/*
- * Wraps the C++ InputManager and provides its callbacks.
- */
+/** The system implementation of {@link IInputManager} that manages input devices. */
 public class InputManagerService extends IInputManager.Stub
         implements Watchdog.Monitor {
     static final String TAG = "InputManager";
@@ -182,8 +181,7 @@
     /** TODO(b/169067926): Remove this. */
     private static final boolean UNTRUSTED_TOUCHES_TOAST = false;
 
-    // Pointer to native input manager service object.
-    private final long mPtr;
+    private final NativeInputManagerService mNative;
 
     private final Context mContext;
     private final InputManagerHandler mHandler;
@@ -298,92 +296,6 @@
     @GuardedBy("mInputMonitors")
     final Map<IBinder, GestureMonitorSpyWindow> mInputMonitors = new HashMap<>();
 
-    private static native long nativeInit(InputManagerService service,
-            Context context, MessageQueue messageQueue);
-    private static native void nativeStart(long ptr);
-    private static native void nativeSetDisplayViewports(long ptr,
-            DisplayViewport[] viewports);
-
-    private static native int nativeGetScanCodeState(long ptr,
-            int deviceId, int sourceMask, int scanCode);
-    private static native int nativeGetKeyCodeState(long ptr,
-            int deviceId, int sourceMask, int keyCode);
-    private static native int nativeGetSwitchState(long ptr,
-            int deviceId, int sourceMask, int sw);
-    private static native boolean nativeHasKeys(long ptr,
-            int deviceId, int sourceMask, int[] keyCodes, boolean[] keyExists);
-    private static native int nativeGetKeyCodeForKeyLocation(long ptr, int deviceId,
-            int locationKeyCode);
-    private static native InputChannel nativeCreateInputChannel(long ptr, String name);
-    private static native InputChannel nativeCreateInputMonitor(long ptr, int displayId,
-            String name, int pid);
-    private static native void nativeRemoveInputChannel(long ptr, IBinder connectionToken);
-    private static native void nativePilferPointers(long ptr, IBinder token);
-    private static native void nativeSetInputFilterEnabled(long ptr, boolean enable);
-    private static native boolean nativeSetInTouchMode(long ptr, boolean inTouchMode, int pid,
-            int uid, boolean hasPermission);
-    private static native void nativeSetMaximumObscuringOpacityForTouch(long ptr, float opacity);
-    private static native void nativeSetBlockUntrustedTouchesMode(long ptr, int mode);
-    private static native int nativeInjectInputEvent(long ptr, InputEvent event,
-            int injectorPid, int injectorUid, int syncMode, int timeoutMillis,
-            int policyFlags);
-    private static native VerifiedInputEvent nativeVerifyInputEvent(long ptr, InputEvent event);
-    private static native void nativeToggleCapsLock(long ptr, int deviceId);
-    private static native void nativeDisplayRemoved(long ptr, int displayId);
-    private static native void nativeSetInputDispatchMode(long ptr, boolean enabled, boolean frozen);
-    private static native void nativeSetSystemUiLightsOut(long ptr, boolean lightsOut);
-    private static native void nativeSetFocusedApplication(long ptr,
-            int displayId, InputApplicationHandle application);
-    private static native void nativeSetFocusedDisplay(long ptr, int displayId);
-    private static native boolean nativeTransferTouchFocus(long ptr,
-            IBinder fromChannelToken, IBinder toChannelToken, boolean isDragDrop);
-    private static native boolean nativeTransferTouch(long ptr, IBinder destChannelToken);
-    private static native void nativeSetPointerSpeed(long ptr, int speed);
-    private static native void nativeSetPointerAcceleration(long ptr, float acceleration);
-    private static native void nativeSetShowTouches(long ptr, boolean enabled);
-    private static native void nativeSetInteractive(long ptr, boolean interactive);
-    private static native void nativeReloadCalibration(long ptr);
-    private static native void nativeVibrate(long ptr, int deviceId, long[] pattern,
-            int[] amplitudes, int repeat, int token);
-    private static native void nativeVibrateCombined(long ptr, int deviceId, long[] pattern,
-            SparseArray<int[]> amplitudes, int repeat, int token);
-    private static native void nativeCancelVibrate(long ptr, int deviceId, int token);
-    private static native boolean nativeIsVibrating(long ptr, int deviceId);
-    private static native int[] nativeGetVibratorIds(long ptr, int deviceId);
-    private static native int nativeGetBatteryCapacity(long ptr, int deviceId);
-    private static native int nativeGetBatteryStatus(long ptr, int deviceId);
-    private static native List<Light> nativeGetLights(long ptr, int deviceId);
-    private static native int nativeGetLightPlayerId(long ptr, int deviceId, int lightId);
-    private static native int nativeGetLightColor(long ptr, int deviceId, int lightId);
-    private static native void nativeSetLightPlayerId(long ptr, int deviceId, int lightId,
-            int playerId);
-    private static native void nativeSetLightColor(long ptr, int deviceId, int lightId, int color);
-    private static native void nativeReloadKeyboardLayouts(long ptr);
-    private static native void nativeReloadDeviceAliases(long ptr);
-    private static native String nativeDump(long ptr);
-    private static native void nativeMonitor(long ptr);
-    private static native boolean nativeIsInputDeviceEnabled(long ptr, int deviceId);
-    private static native void nativeEnableInputDevice(long ptr, int deviceId);
-    private static native void nativeDisableInputDevice(long ptr, int deviceId);
-    private static native void nativeSetPointerIconType(long ptr, int iconId);
-    private static native void nativeReloadPointerIcons(long ptr);
-    private static native void nativeSetCustomPointerIcon(long ptr, PointerIcon icon);
-    private static native void nativeRequestPointerCapture(long ptr, IBinder windowToken,
-            boolean enabled);
-    private static native boolean nativeCanDispatchToDisplay(long ptr, int deviceId, int displayId);
-    private static native void nativeNotifyPortAssociationsChanged(long ptr);
-    private static native void nativeChangeUniqueIdAssociation(long ptr);
-    private static native void nativeNotifyPointerDisplayIdChanged(long ptr);
-    private static native void nativeSetDisplayEligibilityForPointerCapture(long ptr, int displayId,
-            boolean enabled);
-    private static native void nativeSetMotionClassifierEnabled(long ptr, boolean enabled);
-    private static native InputSensorInfo[] nativeGetSensorList(long ptr, int deviceId);
-    private static native boolean nativeFlushSensor(long ptr, int deviceId, int sensorType);
-    private static native boolean nativeEnableSensor(long ptr, int deviceId, int sensorType,
-            int samplingPeriodUs, int maxBatchReportLatencyUs);
-    private static native void nativeDisableSensor(long ptr, int deviceId, int sensorType);
-    private static native void nativeCancelCurrentTouch(long ptr);
-
     // Maximum number of milliseconds to wait for input event injection.
     private static final int INJECTION_TIMEOUT_MILLIS = 30 * 1000;
 
@@ -450,18 +362,47 @@
     /** Whether to use the dev/input/event or uevent subsystem for the audio jack. */
     final boolean mUseDevInputEventForAudioJack;
 
+    /** Point of injection for test dependencies. */
+    @VisibleForTesting
+    static class Injector {
+        private final Context mContext;
+        private final Looper mLooper;
+
+        Injector(Context context, Looper looper) {
+            mContext = context;
+            mLooper = looper;
+        }
+
+        Context getContext() {
+            return mContext;
+        }
+
+        Looper getLooper() {
+            return mLooper;
+        }
+
+        NativeInputManagerService getNativeService(InputManagerService service) {
+            return new NativeInputManagerService.NativeImpl(service, mContext, mLooper.getQueue());
+        }
+    }
+
     public InputManagerService(Context context) {
-        this.mContext = context;
-        this.mHandler = new InputManagerHandler(DisplayThread.get().getLooper());
+        this(new Injector(context, DisplayThread.get().getLooper()));
+    }
+
+    @VisibleForTesting
+    InputManagerService(Injector injector) {
+        mContext = injector.getContext();
+        mHandler = new InputManagerHandler(injector.getLooper());
+        mNative = injector.getNativeService(this);
 
         mStaticAssociations = loadStaticInputPortAssociations();
         mUseDevInputEventForAudioJack =
-                context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
+                mContext.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
         Slog.i(TAG, "Initializing input manager, mUseDevInputEventForAudioJack="
                 + mUseDevInputEventForAudioJack);
-        mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
 
-        String doubleTouchGestureEnablePath = context.getResources().getString(
+        String doubleTouchGestureEnablePath = mContext.getResources().getString(
                 R.string.config_doubleTouchGestureEnableFile);
         mDoubleTouchGestureEnableFile = TextUtils.isEmpty(doubleTouchGestureEnablePath) ? null :
             new File(doubleTouchGestureEnablePath);
@@ -504,9 +445,9 @@
 
     public void start() {
         Slog.i(TAG, "Starting input manager");
-        nativeStart(mPtr);
+        mNative.start();
 
-        // Add ourself to the Watchdog monitors.
+        // Add ourselves to the Watchdog monitors.
         Watchdog.getInstance().addMonitor(this);
 
         registerPointerSpeedSettingObserver();
@@ -599,14 +540,14 @@
         if (DEBUG) {
             Slog.d(TAG, "Reloading keyboard layouts.");
         }
-        nativeReloadKeyboardLayouts(mPtr);
+        mNative.reloadKeyboardLayouts();
     }
 
     private void reloadDeviceAliases() {
         if (DEBUG) {
             Slog.d(TAG, "Reloading device names.");
         }
-        nativeReloadDeviceAliases(mPtr);
+        mNative.reloadDeviceAliases();
     }
 
     private void setDisplayViewportsInternal(List<DisplayViewport> viewports) {
@@ -615,7 +556,7 @@
             for (int i = viewports.size() - 1; i >= 0; --i) {
                 vArray[i] = viewports.get(i);
             }
-            nativeSetDisplayViewports(mPtr, vArray);
+            mNative.setDisplayViewports(vArray);
 
             if (mOverriddenPointerDisplayId != Display.INVALID_DISPLAY) {
                 final AdditionalDisplayInputProperties properties =
@@ -642,7 +583,7 @@
      * @return The key state.
      */
     public int getKeyCodeState(int deviceId, int sourceMask, int keyCode) {
-        return nativeGetKeyCodeState(mPtr, deviceId, sourceMask, keyCode);
+        return mNative.getKeyCodeState(deviceId, sourceMask, keyCode);
     }
 
     /**
@@ -655,7 +596,7 @@
      * @return The key state.
      */
     public int getScanCodeState(int deviceId, int sourceMask, int scanCode) {
-        return nativeGetScanCodeState(mPtr, deviceId, sourceMask, scanCode);
+        return mNative.getScanCodeState(deviceId, sourceMask, scanCode);
     }
 
     /**
@@ -668,7 +609,7 @@
      * @return The switch state.
      */
     public int getSwitchState(int deviceId, int sourceMask, int switchCode) {
-        return nativeGetSwitchState(mPtr, deviceId, sourceMask, switchCode);
+        return mNative.getSwitchState(deviceId, sourceMask, switchCode);
     }
 
     /**
@@ -691,7 +632,7 @@
             throw new IllegalArgumentException("keyExists must be at least as large as keyCodes");
         }
 
-        return nativeHasKeys(mPtr, deviceId, sourceMask, keyCodes, keyExists);
+        return mNative.hasKeys(deviceId, sourceMask, keyCodes, keyExists);
     }
 
     /**
@@ -707,7 +648,7 @@
         if (locationKeyCode <= KEYCODE_UNKNOWN || locationKeyCode > KeyEvent.getMaxKeyCode()) {
             return KEYCODE_UNKNOWN;
         }
-        return nativeGetKeyCodeForKeyLocation(mPtr, deviceId, locationKeyCode);
+        return mNative.getKeyCodeForKeyLocation(deviceId, locationKeyCode);
     }
 
     /**
@@ -720,7 +661,7 @@
     public boolean transferTouch(IBinder destChannelToken) {
         // TODO(b/162194035): Replace this with a SPY window
         Objects.requireNonNull(destChannelToken, "destChannelToken must not be null");
-        return nativeTransferTouch(mPtr, destChannelToken);
+        return mNative.transferTouch(destChannelToken);
     }
 
     /**
@@ -736,7 +677,7 @@
             throw new IllegalArgumentException("displayId must >= 0.");
         }
 
-        return nativeCreateInputMonitor(mPtr, displayId, inputChannelName, Binder.getCallingPid());
+        return mNative.createInputMonitor(displayId, inputChannelName, Binder.getCallingPid());
     }
 
     @NonNull
@@ -818,7 +759,7 @@
      * @param name The name of this input channel
      */
     public InputChannel createInputChannel(String name) {
-        return nativeCreateInputChannel(mPtr, name);
+        return mNative.createInputChannel(name);
     }
 
     /**
@@ -827,7 +768,7 @@
      */
     public void removeInputChannel(IBinder connectionToken) {
         Objects.requireNonNull(connectionToken, "connectionToken must not be null");
-        nativeRemoveInputChannel(mPtr, connectionToken);
+        mNative.removeInputChannel(connectionToken);
     }
 
     /**
@@ -869,7 +810,7 @@
                 }
             }
 
-            nativeSetInputFilterEnabled(mPtr, filter != null);
+            mNative.setInputFilterEnabled(filter != null);
         }
     }
 
@@ -893,15 +834,19 @@
      * @return {@code true} if the touch mode was successfully changed, {@code false} otherwise
      */
     public boolean setInTouchMode(boolean inTouchMode, int pid, int uid, boolean hasPermission) {
-        return nativeSetInTouchMode(mPtr, inTouchMode, pid, uid, hasPermission);
+        return mNative.setInTouchMode(inTouchMode, pid, uid, hasPermission);
     }
 
     @Override // Binder call
-    public boolean injectInputEvent(InputEvent event, int mode) {
-        return injectInputEventInternal(event, mode);
-    }
-
-    private boolean injectInputEventInternal(InputEvent event, int mode) {
+    public boolean injectInputEvent(InputEvent event, int mode, int targetUid) {
+        if (!checkCallingPermission(android.Manifest.permission.INJECT_EVENTS,
+                "injectInputEvent()", true /*checkInstrumentationSource*/)) {
+            throw new SecurityException(
+                    "Injecting input events requires the caller (or the source of the "
+                            + "instrumentation, if any) to have the INJECT_EVENTS permission.");
+        }
+        // We are not checking if targetUid matches the callingUid, since having the permission
+        // already means you can inject into any window.
         Objects.requireNonNull(event, "event must not be null");
         if (mode != InputEventInjectionSync.NONE
                 && mode != InputEventInjectionSync.WAIT_FOR_FINISHED
@@ -910,22 +855,41 @@
         }
 
         final int pid = Binder.getCallingPid();
-        final int uid = Binder.getCallingUid();
         final long ident = Binder.clearCallingIdentity();
+        final boolean injectIntoUid = targetUid != Process.INVALID_UID;
         final int result;
         try {
-            result = nativeInjectInputEvent(mPtr, event, pid, uid, mode,
-                    INJECTION_TIMEOUT_MILLIS, WindowManagerPolicy.FLAG_DISABLE_KEY_REPEAT);
+            result = mNative.injectInputEvent(event, injectIntoUid,
+                    targetUid, mode, INJECTION_TIMEOUT_MILLIS,
+                    WindowManagerPolicy.FLAG_DISABLE_KEY_REPEAT);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
         switch (result) {
-            case InputEventInjectionResult.PERMISSION_DENIED:
-                Slog.w(TAG, "Input event injection from pid " + pid + " permission denied.");
-                throw new SecurityException(
-                        "Injecting to another application requires INJECT_EVENTS permission");
             case InputEventInjectionResult.SUCCEEDED:
                 return true;
+            case InputEventInjectionResult.TARGET_MISMATCH:
+                if (!injectIntoUid) {
+                    throw new IllegalStateException("Injection should not result in TARGET_MISMATCH"
+                            + " when it is not targeted into to a specific uid.");
+                }
+                // Attempt to inject into a window owned by the instrumentation source of the caller
+                // because it is possible that tests adopt the identity of the shell when launching
+                // activities that they would like to inject into.
+                final ActivityManagerInternal ami =
+                        LocalServices.getService(ActivityManagerInternal.class);
+                Objects.requireNonNull(ami, "ActivityManagerInternal should not be null.");
+                final int instrUid = ami.getInstrumentationSourceUid(Binder.getCallingUid());
+                if (instrUid != Process.INVALID_UID && targetUid != instrUid) {
+                    Slog.w(TAG, "Targeted input event was not directed at a window owned by uid "
+                            + targetUid + ". Attempting to inject into window owned by "
+                            + "instrumentation source uid " + instrUid + ".");
+                    return injectInputEvent(event, mode, instrUid);
+                }
+                throw new IllegalArgumentException(
+                        "Targeted input event injection from pid " + pid
+                                + " was not directed at a window owned by uid "
+                                + targetUid + ".");
             case InputEventInjectionResult.TIMED_OUT:
                 Slog.w(TAG, "Input event injection from pid " + pid + " timed out.");
                 return false;
@@ -939,7 +903,7 @@
     @Override // Binder call
     public VerifiedInputEvent verifyInputEvent(InputEvent event) {
         Objects.requireNonNull(event, "event must not be null");
-        return nativeVerifyInputEvent(mPtr, event);
+        return mNative.verifyInputEvent(event);
     }
 
     /**
@@ -962,7 +926,7 @@
     // Binder call
     @Override
     public boolean isInputDeviceEnabled(int deviceId) {
-        return nativeIsInputDeviceEnabled(mPtr, deviceId);
+        return mNative.isInputDeviceEnabled(deviceId);
     }
 
     // Binder call
@@ -972,7 +936,7 @@
                 "enableInputDevice()")) {
             throw new SecurityException("Requires DISABLE_INPUT_DEVICE permission");
         }
-        nativeEnableInputDevice(mPtr, deviceId);
+        mNative.enableInputDevice(deviceId);
     }
 
     // Binder call
@@ -982,7 +946,7 @@
                 "disableInputDevice()")) {
             throw new SecurityException("Requires DISABLE_INPUT_DEVICE permission");
         }
-        nativeDisableInputDevice(mPtr, deviceId);
+        mNative.disableInputDevice(deviceId);
     }
 
     /**
@@ -1228,7 +1192,7 @@
             try {
                 if (mDataStore.setTouchCalibration(inputDeviceDescriptor, surfaceRotation,
                         calibration)) {
-                    nativeReloadCalibration(mPtr);
+                    mNative.reloadCalibration();
                 }
             } finally {
                 mDataStore.saveIfNeeded();
@@ -1740,11 +1704,11 @@
     }
 
     public void setFocusedApplication(int displayId, InputApplicationHandle application) {
-        nativeSetFocusedApplication(mPtr, displayId, application);
+        mNative.setFocusedApplication(displayId, application);
     }
 
     public void setFocusedDisplay(int displayId) {
-        nativeSetFocusedDisplay(mPtr, displayId);
+        mNative.setFocusedDisplay(displayId);
     }
 
     /** Clean up input window handles of the given display. */
@@ -1754,22 +1718,22 @@
             mPointerIconDisplayContext = null;
         }
 
-        nativeDisplayRemoved(mPtr, displayId);
+        mNative.displayRemoved(displayId);
     }
 
     @Override
     public void requestPointerCapture(IBinder inputChannelToken, boolean enabled) {
         Objects.requireNonNull(inputChannelToken, "event must not be null");
 
-        nativeRequestPointerCapture(mPtr, inputChannelToken, enabled);
+        mNative.requestPointerCapture(inputChannelToken, enabled);
     }
 
     public void setInputDispatchMode(boolean enabled, boolean frozen) {
-        nativeSetInputDispatchMode(mPtr, enabled, frozen);
+        mNative.setInputDispatchMode(enabled, frozen);
     }
 
     public void setSystemUiLightsOut(boolean lightsOut) {
-        nativeSetSystemUiLightsOut(mPtr, lightsOut);
+        mNative.setSystemUiLightsOut(lightsOut);
     }
 
     /**
@@ -1788,7 +1752,7 @@
      */
     public boolean transferTouchFocus(@NonNull InputChannel fromChannel,
             @NonNull InputChannel toChannel, boolean isDragDrop) {
-        return nativeTransferTouchFocus(mPtr, fromChannel.getToken(), toChannel.getToken(),
+        return mNative.transferTouchFocus(fromChannel.getToken(), toChannel.getToken(),
                 isDragDrop);
     }
 
@@ -1809,7 +1773,7 @@
             @NonNull IBinder toChannelToken) {
         Objects.nonNull(fromChannelToken);
         Objects.nonNull(toChannelToken);
-        return nativeTransferTouchFocus(mPtr, fromChannelToken, toChannelToken,
+        return mNative.transferTouchFocus(fromChannelToken, toChannelToken,
                 false /* isDragDrop */);
     }
 
@@ -1835,7 +1799,7 @@
     private void setPointerSpeedUnchecked(int speed) {
         speed = Math.min(Math.max(speed, InputManager.MIN_POINTER_SPEED),
                 InputManager.MAX_POINTER_SPEED);
-        nativeSetPointerSpeed(mPtr, speed);
+        mNative.setPointerSpeed(speed);
     }
 
     private void setPointerAcceleration(float acceleration, int displayId) {
@@ -1858,7 +1822,7 @@
 
     @GuardedBy("mAdditionalDisplayInputPropertiesLock")
     private void updatePointerAccelerationLocked(float acceleration) {
-        nativeSetPointerAcceleration(mPtr, acceleration);
+        mNative.setPointerAcceleration(acceleration);
     }
 
     private void setPointerIconVisible(boolean visible, int displayId) {
@@ -1883,12 +1847,12 @@
     private void updatePointerIconVisibleLocked(boolean visible) {
         if (visible) {
             if (mIconType == PointerIcon.TYPE_CUSTOM) {
-                nativeSetCustomPointerIcon(mPtr, mIcon);
+                mNative.setCustomPointerIcon(mIcon);
             } else {
-                nativeSetPointerIconType(mPtr, mIconType);
+                mNative.setPointerIconType(mIconType);
             }
         } else {
-            nativeSetPointerIconType(mPtr, PointerIcon.TYPE_NULL);
+            mNative.setPointerIconType(PointerIcon.TYPE_NULL);
         }
     }
 
@@ -1915,7 +1879,7 @@
 
     private void updateShowTouchesFromSettings() {
         int setting = getShowTouchesSetting(0);
-        nativeSetShowTouches(mPtr, setting != 0);
+        mNative.setShowTouches(setting != 0);
     }
 
     private void registerShowTouchesSettingObserver() {
@@ -1934,7 +1898,7 @@
                 mContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_LARGE_POINTER_ICON,
                 0, UserHandle.USER_CURRENT);
         PointerIcon.setUseLargeIcons(accessibilityConfig == 1);
-        nativeReloadPointerIcons(mPtr);
+        mNative.reloadPointerIcons();
     }
 
     private void registerAccessibilityLargePointerSettingObserver() {
@@ -1962,7 +1926,7 @@
                 (enabled ? "Enabling" : "Disabling") + " motion classifier because " + reason
                 + ": feature " + (featureEnabledFlag ? "enabled" : "disabled")
                 + ", long press timeout = " + timeout);
-        nativeSetMotionClassifierEnabled(mPtr, enabled);
+        mNative.setMotionClassifierEnabled(enabled);
     }
 
     private void registerLongPressTimeoutObserver() {
@@ -1990,7 +1954,7 @@
 
     private void updateBlockUntrustedTouchesModeFromSettings() {
         final int mode = InputManager.getInstance().getBlockUntrustedTouchesMode(mContext);
-        nativeSetBlockUntrustedTouchesMode(mPtr, mode);
+        mNative.setBlockUntrustedTouchesMode(mode);
     }
 
     private void registerMaximumObscuringOpacityForTouchSettingObserver() {
@@ -2012,7 +1976,7 @@
                     + ", it should be >= 0 and <= 1, rejecting update.");
             return;
         }
-        nativeSetMaximumObscuringOpacityForTouch(mPtr, opacity);
+        mNative.setMaximumObscuringOpacityForTouch(opacity);
     }
 
     private int getShowTouchesSetting(int defaultValue) {
@@ -2038,7 +2002,7 @@
             }
         }
         // TODO(b/215597605): trigger MousePositionTracker update
-        nativeNotifyPointerDisplayIdChanged(mPtr);
+        mNative.notifyPointerDisplayIdChanged();
     }
 
     private int getVirtualMousePointerDisplayId() {
@@ -2048,7 +2012,7 @@
     }
 
     private void setDisplayEligibilityForPointerCapture(int displayId, boolean isEligible) {
-        nativeSetDisplayEligibilityForPointerCapture(mPtr, displayId, isEligible);
+        mNative.setDisplayEligibilityForPointerCapture(displayId, isEligible);
     }
 
     private static class VibrationInfo {
@@ -2147,7 +2111,7 @@
         VibratorToken v = getVibratorToken(deviceId, token);
         synchronized (v) {
             v.mVibrating = true;
-            nativeVibrate(mPtr, deviceId, info.getPattern(), info.getAmplitudes(),
+            mNative.vibrate(deviceId, info.getPattern(), info.getAmplitudes(),
                     info.getRepeatIndex(), v.mTokenValue);
         }
     }
@@ -2155,13 +2119,13 @@
     // Binder call
     @Override
     public int[] getVibratorIds(int deviceId) {
-        return nativeGetVibratorIds(mPtr, deviceId);
+        return mNative.getVibratorIds(deviceId);
     }
 
     // Binder call
     @Override
     public boolean isVibrating(int deviceId) {
-        return nativeIsVibrating(mPtr, deviceId);
+        return mNative.isVibrating(deviceId);
     }
 
     // Binder call
@@ -2179,7 +2143,7 @@
             if (effect instanceof CombinedVibration.Mono) {
                 CombinedVibration.Mono mono = (CombinedVibration.Mono) effect;
                 VibrationInfo info = new VibrationInfo(mono.getEffect());
-                nativeVibrate(mPtr, deviceId, info.getPattern(), info.getAmplitudes(),
+                mNative.vibrate(deviceId, info.getPattern(), info.getAmplitudes(),
                         info.getRepeatIndex(), v.mTokenValue);
             } else if (effect instanceof CombinedVibration.Stereo) {
                 CombinedVibration.Stereo stereo = (CombinedVibration.Stereo) effect;
@@ -2198,7 +2162,7 @@
                     }
                     amplitudes.put(effects.keyAt(i), info.getAmplitudes());
                 }
-                nativeVibrateCombined(mPtr, deviceId, pattern, amplitudes, repeat,
+                mNative.vibrateCombined(deviceId, pattern, amplitudes, repeat,
                         v.mTokenValue);
             }
         }
@@ -2229,7 +2193,7 @@
     private void cancelVibrateIfNeeded(VibratorToken v) {
         synchronized (v) {
             if (v.mVibrating) {
-                nativeCancelVibrate(mPtr, v.mDeviceId, v.mTokenValue);
+                mNative.cancelVibrate(v.mDeviceId, v.mTokenValue);
                 v.mVibrating = false;
             }
         }
@@ -2325,13 +2289,13 @@
     // Binder call
     @Override
     public int getBatteryStatus(int deviceId) {
-        return nativeGetBatteryStatus(mPtr, deviceId);
+        return mNative.getBatteryStatus(deviceId);
     }
 
     // Binder call
     @Override
     public int getBatteryCapacity(int deviceId) {
-        return nativeGetBatteryCapacity(mPtr, deviceId);
+        return mNative.getBatteryCapacity(deviceId);
     }
 
     // Binder call
@@ -2347,10 +2311,10 @@
                 final AdditionalDisplayInputProperties properties =
                         mAdditionalDisplayInputProperties.get(mOverriddenPointerDisplayId);
                 if (properties == null || properties.pointerIconVisible) {
-                    nativeSetPointerIconType(mPtr, mIconType);
+                    mNative.setPointerIconType(mIconType);
                 }
             } else {
-                nativeSetPointerIconType(mPtr, mIconType);
+                mNative.setPointerIconType(mIconType);
             }
         }
     }
@@ -2368,10 +2332,10 @@
                 if (properties == null || properties.pointerIconVisible) {
                     // Only set the icon if it is not currently hidden; otherwise, it will be set
                     // once it's no longer hidden.
-                    nativeSetCustomPointerIcon(mPtr, mIcon);
+                    mNative.setCustomPointerIcon(mIcon);
                 }
             } else {
-                nativeSetCustomPointerIcon(mPtr, mIcon);
+                mNative.setCustomPointerIcon(mIcon);
             }
         }
     }
@@ -2395,7 +2359,7 @@
         synchronized (mAssociationsLock) {
             mRuntimeAssociations.put(inputPort, displayPort);
         }
-        nativeNotifyPortAssociationsChanged(mPtr);
+        mNative.notifyPortAssociationsChanged();
     }
 
     /**
@@ -2416,7 +2380,7 @@
         synchronized (mAssociationsLock) {
             mRuntimeAssociations.remove(inputPort);
         }
-        nativeNotifyPortAssociationsChanged(mPtr);
+        mNative.notifyPortAssociationsChanged();
     }
 
     @Override // Binder call
@@ -2433,7 +2397,7 @@
         synchronized (mAssociationsLock) {
             mUniqueIdAssociations.put(inputPort, displayUniqueId);
         }
-        nativeChangeUniqueIdAssociation(mPtr);
+        mNative.changeUniqueIdAssociation();
     }
 
     @Override // Binder call
@@ -2449,12 +2413,12 @@
         synchronized (mAssociationsLock) {
             mUniqueIdAssociations.remove(inputPort);
         }
-        nativeChangeUniqueIdAssociation(mPtr);
+        mNative.changeUniqueIdAssociation();
     }
 
     @Override // Binder call
     public InputSensorInfo[] getSensorList(int deviceId) {
-        return nativeGetSensorList(mPtr, deviceId);
+        return mNative.getSensorList(deviceId);
     }
 
     @Override // Binder call
@@ -2515,7 +2479,7 @@
             int callingPid = Binder.getCallingPid();
             SensorEventListenerRecord listener = mSensorEventListeners.get(callingPid);
             if (listener != null) {
-                return nativeFlushSensor(mPtr, deviceId, sensorType);
+                return mNative.flushSensor(deviceId, sensorType);
             }
             return false;
         }
@@ -2525,7 +2489,7 @@
     public boolean enableSensor(int deviceId, int sensorType, int samplingPeriodUs,
             int maxBatchReportLatencyUs) {
         synchronized (mInputDevicesLock) {
-            return nativeEnableSensor(mPtr, deviceId, sensorType, samplingPeriodUs,
+            return mNative.enableSensor(deviceId, sensorType, samplingPeriodUs,
                     maxBatchReportLatencyUs);
         }
     }
@@ -2533,7 +2497,7 @@
     @Override // Binder call
     public void disableSensor(int deviceId, int sensorType) {
         synchronized (mInputDevicesLock) {
-            nativeDisableSensor(mPtr, deviceId, sensorType);
+            mNative.disableSensor(deviceId, sensorType);
         }
     }
 
@@ -2572,7 +2536,7 @@
      */
     @Override // Binder call
     public List<Light> getLights(int deviceId) {
-        return nativeGetLights(mPtr, deviceId);
+        return mNative.getLights(deviceId);
     }
 
     /**
@@ -2585,11 +2549,11 @@
                     + "lightState " + lightState);
         }
         if (light.getType() == Light.LIGHT_TYPE_PLAYER_ID) {
-            nativeSetLightPlayerId(mPtr, deviceId, light.getId(), lightState.getPlayerId());
+            mNative.setLightPlayerId(deviceId, light.getId(), lightState.getPlayerId());
         } else {
             // Set ARGB format color to input device light
             // Refer to https://developer.android.com/reference/kotlin/android/graphics/Color
-            nativeSetLightColor(mPtr, deviceId, light.getId(), lightState.getColor());
+            mNative.setLightColor(deviceId, light.getId(), lightState.getColor());
         }
     }
 
@@ -2597,7 +2561,7 @@
      * Set multiple light states with multiple light ids for a specific input device.
      */
     private void setLightStatesInternal(int deviceId, int[] lightIds, LightState[] lightStates) {
-        final List<Light> lights = nativeGetLights(mPtr, deviceId);
+        final List<Light> lights = mNative.getLights(deviceId);
         SparseArray<Light> lightArray = new SparseArray<>();
         for (int i = 0; i < lights.size(); i++) {
             lightArray.put(lights.get(i).getId(), lights.get(i));
@@ -2633,8 +2597,8 @@
     @Override
     public @Nullable LightState getLightState(int deviceId, int lightId) {
         synchronized (mLightLock) {
-            int color = nativeGetLightColor(mPtr, deviceId, lightId);
-            int playerId = nativeGetLightPlayerId(mPtr, deviceId, lightId);
+            int color = mNative.getLightColor(deviceId, lightId);
+            int playerId = mNative.getLightPlayerId(deviceId, lightId);
 
             return new LightState(color, playerId);
         }
@@ -2686,7 +2650,7 @@
             throw new SecurityException("Requires MONITOR_INPUT permission");
         }
 
-        nativeCancelCurrentTouch(mPtr);
+        mNative.cancelCurrentTouch();
     }
 
     @Override
@@ -2694,7 +2658,7 @@
         if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
 
         pw.println("INPUT MANAGER (dumpsys input)\n");
-        String dumpStr = nativeDump(mPtr);
+        String dumpStr = mNative.dump();
         if (dumpStr != null) {
             pw.println(dumpStr);
         }
@@ -2761,8 +2725,12 @@
             }
         }
     }
-
     private boolean checkCallingPermission(String permission, String func) {
+        return checkCallingPermission(permission, func, false /*checkInstrumentationSource*/);
+    }
+
+    private boolean checkCallingPermission(String permission, String func,
+            boolean checkInstrumentationSource) {
         // Quick check: if the calling permission is me, it's all okay.
         if (Binder.getCallingPid() == Process.myPid()) {
             return true;
@@ -2771,6 +2739,18 @@
         if (mContext.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED) {
             return true;
         }
+
+        if (checkInstrumentationSource) {
+            final ActivityManagerInternal ami =
+                    LocalServices.getService(ActivityManagerInternal.class);
+            Objects.requireNonNull(ami, "ActivityManagerInternal should not be null.");
+            final int instrumentationUid = ami.getInstrumentationSourceUid(Binder.getCallingUid());
+            if (instrumentationUid != Process.INVALID_UID && mContext.checkPermission(permission,
+                    -1 /*pid*/, instrumentationUid) == PackageManager.PERMISSION_GRANTED) {
+                return true;
+            }
+        }
+
         String msg = "Permission Denial: " + func + " from pid="
                 + Binder.getCallingPid()
                 + ", uid=" + Binder.getCallingUid()
@@ -2787,7 +2767,7 @@
         synchronized (mLidSwitchLock) { /* Test if blocked by lid switch lock. */ }
         synchronized (mInputMonitors) { /* Test if blocked by input monitor lock. */ }
         synchronized (mAdditionalDisplayInputPropertiesLock) { /* Test if blocked by props lock */ }
-        nativeMonitor(mPtr);
+        mNative.monitor();
     }
 
     // Native callback.
@@ -3016,13 +2996,6 @@
 
     // Native callback.
     @SuppressWarnings("unused")
-    private boolean checkInjectEventsPermission(int injectorPid, int injectorUid) {
-        return mContext.checkPermission(android.Manifest.permission.INJECT_EVENTS,
-                injectorPid, injectorUid) == PackageManager.PERMISSION_GRANTED;
-    }
-
-    // Native callback.
-    @SuppressWarnings("unused")
     private void onPointerDownOutsideFocus(IBinder touchedToken) {
         mWindowManagerCallbacks.onPointerDownOutsideFocus(touchedToken);
     }
@@ -3121,7 +3094,7 @@
      * @return True if the device could dispatch to the given display, false otherwise.
      */
     public boolean canDispatchToDisplay(int deviceId, int displayId) {
-        return nativeCanDispatchToDisplay(mPtr, deviceId, displayId);
+        return mNative.canDispatchToDisplay(deviceId, displayId);
     }
 
     // Native callback.
@@ -3463,12 +3436,17 @@
 
         @Override
         public void sendInputEvent(InputEvent event, int policyFlags) {
+            if (!checkCallingPermission(android.Manifest.permission.INJECT_EVENTS,
+                    "sendInputEvent()")) {
+                throw new SecurityException(
+                        "The INJECT_EVENTS permission is required for injecting input events.");
+            }
             Objects.requireNonNull(event, "event must not be null");
 
             synchronized (mInputFilterLock) {
                 if (!mDisconnected) {
-                    nativeInjectInputEvent(mPtr, event, 0, 0,
-                            InputManager.INJECT_INPUT_EVENT_MODE_ASYNC, 0,
+                    mNative.injectInputEvent(event, false /* injectIntoUid */, -1 /* uid */,
+                            InputManager.INJECT_INPUT_EVENT_MODE_ASYNC, 0 /* timeout */,
                             policyFlags | WindowManagerPolicy.FLAG_FILTERED);
                 }
             }
@@ -3487,7 +3465,7 @@
 
         @Override
         public void pilferPointers() {
-            nativePilferPointers(mPtr, mInputChannelToken);
+            mNative.pilferPointers(mInputChannelToken);
         }
 
         @Override
@@ -3664,18 +3642,13 @@
         }
 
         @Override
-        public boolean injectInputEvent(InputEvent event, int mode) {
-            return injectInputEventInternal(event, mode);
-        }
-
-        @Override
         public void setInteractive(boolean interactive) {
-            nativeSetInteractive(mPtr, interactive);
+            mNative.setInteractive(interactive);
         }
 
         @Override
         public void toggleCapsLock(int deviceId) {
-            nativeToggleCapsLock(mPtr, deviceId);
+            mNative.toggleCapsLock(deviceId);
         }
 
         @Override
@@ -3746,7 +3719,7 @@
 
         @Override
         public void pilferPointers(IBinder token) {
-            nativePilferPointers(mPtr, token);
+            mNative.pilferPointers(token);
         }
     }
 
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
new file mode 100644
index 0000000..7178d20
--- /dev/null
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -0,0 +1,389 @@
+/*
+ * 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.input;
+
+import android.content.Context;
+import android.hardware.display.DisplayViewport;
+import android.hardware.input.InputSensorInfo;
+import android.hardware.lights.Light;
+import android.os.IBinder;
+import android.os.MessageQueue;
+import android.util.SparseArray;
+import android.view.InputApplicationHandle;
+import android.view.InputChannel;
+import android.view.InputEvent;
+import android.view.PointerIcon;
+import android.view.VerifiedInputEvent;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.List;
+
+/**
+ * An interface for the native methods of InputManagerService. We use a public interface so that
+ * this can be mocked for testing by Mockito.
+ */
+@VisibleForTesting
+public interface NativeInputManagerService {
+
+    void start();
+
+    void setDisplayViewports(DisplayViewport[] viewports);
+
+    int getScanCodeState(int deviceId, int sourceMask, int scanCode);
+
+    int getKeyCodeState(int deviceId, int sourceMask, int keyCode);
+
+    int getSwitchState(int deviceId, int sourceMask, int sw);
+
+    boolean hasKeys(int deviceId, int sourceMask, int[] keyCodes, boolean[] keyExists);
+
+    int getKeyCodeForKeyLocation(int deviceId, int locationKeyCode);
+
+    InputChannel createInputChannel(String name);
+
+    InputChannel createInputMonitor(int displayId, String name, int pid);
+
+    void removeInputChannel(IBinder connectionToken);
+
+    void pilferPointers(IBinder token);
+
+    void setInputFilterEnabled(boolean enable);
+
+    boolean setInTouchMode(boolean inTouchMode, int pid, int uid, boolean hasPermission);
+
+    void setMaximumObscuringOpacityForTouch(float opacity);
+
+    void setBlockUntrustedTouchesMode(int mode);
+
+    int injectInputEvent(InputEvent event, boolean injectIntoUid, int uid, int syncMode,
+            int timeoutMillis, int policyFlags);
+
+    VerifiedInputEvent verifyInputEvent(InputEvent event);
+
+    void toggleCapsLock(int deviceId);
+
+    void displayRemoved(int displayId);
+
+    void setInputDispatchMode(boolean enabled, boolean frozen);
+
+    void setSystemUiLightsOut(boolean lightsOut);
+
+    void setFocusedApplication(int displayId, InputApplicationHandle application);
+
+    void setFocusedDisplay(int displayId);
+
+    boolean transferTouchFocus(IBinder fromChannelToken, IBinder toChannelToken,
+            boolean isDragDrop);
+
+    boolean transferTouch(IBinder destChannelToken);
+
+    void setPointerSpeed(int speed);
+
+    void setPointerAcceleration(float acceleration);
+
+    void setShowTouches(boolean enabled);
+
+    void setInteractive(boolean interactive);
+
+    void reloadCalibration();
+
+    void vibrate(int deviceId, long[] pattern, int[] amplitudes, int repeat, int token);
+
+    void vibrateCombined(int deviceId, long[] pattern, SparseArray<int[]> amplitudes,
+            int repeat, int token);
+
+    void cancelVibrate(int deviceId, int token);
+
+    boolean isVibrating(int deviceId);
+
+    int[] getVibratorIds(int deviceId);
+
+    int getBatteryCapacity(int deviceId);
+
+    int getBatteryStatus(int deviceId);
+
+    List<Light> getLights(int deviceId);
+
+    int getLightPlayerId(int deviceId, int lightId);
+
+    int getLightColor(int deviceId, int lightId);
+
+    void setLightPlayerId(int deviceId, int lightId, int playerId);
+
+    void setLightColor(int deviceId, int lightId, int color);
+
+    void reloadKeyboardLayouts();
+
+    void reloadDeviceAliases();
+
+    String dump();
+
+    void monitor();
+
+    boolean isInputDeviceEnabled(int deviceId);
+
+    void enableInputDevice(int deviceId);
+
+    void disableInputDevice(int deviceId);
+
+    void setPointerIconType(int iconId);
+
+    void reloadPointerIcons();
+
+    void setCustomPointerIcon(PointerIcon icon);
+
+    void requestPointerCapture(IBinder windowToken, boolean enabled);
+
+    boolean canDispatchToDisplay(int deviceId, int displayId);
+
+    void notifyPortAssociationsChanged();
+
+    void changeUniqueIdAssociation();
+
+    void notifyPointerDisplayIdChanged();
+
+    void setDisplayEligibilityForPointerCapture(int displayId, boolean enabled);
+
+    void setMotionClassifierEnabled(boolean enabled);
+
+    InputSensorInfo[] getSensorList(int deviceId);
+
+    boolean flushSensor(int deviceId, int sensorType);
+
+    boolean enableSensor(int deviceId, int sensorType, int samplingPeriodUs,
+            int maxBatchReportLatencyUs);
+
+    void disableSensor(int deviceId, int sensorType);
+
+    void cancelCurrentTouch();
+
+    /** The native implementation of InputManagerService methods. */
+    class NativeImpl implements NativeInputManagerService {
+        /** Pointer to native input manager service object, used by native code. */
+        @SuppressWarnings({"unused", "FieldCanBeLocal"})
+        private final long mPtr;
+
+        NativeImpl(InputManagerService service, Context context, MessageQueue messageQueue) {
+            mPtr = init(service, context, messageQueue);
+        }
+
+        private native long init(InputManagerService service, Context context,
+                MessageQueue messageQueue);
+
+        @Override
+        public native void start();
+
+        @Override
+        public native void setDisplayViewports(DisplayViewport[] viewports);
+
+        @Override
+        public native int getScanCodeState(int deviceId, int sourceMask, int scanCode);
+
+        @Override
+        public native int getKeyCodeState(int deviceId, int sourceMask, int keyCode);
+
+        @Override
+        public native int getSwitchState(int deviceId, int sourceMask, int sw);
+
+        @Override
+        public native boolean hasKeys(int deviceId, int sourceMask, int[] keyCodes,
+                boolean[] keyExists);
+
+        @Override
+        public native int getKeyCodeForKeyLocation(int deviceId, int locationKeyCode);
+
+        @Override
+        public native InputChannel createInputChannel(String name);
+
+        @Override
+        public native InputChannel createInputMonitor(int displayId, String name, int pid);
+
+        @Override
+        public native void removeInputChannel(IBinder connectionToken);
+
+        @Override
+        public native void pilferPointers(IBinder token);
+
+        @Override
+        public native void setInputFilterEnabled(boolean enable);
+
+        @Override
+        public native boolean setInTouchMode(boolean inTouchMode, int pid, int uid,
+                boolean hasPermission);
+
+        @Override
+        public native void setMaximumObscuringOpacityForTouch(float opacity);
+
+        @Override
+        public native void setBlockUntrustedTouchesMode(int mode);
+
+        @Override
+        public native int injectInputEvent(InputEvent event, boolean injectIntoUid, int uid,
+                int syncMode,
+                int timeoutMillis, int policyFlags);
+
+        @Override
+        public native VerifiedInputEvent verifyInputEvent(InputEvent event);
+
+        @Override
+        public native void toggleCapsLock(int deviceId);
+
+        @Override
+        public native void displayRemoved(int displayId);
+
+        @Override
+        public native void setInputDispatchMode(boolean enabled, boolean frozen);
+
+        @Override
+        public native void setSystemUiLightsOut(boolean lightsOut);
+
+        @Override
+        public native void setFocusedApplication(int displayId, InputApplicationHandle application);
+
+        @Override
+        public native void setFocusedDisplay(int displayId);
+
+        @Override
+        public native boolean transferTouchFocus(IBinder fromChannelToken, IBinder toChannelToken,
+                boolean isDragDrop);
+
+        @Override
+        public native boolean transferTouch(IBinder destChannelToken);
+
+        @Override
+        public native void setPointerSpeed(int speed);
+
+        @Override
+        public native void setPointerAcceleration(float acceleration);
+
+        @Override
+        public native void setShowTouches(boolean enabled);
+
+        @Override
+        public native void setInteractive(boolean interactive);
+
+        @Override
+        public native void reloadCalibration();
+
+        @Override
+        public native void vibrate(int deviceId, long[] pattern, int[] amplitudes, int repeat,
+                int token);
+
+        @Override
+        public native void vibrateCombined(int deviceId, long[] pattern,
+                SparseArray<int[]> amplitudes,
+                int repeat, int token);
+
+        @Override
+        public native void cancelVibrate(int deviceId, int token);
+
+        @Override
+        public native boolean isVibrating(int deviceId);
+
+        @Override
+        public native int[] getVibratorIds(int deviceId);
+
+        @Override
+        public native int getBatteryCapacity(int deviceId);
+
+        @Override
+        public native int getBatteryStatus(int deviceId);
+
+        @Override
+        public native List<Light> getLights(int deviceId);
+
+        @Override
+        public native int getLightPlayerId(int deviceId, int lightId);
+
+        @Override
+        public native int getLightColor(int deviceId, int lightId);
+
+        @Override
+        public native void setLightPlayerId(int deviceId, int lightId, int playerId);
+
+        @Override
+        public native void setLightColor(int deviceId, int lightId, int color);
+
+        @Override
+        public native void reloadKeyboardLayouts();
+
+        @Override
+        public native void reloadDeviceAliases();
+
+        @Override
+        public native String dump();
+
+        @Override
+        public native void monitor();
+
+        @Override
+        public native boolean isInputDeviceEnabled(int deviceId);
+
+        @Override
+        public native void enableInputDevice(int deviceId);
+
+        @Override
+        public native void disableInputDevice(int deviceId);
+
+        @Override
+        public native void setPointerIconType(int iconId);
+
+        @Override
+        public native void reloadPointerIcons();
+
+        @Override
+        public native void setCustomPointerIcon(PointerIcon icon);
+
+        @Override
+        public native void requestPointerCapture(IBinder windowToken, boolean enabled);
+
+        @Override
+        public native boolean canDispatchToDisplay(int deviceId, int displayId);
+
+        @Override
+        public native void notifyPortAssociationsChanged();
+
+        @Override
+        public native void changeUniqueIdAssociation();
+
+        @Override
+        public native void notifyPointerDisplayIdChanged();
+
+        @Override
+        public native void setDisplayEligibilityForPointerCapture(int displayId, boolean enabled);
+
+        @Override
+        public native void setMotionClassifierEnabled(boolean enabled);
+
+        @Override
+        public native InputSensorInfo[] getSensorList(int deviceId);
+
+        @Override
+        public native boolean flushSensor(int deviceId, int sensorType);
+
+        @Override
+        public native boolean enableSensor(int deviceId, int sensorType, int samplingPeriodUs,
+                int maxBatchReportLatencyUs);
+
+        @Override
+        public native void disableSensor(int deviceId, int sensorType);
+
+        @Override
+        public native void cancelCurrentTouch();
+    }
+}
diff --git a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
index db81393..37a4869 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
@@ -30,8 +30,6 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
-import android.os.Binder;
 import android.os.HandlerThread;
 import android.os.LocaleList;
 import android.os.RemoteException;
@@ -78,7 +76,7 @@
     private static final Duration STAGE_DATA_RETENTION_PERIOD = Duration.ofDays(3);
 
     private final LocaleManagerService mLocaleManagerService;
-    private final PackageManagerInternal mPackageManagerInternal;
+    private final PackageManager mPackageManager;
     private final Clock mClock;
     private final Context mContext;
     private final Object mStagedDataLock = new Object();
@@ -90,18 +88,18 @@
     private final BroadcastReceiver mUserMonitor;
 
     LocaleManagerBackupHelper(LocaleManagerService localeManagerService,
-            PackageManagerInternal pmInternal, HandlerThread broadcastHandlerThread) {
-        this(localeManagerService.mContext, localeManagerService, pmInternal, Clock.systemUTC(),
+            PackageManager packageManager, HandlerThread broadcastHandlerThread) {
+        this(localeManagerService.mContext, localeManagerService, packageManager, Clock.systemUTC(),
                 new SparseArray<>(), broadcastHandlerThread);
     }
 
     @VisibleForTesting LocaleManagerBackupHelper(Context context,
             LocaleManagerService localeManagerService,
-            PackageManagerInternal pmInternal, Clock clock, SparseArray<StagedData> stagedData,
+            PackageManager packageManager, Clock clock, SparseArray<StagedData> stagedData,
             HandlerThread broadcastHandlerThread) {
         mContext = context;
         mLocaleManagerService = localeManagerService;
-        mPackageManagerInternal = pmInternal;
+        mPackageManager = packageManager;
         mClock = clock;
         mStagedData = stagedData;
 
@@ -130,8 +128,8 @@
         }
 
         HashMap<String, String> pkgStates = new HashMap<>();
-        for (ApplicationInfo appInfo : mPackageManagerInternal.getInstalledApplications(/*flags*/0,
-                userId, Binder.getCallingUid())) {
+        for (ApplicationInfo appInfo : mPackageManager.getInstalledApplicationsAsUser(
+                PackageManager.ApplicationInfoFlags.of(0), userId)) {
             try {
                 LocaleList appLocales = mLocaleManagerService.getApplicationLocales(
                         appInfo.packageName,
diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java
index 924db6a..fc7be7f 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerService.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerService.java
@@ -28,7 +28,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
+import android.content.pm.PackageManager.PackageInfoFlags;
 import android.content.res.Configuration;
 import android.os.Binder;
 import android.os.HandlerThread;
@@ -60,7 +60,7 @@
     private final LocaleManagerService.LocaleManagerBinderService mBinderService;
     private ActivityTaskManagerInternal mActivityTaskManagerInternal;
     private ActivityManagerInternal mActivityManagerInternal;
-    private PackageManagerInternal mPackageManagerInternal;
+    private PackageManager mPackageManager;
 
     private LocaleManagerBackupHelper mBackupHelper;
 
@@ -74,7 +74,7 @@
         mBinderService = new LocaleManagerBinderService();
         mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
         mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
-        mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
+        mPackageManager = mContext.getPackageManager();
 
         HandlerThread broadcastHandlerThread = new HandlerThread(TAG,
                 Process.THREAD_PRIORITY_BACKGROUND);
@@ -90,7 +90,7 @@
         });
 
         mBackupHelper = new LocaleManagerBackupHelper(this,
-                mPackageManagerInternal, broadcastHandlerThread);
+                mPackageManager, broadcastHandlerThread);
 
         mPackageMonitor = new LocaleManagerServicePackageMonitor(mBackupHelper,
                 systemAppUpdateTracker);
@@ -102,7 +102,7 @@
     @VisibleForTesting
     LocaleManagerService(Context context, ActivityTaskManagerInternal activityTaskManagerInternal,
             ActivityManagerInternal activityManagerInternal,
-            PackageManagerInternal packageManagerInternal,
+            PackageManager packageManager,
             LocaleManagerBackupHelper localeManagerBackupHelper,
             PackageMonitor packageMonitor) {
         super(context);
@@ -110,7 +110,7 @@
         mBinderService = new LocaleManagerBinderService();
         mActivityTaskManagerInternal = activityTaskManagerInternal;
         mActivityManagerInternal = activityManagerInternal;
-        mPackageManagerInternal = packageManagerInternal;
+        mPackageManager = packageManager;
         mBackupHelper = localeManagerBackupHelper;
         mPackageMonitor = packageMonitor;
     }
@@ -186,7 +186,7 @@
             userId = mActivityManagerInternal.handleIncomingUser(
                     Binder.getCallingPid(), Binder.getCallingUid(), userId,
                     false /* allowAll */, ActivityManagerInternal.ALLOW_NON_FULL,
-                    "setApplicationLocales", appPackageName);
+                    "setApplicationLocales", /* callerPackage= */ null);
 
             // This function handles two types of set operations:
             // 1.) A normal, non-privileged app setting its own locale.
@@ -355,7 +355,7 @@
         userId = mActivityManagerInternal.handleIncomingUser(
                 Binder.getCallingPid(), Binder.getCallingUid(), userId,
                 false /* allowAll */, ActivityManagerInternal.ALLOW_NON_FULL,
-                "getApplicationLocales", appPackageName);
+                "getApplicationLocales", /* callerPackage= */ null);
 
         // This function handles three types of query operations:
         // 1.) A normal, non-privileged app querying its own locale.
@@ -419,8 +419,12 @@
     }
 
     private int getPackageUid(String appPackageName, int userId) {
-        return mPackageManagerInternal
-                .getPackageUid(appPackageName, /* flags */ 0, userId);
+        try {
+            return mPackageManager
+                    .getPackageUidAsUser(appPackageName, PackageInfoFlags.of(0), userId);
+        } catch (PackageManager.NameNotFoundException e) {
+            return Process.INVALID_UID;
+        }
     }
 
     @Nullable
diff --git a/services/core/java/com/android/server/locales/OWNERS b/services/core/java/com/android/server/locales/OWNERS
index be284a7..4d93bff 100644
--- a/services/core/java/com/android/server/locales/OWNERS
+++ b/services/core/java/com/android/server/locales/OWNERS
@@ -1,3 +1,4 @@
 roosa@google.com
 pratyushmore@google.com
 goldmanj@google.com
+ankitavyas@google.com
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index fac5106..31d5136 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -94,7 +94,6 @@
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.Preconditions;
 import com.android.server.FgThread;
@@ -279,6 +278,9 @@
                 this::onLocationUserSettingsChanged);
         mInjector.getSettingsHelper().addOnLocationEnabledChangedListener(
                 this::onLocationModeChanged);
+        mInjector.getSettingsHelper().addAdasAllowlistChangedListener(
+                () -> refreshAppOpsRestrictions(UserHandle.USER_ALL)
+        );
         mInjector.getSettingsHelper().addIgnoreSettingsAllowlistChangedListener(
                 () -> refreshAppOpsRestrictions(UserHandle.USER_ALL));
         mInjector.getUserInfoHelper().addListener((userId, change) -> {
@@ -823,12 +825,6 @@
                 throw new IllegalArgumentException(
                         "adas gnss bypass requests are only allowed on the \"gps\" provider");
             }
-            if (!ArrayUtils.contains(mContext.getResources().getStringArray(
-                    com.android.internal.R.array.config_locationDriverAssistancePackageNames),
-                    identity.getPackageName())) {
-                throw new SecurityException(
-                        "only verified adas packages may use adas gnss bypass requests");
-            }
             if (!isLocationProvider) {
                 LocationPermissions.enforceCallingOrSelfBypassPermission(mContext);
             }
@@ -923,12 +919,6 @@
                 throw new IllegalArgumentException(
                         "adas gnss bypass requests are only allowed on the \"gps\" provider");
             }
-            if (!ArrayUtils.contains(mContext.getResources().getStringArray(
-                    com.android.internal.R.array.config_locationDriverAssistancePackageNames),
-                    identity.getPackageName())) {
-                throw new SecurityException(
-                        "only verified adas packages may use adas gnss bypass requests");
-            }
             if (!isLocationProvider) {
                 LocationPermissions.enforceCallingOrSelfBypassPermission(mContext);
             }
@@ -1542,6 +1532,7 @@
                 }
             }
             builder.add(mInjector.getSettingsHelper().getIgnoreSettingsAllowlist());
+            builder.add(mInjector.getSettingsHelper().getAdasAllowlist());
             allowedPackages = builder.build();
         }
 
diff --git a/services/core/java/com/android/server/location/injector/SettingsHelper.java b/services/core/java/com/android/server/location/injector/SettingsHelper.java
index 148afa7..490bfe1 100644
--- a/services/core/java/com/android/server/location/injector/SettingsHelper.java
+++ b/services/core/java/com/android/server/location/injector/SettingsHelper.java
@@ -146,6 +146,20 @@
     public abstract void removeOnGnssMeasurementsFullTrackingEnabledChangedListener(
             GlobalSettingChangedListener listener);
 
+    /** Retrieve adas allowlist. */
+    public abstract PackageTagsList getAdasAllowlist();
+
+    /**
+     * Add a listener for changes to the ADAS settings package allowlist. Callbacks occur on an
+     * unspecified thread.
+     */
+    public abstract void addAdasAllowlistChangedListener(GlobalSettingChangedListener listener);
+
+    /**
+     * Remove a listener for changes to the ADAS package allowlist.
+     */
+    public abstract void removeAdasAllowlistChangedListener(GlobalSettingChangedListener listener);
+
     /**
      * Retrieve the ignore location settings package+tags allowlist setting.
      */
diff --git a/services/core/java/com/android/server/location/injector/SystemSettingsHelper.java b/services/core/java/com/android/server/location/injector/SystemSettingsHelper.java
index 3e8da7d..777683e 100644
--- a/services/core/java/com/android/server/location/injector/SystemSettingsHelper.java
+++ b/services/core/java/com/android/server/location/injector/SystemSettingsHelper.java
@@ -16,6 +16,7 @@
 
 package com.android.server.location.injector;
 
+import static android.location.LocationDeviceConfig.ADAS_SETTINGS_ALLOWLIST;
 import static android.location.LocationDeviceConfig.IGNORE_SETTINGS_ALLOWLIST;
 import static android.provider.Settings.Global.ENABLE_GNSS_RAW_MEAS_FULL_TRACKING;
 import static android.provider.Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS;
@@ -80,6 +81,7 @@
     private final StringListCachedSecureSetting mLocationPackageBlacklist;
     private final StringListCachedSecureSetting mLocationPackageWhitelist;
     private final StringSetCachedGlobalSetting mBackgroundThrottlePackageWhitelist;
+    private final PackageTagsListSetting mAdasPackageAllowlist;
     private final PackageTagsListSetting mIgnoreSettingsPackageAllowlist;
 
     public SystemSettingsHelper(Context context) {
@@ -98,6 +100,9 @@
                 LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST,
                 () -> SystemConfig.getInstance().getAllowUnthrottledLocation(),
                 FgThread.getHandler());
+        mAdasPackageAllowlist = new PackageTagsListSetting(
+                ADAS_SETTINGS_ALLOWLIST,
+                () -> SystemConfig.getInstance().getAllowAdasLocationSettings());
         mIgnoreSettingsPackageAllowlist = new PackageTagsListSetting(
                 IGNORE_SETTINGS_ALLOWLIST,
                 () -> SystemConfig.getInstance().getAllowIgnoreLocationSettings());
@@ -233,6 +238,21 @@
     }
 
     @Override
+    public PackageTagsList getAdasAllowlist() {
+        return mAdasPackageAllowlist.getValue();
+    }
+
+    @Override
+    public void addAdasAllowlistChangedListener(GlobalSettingChangedListener listener) {
+        mAdasPackageAllowlist.addListener(listener);
+    }
+
+    @Override
+    public void removeAdasAllowlistChangedListener(GlobalSettingChangedListener listener) {
+        mAdasPackageAllowlist.removeListener(listener);
+    }
+
+    @Override
     public PackageTagsList getIgnoreSettingsAllowlist() {
         return mIgnoreSettingsPackageAllowlist.getValue();
     }
@@ -359,11 +379,19 @@
 
         PackageTagsList ignoreSettingsAllowlist = mIgnoreSettingsPackageAllowlist.getValue();
         if (!ignoreSettingsAllowlist.isEmpty()) {
-            ipw.println("Bypass Allow Packages:");
+            ipw.println("Emergency Bypass Allow Packages:");
             ipw.increaseIndent();
             ignoreSettingsAllowlist.dump(ipw);
             ipw.decreaseIndent();
         }
+
+        PackageTagsList adasPackageAllowlist = mAdasPackageAllowlist.getValue();
+        if (!adasPackageAllowlist.isEmpty()) {
+            ipw.println("ADAS Bypass Allow Packages:");
+            ipw.increaseIndent();
+            adasPackageAllowlist.dump(ipw);
+            ipw.decreaseIndent();
+        }
     }
 
     private abstract static class ObservingSetting extends ContentObserver {
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index 721ef1e..1235352 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -699,6 +699,9 @@
                 } else if (!mLocationSettings.getUserSettings(
                         getIdentity().getUserId()).isAdasGnssLocationEnabled()) {
                     adasGnssBypass = false;
+                } else if (!mSettingsHelper.getAdasAllowlist().contains(
+                        getIdentity().getPackageName(), getIdentity().getAttributionTag())) {
+                    adasGnssBypass = false;
                 }
 
                 builder.setAdasGnssBypass(adasGnssBypass);
@@ -1406,6 +1409,8 @@
             this::onAppForegroundChanged;
     private final GlobalSettingChangedListener mBackgroundThrottleIntervalChangedListener =
             this::onBackgroundThrottleIntervalChanged;
+    private final GlobalSettingChangedListener mAdasPackageAllowlistChangedListener =
+            this::onAdasAllowlistChanged;
     private final GlobalSettingChangedListener mIgnoreSettingsPackageWhitelistChangedListener =
             this::onIgnoreSettingsWhitelistChanged;
     private final LocationPowerSaveModeChangedListener mLocationPowerSaveModeChangedListener =
@@ -1710,6 +1715,9 @@
             } else if (!mLocationSettings.getUserSettings(
                     identity.getUserId()).isAdasGnssLocationEnabled()) {
                 adasGnssBypass = false;
+            } else if (!mSettingsHelper.getAdasAllowlist().contains(
+                    identity.getPackageName(), identity.getAttributionTag())) {
+                adasGnssBypass = false;
             }
 
             builder.setAdasGnssBypass(adasGnssBypass);
@@ -1979,6 +1987,8 @@
                 mBackgroundThrottlePackageWhitelistChangedListener);
         mSettingsHelper.addOnLocationPackageBlacklistChangedListener(
                 mLocationPackageBlacklistChangedListener);
+        mSettingsHelper.addAdasAllowlistChangedListener(
+                mAdasPackageAllowlistChangedListener);
         mSettingsHelper.addIgnoreSettingsAllowlistChangedListener(
                 mIgnoreSettingsPackageWhitelistChangedListener);
         mLocationPermissionsHelper.addListener(mLocationPermissionsListener);
@@ -2000,6 +2010,7 @@
                 mBackgroundThrottlePackageWhitelistChangedListener);
         mSettingsHelper.removeOnLocationPackageBlacklistChangedListener(
                 mLocationPackageBlacklistChangedListener);
+        mSettingsHelper.removeAdasAllowlistChangedListener(mAdasPackageAllowlistChangedListener);
         mSettingsHelper.removeIgnoreSettingsAllowlistChangedListener(
                 mIgnoreSettingsPackageWhitelistChangedListener);
         mLocationPermissionsHelper.removeListener(mLocationPermissionsListener);
@@ -2422,6 +2433,12 @@
         }
     }
 
+    private void onAdasAllowlistChanged() {
+        synchronized (mLock) {
+            updateRegistrations(Registration::onProviderLocationRequestChanged);
+        }
+    }
+
     private void onIgnoreSettingsWhitelistChanged() {
         synchronized (mLock) {
             updateRegistrations(Registration::onProviderLocationRequestChanged);
diff --git a/services/core/java/com/android/server/logcat/LogAccessDialogActivity.java b/services/core/java/com/android/server/logcat/LogAccessDialogActivity.java
index 8be90e0c..b45bfb1 100644
--- a/services/core/java/com/android/server/logcat/LogAccessDialogActivity.java
+++ b/services/core/java/com/android/server/logcat/LogAccessDialogActivity.java
@@ -30,6 +30,7 @@
 import android.os.UserHandle;
 import android.os.logcat.ILogcatManagerService;
 import android.util.Slog;
+import android.view.InflateException;
 import android.view.View;
 import android.widget.Button;
 import android.widget.TextView;
@@ -56,33 +57,46 @@
     private String mAlertTitle;
     private AlertDialog.Builder mAlertDialog;
     private AlertDialog mAlert;
+    private View mAlertView;
 
     private static final int DIALOG_TIME_OUT = Build.IS_DEBUGGABLE ? 60000 : 300000;
     private static final int MSG_DISMISS_DIALOG = 0;
 
-
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        mContext = this;
 
-        Intent intent = getIntent();
-        mPackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
-        mUid = intent.getIntExtra("com.android.server.logcat.uid", 0);
-        mGid = intent.getIntExtra("com.android.server.logcat.gid", 0);
-        mPid = intent.getIntExtra("com.android.server.logcat.pid", 0);
-        mFd = intent.getIntExtra("com.android.server.logcat.fd", 0);
-        mAlertTitle = getTitleString(mContext, mPackageName, mUid);
+        try {
+            mContext = this;
 
-        if (mAlertTitle != null) {
+            // retrieve Intent extra information
+            Intent intent = getIntent();
+            getIntentInfo(intent);
 
+            // retrieve the title string from passed intent extra
+            mAlertTitle = getTitleString(mContext, mPackageName, mUid);
+
+            // creaet View
+            mAlertView = createView();
+
+            // create AlertDialog
             mAlertDialog = new AlertDialog.Builder(this);
-            mAlertDialog.setView(createView());
+            mAlertDialog.setView(mAlertView);
 
+            // show Alert
             mAlert = mAlertDialog.create();
             mAlert.show();
+
+            // set Alert Timeout
             mHandler.sendEmptyMessageDelayed(MSG_DISMISS_DIALOG, DIALOG_TIME_OUT);
 
+        } catch (Exception e) {
+            try {
+                Slog.e(TAG, "onCreate failed, declining the logd access", e);
+                mLogcatManagerService.decline(mUid, mGid, mPid, mFd);
+            } catch (RemoteException ex) {
+                Slog.e(TAG, "Fails to call remote functions", ex);
+            }
         }
     }
 
@@ -95,6 +109,19 @@
         mAlert = null;
     }
 
+    private void getIntentInfo(Intent intent) throws Exception {
+
+        if (intent == null) {
+            throw new NullPointerException("Intent is null");
+        }
+
+        mPackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+        mUid = intent.getIntExtra("com.android.server.logcat.uid", 0);
+        mGid = intent.getIntExtra("com.android.server.logcat.gid", 0);
+        mPid = intent.getIntExtra("com.android.server.logcat.pid", 0);
+        mFd = intent.getIntExtra("com.android.server.logcat.fd", 0);
+    }
+
     private Handler mHandler = new Handler() {
         public void handleMessage(android.os.Message msg) {
             switch (msg.what) {
@@ -116,26 +143,41 @@
         }
     };
 
-    private String getTitleString(Context context, String callingPackage, int uid) {
+    private String getTitleString(Context context, String callingPackage, int uid)
+            throws Exception {
+
         PackageManager pm = context.getPackageManager();
-        try {
-            return context.getString(
-                    com.android.internal.R.string.log_access_confirmation_title,
-                    pm.getApplicationInfoAsUser(callingPackage,
-                            PackageManager.MATCH_DIRECT_BOOT_AUTO,
-                            UserHandle.getUserId(uid)).loadLabel(pm));
-        } catch (NameNotFoundException e) {
-            Slog.e(TAG, "App name is unknown.", e);
-            return null;
+        if (pm == null) {
+            throw new NullPointerException("PackageManager is null");
         }
+
+        CharSequence appLabel = pm.getApplicationInfoAsUser(callingPackage,
+                PackageManager.MATCH_DIRECT_BOOT_AUTO,
+                UserHandle.getUserId(uid)).loadLabel(pm);
+        if (appLabel == null) {
+            throw new NameNotFoundException("Application Label is null");
+        }
+
+        return context.getString(com.android.internal.R.string.log_access_confirmation_title,
+            appLabel);
     }
 
-    private View createView() {
+    /**
+     * Returns the dialog view.
+     * If we cannot retrieve the package name, it returns null and we decline the full device log
+     * access
+     */
+    private View createView() throws Exception {
+
         final View view = getLayoutInflater().inflate(
                 R.layout.log_access_user_consent_dialog_permission, null /*root*/);
 
+        if (view == null) {
+            throw new InflateException();
+        }
+
         ((TextView) view.findViewById(R.id.log_access_dialog_title))
-                .setText(mAlertTitle);
+            .setText(mAlertTitle);
 
         Button button_allow = (Button) view.findViewById(R.id.log_access_dialog_allow_button);
         button_allow.setOnClickListener(this);
@@ -144,6 +186,7 @@
         button_deny.setOnClickListener(this);
 
         return view;
+
     }
 
     @Override
diff --git a/services/core/java/com/android/server/logcat/LogcatManagerService.java b/services/core/java/com/android/server/logcat/LogcatManagerService.java
index 0aa384c..4c265ad 100644
--- a/services/core/java/com/android/server/logcat/LogcatManagerService.java
+++ b/services/core/java/com/android/server/logcat/LogcatManagerService.java
@@ -102,16 +102,27 @@
         }
     }
 
-    private void showDialog(int uid, int gid, int pid, int fd) {
+    /**
+     * Returns the package name.
+     * If we cannot retrieve the package name, it returns null and we decline the full device log
+     * access
+     */
+    private String getPackageName(int uid, int gid, int pid, int fd) {
+
         final ActivityManagerInternal activityManagerInternal =
                 LocalServices.getService(ActivityManagerInternal.class);
+        if (activityManagerInternal != null) {
+            String packageName = activityManagerInternal.getPackageNameByPid(pid);
+            if (packageName != null) {
+                return packageName;
+            }
+        }
 
         PackageManager pm = mContext.getPackageManager();
-        String packageName = activityManagerInternal.getPackageNameByPid(pid);
-        if (packageName != null) {
-            Intent mIntent = createIntent(packageName, uid, gid, pid, fd);
-            mContext.startActivityAsUser(mIntent, UserHandle.SYSTEM);
-            return;
+        if (pm == null) {
+            // Decline the logd access if PackageManager is null
+            Slog.e(TAG, "PackageManager is null, declining the logd access");
+            return null;
         }
 
         String[] packageNames = pm.getPackagesForUid(uid);
@@ -119,21 +130,19 @@
         if (ArrayUtils.isEmpty(packageNames)) {
             // Decline the logd access if the app name is unknown
             Slog.e(TAG, "Unknown calling package name, declining the logd access");
-            declineLogdAccess(uid, gid, pid, fd);
-            return;
+            return null;
         }
 
         String firstPackageName = packageNames[0];
 
-        if (firstPackageName.isEmpty() || firstPackageName == null) {
+        if (firstPackageName == null || firstPackageName.isEmpty()) {
             // Decline the logd access if the package name from uid is unknown
             Slog.e(TAG, "Unknown calling package name, declining the logd access");
-            declineLogdAccess(uid, gid, pid, fd);
-            return;
+            return null;
         }
 
-        final Intent mIntent = createIntent(firstPackageName, uid, gid, pid, fd);
-        mContext.startActivityAsUser(mIntent, UserHandle.SYSTEM);
+        return firstPackageName;
+
     }
 
     private void declineLogdAccess(int uid, int gid, int pid, int fd) {
@@ -183,7 +192,8 @@
 
                 ActivityManagerInternal ami = LocalServices.getService(
                         ActivityManagerInternal.class);
-                boolean isCallerInstrumented = ami.isUidCurrentlyInstrumented(mUid);
+                boolean isCallerInstrumented =
+                        ami.getInstrumentationSourceUid(mUid) != android.os.Process.INVALID_UID;
 
                 // The instrumented apks only run for testing, so we don't check user permission.
                 if (isCallerInstrumented) {
@@ -197,16 +207,23 @@
 
                 final int procState = LocalServices.getService(ActivityManagerInternal.class)
                         .getUidProcessState(mUid);
-                // If the process is foreground, show a dialog for user consent
+                // If the process is foreground and we can retrieve the package name, show a dialog
+                // for user consent
                 if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
-                    showDialog(mUid, mGid, mPid, mFd);
-                } else {
-                    /**
-                     * If the process is background, decline the logd access.
-                     **/
-                    declineLogdAccess(mUid, mGid, mPid, mFd);
-                    return;
+                    String packageName = getPackageName(mUid, mGid, mPid, mFd);
+                    if (packageName != null) {
+                        final Intent mIntent = createIntent(packageName, mUid, mGid, mPid, mFd);
+                        mContext.startActivityAsUser(mIntent, UserHandle.SYSTEM);
+                        return;
+                    }
                 }
+
+                /**
+                 * If the process is background or cannot retrieve the package name,
+                 * decline the logd access.
+                 **/
+                declineLogdAccess(mUid, mGid, mPid, mFd);
+                return;
             }
         }
     }
diff --git a/services/core/java/com/android/server/media/BluetoothRouteProvider.java b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
index 728782c..d0651ed 100644
--- a/services/core/java/com/android/server/media/BluetoothRouteProvider.java
+++ b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
@@ -133,6 +133,10 @@
                 mIntentFilter, null, null);
     }
 
+    public void stop() {
+        mContext.unregisterReceiver(mBroadcastReceiver);
+    }
+
     /**
      * Transfers to a given bluetooth route.
      * The dedicated BT device with the route would be activated.
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index b307266..e27cbea 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -1150,6 +1150,8 @@
             if (DEBUG) {
                 Slog.d(TAG, userRecord + ": Disposed");
             }
+            userRecord.mHandler.sendMessage(
+                    obtainMessage(UserHandler::stop, userRecord.mHandler));
             mUserRecords.remove(userRecord.mUserId);
             // Note: User already stopped (by switchUser) so no need to send stop message here.
         }
@@ -1330,6 +1332,7 @@
         private void start() {
             if (!mRunning) {
                 mRunning = true;
+                mSystemProvider.start();
                 mWatcher.start();
             }
         }
@@ -1338,6 +1341,7 @@
             if (mRunning) {
                 mRunning = false;
                 mWatcher.stop(); // also stops all providers
+                mSystemProvider.stop();
             }
         }
 
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 7878159..d91bf8c 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -71,6 +71,7 @@
     private final IAudioService mAudioService;
     private final Handler mHandler;
     private final Context mContext;
+    private final UserHandle mUser;
     private final BluetoothRouteProvider mBtRouteProvider;
 
     private static ComponentName sComponentName = new ComponentName(
@@ -86,6 +87,9 @@
     final AudioRoutesInfo mCurAudioRoutesInfo = new AudioRoutesInfo();
     int mDeviceVolume;
 
+    private final AudioManagerBroadcastReceiver mAudioReceiver =
+            new AudioManagerBroadcastReceiver();
+
     private final Object mRequestLock = new Object();
     @GuardedBy("mRequestLock")
     private volatile SessionCreationRequest mPendingSessionCreationRequest;
@@ -108,6 +112,7 @@
 
         mIsSystemRouteProvider = true;
         mContext = context;
+        mUser = user;
         mHandler = new Handler(Looper.getMainLooper());
 
         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
@@ -128,21 +133,33 @@
             }
         });
         updateSessionInfosIfNeeded();
+    }
 
+    public void start() {
         IntentFilter intentFilter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION);
         intentFilter.addAction(AudioManager.STREAM_DEVICES_CHANGED_ACTION);
-        mContext.registerReceiverAsUser(new AudioManagerBroadcastReceiver(), user,
+        mContext.registerReceiverAsUser(mAudioReceiver, mUser,
                 intentFilter, null, null);
 
         if (mBtRouteProvider != null) {
             mHandler.post(() -> {
-                mBtRouteProvider.start(user);
+                mBtRouteProvider.start(mUser);
                 notifyProviderState();
             });
         }
         updateVolume();
     }
 
+    public void stop() {
+        mContext.unregisterReceiver(mAudioReceiver);
+        if (mBtRouteProvider != null) {
+            mHandler.post(() -> {
+                mBtRouteProvider.stop();
+                notifyProviderState();
+            });
+        }
+    }
+
     @Override
     public void setCallback(Callback callback) {
         super.setCallback(callback);
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 74b11da..e5a6e65 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -1007,7 +1007,7 @@
                 final int changes = ActivityManager.UID_OBSERVER_PROCSTATE
                         | ActivityManager.UID_OBSERVER_GONE
                         | ActivityManager.UID_OBSERVER_CAPABILITY;
-                mActivityManager.registerUidObserver(mUidObserver, changes,
+                mActivityManagerInternal.registerNetworkPolicyUidObserver(mUidObserver, changes,
                         NetworkPolicyManager.FOREGROUND_THRESHOLD_STATE, "android");
                 mNetworkManager.registerObserver(mAlertObserver);
             } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/net/TEST_MAPPING b/services/core/java/com/android/server/net/TEST_MAPPING
index 02095eb..4ccf09e 100644
--- a/services/core/java/com/android/server/net/TEST_MAPPING
+++ b/services/core/java/com/android/server/net/TEST_MAPPING
@@ -2,12 +2,8 @@
   "presubmit-large": [
     {
       "name": "CtsHostsideNetworkTests",
-      "file_patterns": ["(/|^)NetworkPolicy[^/]*\\.java"],
       "options": [
         {
-          "include-filter": "com.android.cts.net.HostsideRestrictBackgroundNetworkTests"
-        },
-        {
           "exclude-annotation": "androidx.test.filters.FlakyTest"
         },
         {
diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java
index 5013570..66f71a3 100644
--- a/services/core/java/com/android/server/pm/AppDataHelper.java
+++ b/services/core/java/com/android/server/pm/AppDataHelper.java
@@ -386,6 +386,7 @@
         final File ceDir = Environment.getDataUserCeDirectory(volumeUuid, userId);
         final File deDir = Environment.getDataUserDeDirectory(volumeUuid, userId);
 
+        final Computer snapshot = mPm.snapshotComputer();
         // First look for stale data that doesn't belong, and check if things
         // have changed since we did our last restorecon
         if ((flags & StorageManager.FLAG_STORAGE_CE) != 0) {
@@ -400,7 +401,7 @@
             for (File file : files) {
                 final String packageName = file.getName();
                 try {
-                    assertPackageStorageValid(volumeUuid, packageName, userId);
+                    assertPackageStorageValid(snapshot, volumeUuid, packageName, userId);
                 } catch (PackageManagerException e) {
                     logCriticalInfo(Log.WARN, "Destroying " + file + " due to: " + e);
                     try {
@@ -417,7 +418,7 @@
             for (File file : files) {
                 final String packageName = file.getName();
                 try {
-                    assertPackageStorageValid(volumeUuid, packageName, userId);
+                    assertPackageStorageValid(snapshot, volumeUuid, packageName, userId);
                 } catch (PackageManagerException e) {
                     logCriticalInfo(Log.WARN, "Destroying " + file + " due to: " + e);
                     try {
@@ -434,12 +435,9 @@
         // installed for this volume and user
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "prepareAppDataAndMigrate");
         Installer.Batch batch = new Installer.Batch();
-        final List<PackageSetting> packages;
-        synchronized (mPm.mLock) {
-            packages = mPm.mSettings.getVolumePackagesLPr(volumeUuid);
-        }
+        List<? extends PackageStateInternal> packages = snapshot.getVolumePackages(volumeUuid);
         int preparedCount = 0;
-        for (PackageSetting ps : packages) {
+        for (PackageStateInternal ps : packages) {
             final String packageName = ps.getPackageName();
             if (ps.getPkg() == null) {
                 Slog.w(TAG, "Odd, missing scanned package " + packageName);
@@ -453,7 +451,7 @@
                 continue;
             }
 
-            if (ps.getInstalled(userId)) {
+            if (ps.getUserStateOrDefault(userId).isInstalled()) {
                 prepareAppDataAndMigrate(batch, ps.getPkg(), userId, flags, migrateAppData);
                 preparedCount++;
             }
@@ -469,35 +467,25 @@
      * Asserts that storage path is valid by checking that {@code packageName} is present,
      * installed for the given {@code userId} and can have app data.
      */
-    private void assertPackageStorageValid(String volumeUuid, String packageName, int userId)
-            throws PackageManagerException {
-        synchronized (mPm.mLock) {
-            // Normalize package name to handle renamed packages
-            packageName = normalizePackageNameLPr(packageName);
-
-            final PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
-            if (ps == null) {
-                throw new PackageManagerException("Package " + packageName + " is unknown");
-            } else if (!TextUtils.equals(volumeUuid, ps.getVolumeUuid())) {
-                throw new PackageManagerException(
-                        "Package " + packageName + " found on unknown volume " + volumeUuid
-                                + "; expected volume " + ps.getVolumeUuid());
-            } else if (!ps.getInstalled(userId)) {
-                throw new PackageManagerException(
-                        "Package " + packageName + " not installed for user " + userId);
-            } else if (ps.getPkg() != null && !shouldHaveAppStorage(ps.getPkg())) {
-                throw new PackageManagerException(
-                        "Package " + packageName + " shouldn't have storage");
-            }
+    private void assertPackageStorageValid(@NonNull Computer snapshot, String volumeUuid,
+            String packageName, int userId) throws PackageManagerException {
+        final PackageStateInternal packageState = snapshot.getPackageStateInternal(packageName);
+        if (packageState == null) {
+            throw new PackageManagerException("Package " + packageName + " is unknown");
+        } else if (!TextUtils.equals(volumeUuid, packageState.getVolumeUuid())) {
+            throw new PackageManagerException(
+                    "Package " + packageName + " found on unknown volume " + volumeUuid
+                            + "; expected volume " + packageState.getVolumeUuid());
+        } else if (!packageState.getUserStateOrDefault(userId).isInstalled()) {
+            throw new PackageManagerException(
+                    "Package " + packageName + " not installed for user " + userId);
+        } else if (packageState.getPkg() != null
+                && !shouldHaveAppStorage(packageState.getPkg())) {
+            throw new PackageManagerException(
+                    "Package " + packageName + " shouldn't have storage");
         }
     }
 
-    @GuardedBy("mPm.mLock")
-    private String normalizePackageNameLPr(String packageName) {
-        String normalizedPackageName = mPm.mSettings.getRenamedPackageLPr(packageName);
-        return normalizedPackageName != null ? normalizedPackageName : packageName;
-    }
-
     /**
      * Prepare storage for system user really early during boot,
      * since core system apps like SettingsProvider and SystemUI
diff --git a/services/core/java/com/android/server/pm/AppsFilterImpl.java b/services/core/java/com/android/server/pm/AppsFilter.java
similarity index 67%
rename from services/core/java/com/android/server/pm/AppsFilterImpl.java
rename to services/core/java/com/android/server/pm/AppsFilter.java
index 17dc403..29ee281 100644
--- a/services/core/java/com/android/server/pm/AppsFilterImpl.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -44,6 +44,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
+import android.util.SparseSetArray;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
@@ -63,18 +64,14 @@
 import com.android.server.pm.pkg.component.ParsedProvider;
 import com.android.server.utils.Snappable;
 import com.android.server.utils.SnapshotCache;
+import com.android.server.utils.Snapshots;
 import com.android.server.utils.Watchable;
 import com.android.server.utils.WatchableImpl;
-import com.android.server.utils.Watched;
-import com.android.server.utils.WatchedArrayList;
 import com.android.server.utils.WatchedArrayMap;
-import com.android.server.utils.WatchedArraySet;
 import com.android.server.utils.WatchedSparseBooleanMatrix;
-import com.android.server.utils.WatchedSparseSetArray;
 import com.android.server.utils.Watcher;
 
 import java.io.PrintWriter;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
@@ -87,7 +84,7 @@
  * manifests.
  */
 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-public class AppsFilterImpl implements AppsFilterSnapshot, Watchable, Snappable {
+public class AppsFilter implements Watchable, Snappable {
 
     private static final String TAG = "AppsFilter";
 
@@ -102,48 +99,32 @@
      * application B is implicitly allowed to query for application A; regardless of any manifest
      * entries.
      */
-    @GuardedBy("mLock")
-    @Watched
-    private final WatchedSparseSetArray<Integer> mImplicitlyQueryable;
-    private final SnapshotCache<WatchedSparseSetArray<Integer>> mImplicitQueryableSnapshot;
+    private final SparseSetArray<Integer> mImplicitlyQueryable = new SparseSetArray<>();
 
     /**
      * This contains a list of app UIDs that are implicitly queryable because another app explicitly
      * interacted with it, but could keep across package updates. For example, if application A
      * grants persistable uri permission to application B; regardless of any manifest entries.
      */
-    @GuardedBy("mLock")
-    @Watched
-    private final WatchedSparseSetArray<Integer> mRetainedImplicitlyQueryable;
-    private final SnapshotCache<WatchedSparseSetArray<Integer>>
-            mRetainedImplicitlyQueryableSnapshot;
+    private final SparseSetArray<Integer> mRetainedImplicitlyQueryable = new SparseSetArray<>();
 
     /**
      * A mapping from the set of App IDs that query other App IDs via package name to the
      * list of packages that they can see.
      */
-    @GuardedBy("mLock")
-    @Watched
-    private final WatchedSparseSetArray<Integer> mQueriesViaPackage;
-    private final SnapshotCache<WatchedSparseSetArray<Integer>> mQueriesViaPackageSnapshot;
+    private final SparseSetArray<Integer> mQueriesViaPackage = new SparseSetArray<>();
 
     /**
      * A mapping from the set of App IDs that query others via component match to the list
      * of packages that the they resolve to.
      */
-    @GuardedBy("mLock")
-    @Watched
-    private final WatchedSparseSetArray<Integer> mQueriesViaComponent;
-    private final SnapshotCache<WatchedSparseSetArray<Integer>> mQueriesViaComponentSnapshot;
+    private final SparseSetArray<Integer> mQueriesViaComponent = new SparseSetArray<>();
 
     /**
      * A mapping from the set of App IDs that query other App IDs via library name to the
      * list of packages that they can see.
      */
-    @GuardedBy("mLock")
-    @Watched
-    private final WatchedSparseSetArray<Integer> mQueryableViaUsesLibrary;
-    private final SnapshotCache<WatchedSparseSetArray<Integer>> mQueryableViaUsesLibrarySnapshot;
+    private final SparseSetArray<Integer> mQueryableViaUsesLibrary = new SparseSetArray<>();
 
     /**
      * Executor for running reasonably short background tasks such as building the initial
@@ -163,10 +144,7 @@
      * A set of App IDs that are always queryable by any package, regardless of their manifest
      * content.
      */
-    @Watched
-    @GuardedBy("mLock")
-    private final WatchedArraySet<Integer> mForceQueryable;
-    private final SnapshotCache<WatchedArraySet<Integer>> mForceQueryableSnapshot;
+    private final ArraySet<Integer> mForceQueryable = new ArraySet<>();
 
     /**
      * The set of package names provided by the device that should be force queryable regardless of
@@ -176,16 +154,14 @@
 
     /** True if all system apps should be made queryable by default. */
     private final boolean mSystemAppsQueryable;
+
     private final FeatureConfig mFeatureConfig;
     private final OverlayReferenceMapper mOverlayReferenceMapper;
     private final StateProvider mStateProvider;
     private final PackageManagerInternal mPmInternal;
-    private SigningDetails mSystemSigningDetails;
 
-    @GuardedBy("mLock")
-    @Watched
-    private final WatchedArrayList<String> mProtectedBroadcasts;
-    private final SnapshotCache<WatchedArrayList<String>> mProtectedBroadcastsSnapshot;
+    private SigningDetails mSystemSigningDetails;
+    private Set<String> mProtectedBroadcasts = new ArraySet<>();
 
     private final Object mCacheLock = new Object();
 
@@ -194,29 +170,24 @@
      * filtered to the second. It's essentially a cache of the
      * {@link #shouldFilterApplicationInternal(int, Object, PackageStateInternal, int)} call.
      * NOTE: It can only be relied upon after the system is ready to avoid unnecessary update on
-     * initial scam and is empty until {@link #onSystemReady()} is called.
+     * initial scam and is empty until {@link #mSystemReady} is true.
      */
     @GuardedBy("mCacheLock")
     @NonNull
     private final WatchedSparseBooleanMatrix mShouldFilterCache;
-    private final SnapshotCache<WatchedSparseBooleanMatrix> mShouldFilterCacheSnapshot;
 
-    /**
-     * Guards the accesses for the list/set fields except for {@link #mShouldFilterCache}
-     */
-    private final Object mLock = new Object();
+    private volatile boolean mSystemReady = false;
 
     /**
      * A cached snapshot.
      */
-    private final SnapshotCache<AppsFilterImpl> mSnapshot;
+    private final SnapshotCache<AppsFilter> mSnapshot;
 
-    private SnapshotCache<AppsFilterImpl> makeCache() {
-        return new SnapshotCache<AppsFilterImpl>(this, this) {
+    private SnapshotCache<AppsFilter> makeCache() {
+        return new SnapshotCache<AppsFilter>(this, this) {
             @Override
-            public AppsFilterImpl createSnapshot() {
-                AppsFilterImpl s = new AppsFilterImpl(mSource);
-                s.mWatchable.seal();
+            public AppsFilter createSnapshot() {
+                AppsFilter s = new AppsFilter(mSource);
                 return s;
             }
         };
@@ -226,15 +197,6 @@
      * Watchable machinery
      */
     private final WatchableImpl mWatchable = new WatchableImpl();
-    /**
-     * The observer that watches for changes from array members
-     */
-    private final Watcher mObserver = new Watcher() {
-        @Override
-        public void onChange(@Nullable Watchable what) {
-            AppsFilterImpl.this.dispatchChange(what);
-        }
-    };
 
     /**
      * Ensures an observer is in the list, exactly once. The observer cannot be null.  The
@@ -289,7 +251,7 @@
     }
 
     @VisibleForTesting(visibility = PRIVATE)
-    AppsFilterImpl(StateProvider stateProvider,
+    AppsFilter(StateProvider stateProvider,
             FeatureConfig featureConfig,
             String[] forceQueryableList,
             boolean systemAppsQueryable,
@@ -304,87 +266,38 @@
         mStateProvider = stateProvider;
         mPmInternal = pmInternal;
         mBackgroundExecutor = backgroundExecutor;
-        mImplicitlyQueryable = new WatchedSparseSetArray<>();
-        mImplicitQueryableSnapshot = new SnapshotCache.Auto<>(
-                mImplicitlyQueryable, mImplicitlyQueryable, "AppsFilter.mImplicitlyQueryable");
-        mRetainedImplicitlyQueryable = new WatchedSparseSetArray<>();
-        mRetainedImplicitlyQueryableSnapshot = new SnapshotCache.Auto<>(
-                mRetainedImplicitlyQueryable, mRetainedImplicitlyQueryable,
-                "AppsFilter.mRetainedImplicitlyQueryable");
-        mQueriesViaPackage = new WatchedSparseSetArray<>();
-        mQueriesViaPackageSnapshot = new SnapshotCache.Auto<>(
-                mQueriesViaPackage, mQueriesViaPackage, "AppsFilter.mQueriesViaPackage");
-        mQueriesViaComponent = new WatchedSparseSetArray<>();
-        mQueriesViaComponentSnapshot = new SnapshotCache.Auto<>(
-                mQueriesViaComponent, mQueriesViaComponent, "AppsFilter.mQueriesViaComponent");
-        mQueryableViaUsesLibrary = new WatchedSparseSetArray<>();
-        mQueryableViaUsesLibrarySnapshot = new SnapshotCache.Auto<>(
-                mQueryableViaUsesLibrary, mQueryableViaUsesLibrary,
-                "AppsFilter.mQueryableViaUsesLibrary");
-        mForceQueryable = new WatchedArraySet<>();
-        mForceQueryableSnapshot = new SnapshotCache.Auto<>(
-                mForceQueryable, mForceQueryable, "AppsFilter.mForceQueryable");
-        mProtectedBroadcasts = new WatchedArrayList<>();
-        mProtectedBroadcastsSnapshot = new SnapshotCache.Auto<>(
-                mProtectedBroadcasts, mProtectedBroadcasts, "AppsFilter.mProtectedBroadcasts");
         mShouldFilterCache = new WatchedSparseBooleanMatrix();
-        mShouldFilterCacheSnapshot = new SnapshotCache.Auto<>(
-                mShouldFilterCache, mShouldFilterCache, "AppsFilter.mShouldFilterCache");
-
-        registerObservers();
-        Watchable.verifyWatchedAttributes(this, mObserver);
         mSnapshot = makeCache();
     }
 
     /**
      * The copy constructor is used by PackageManagerService to construct a snapshot.
+     * Attributes are not deep-copied since these are supposed to be immutable.
+     * TODO: deep-copy the attributes, if necessary.
      */
-    private AppsFilterImpl(AppsFilterImpl orig) {
-        synchronized (orig.mLock) {
-            mImplicitlyQueryable = orig.mImplicitQueryableSnapshot.snapshot();
-            mImplicitQueryableSnapshot = new SnapshotCache.Sealed<>();
-            mRetainedImplicitlyQueryable = orig.mRetainedImplicitlyQueryableSnapshot.snapshot();
-            mRetainedImplicitlyQueryableSnapshot = new SnapshotCache.Sealed<>();
-            mQueriesViaPackage = orig.mQueriesViaPackageSnapshot.snapshot();
-            mQueriesViaPackageSnapshot = new SnapshotCache.Sealed<>();
-            mQueriesViaComponent = orig.mQueriesViaComponentSnapshot.snapshot();
-            mQueriesViaComponentSnapshot = new SnapshotCache.Sealed<>();
-            mQueryableViaUsesLibrary = orig.mQueryableViaUsesLibrarySnapshot.snapshot();
-            mQueryableViaUsesLibrarySnapshot = new SnapshotCache.Sealed<>();
-            mForceQueryable = orig.mForceQueryableSnapshot.snapshot();
-            mForceQueryableSnapshot = new SnapshotCache.Sealed<>();
-            mProtectedBroadcasts = orig.mProtectedBroadcastsSnapshot.snapshot();
-            mProtectedBroadcastsSnapshot = new SnapshotCache.Sealed<>();
-        }
+    private AppsFilter(AppsFilter orig) {
+        Snapshots.copy(mImplicitlyQueryable, orig.mImplicitlyQueryable);
+        Snapshots.copy(mRetainedImplicitlyQueryable, orig.mRetainedImplicitlyQueryable);
+        Snapshots.copy(mQueriesViaPackage, orig.mQueriesViaPackage);
+        Snapshots.copy(mQueriesViaComponent, orig.mQueriesViaComponent);
+        Snapshots.copy(mQueryableViaUsesLibrary, orig.mQueryableViaUsesLibrary);
         mQueriesViaComponentRequireRecompute = orig.mQueriesViaComponentRequireRecompute;
-        mForceQueryableByDevicePackageNames =
-                Arrays.copyOf(orig.mForceQueryableByDevicePackageNames,
-                        orig.mForceQueryableByDevicePackageNames.length);
+        mForceQueryable.addAll(orig.mForceQueryable);
+        mForceQueryableByDevicePackageNames = orig.mForceQueryableByDevicePackageNames;
         mSystemAppsQueryable = orig.mSystemAppsQueryable;
         mFeatureConfig = orig.mFeatureConfig;
         mOverlayReferenceMapper = orig.mOverlayReferenceMapper;
         mStateProvider = orig.mStateProvider;
         mSystemSigningDetails = orig.mSystemSigningDetails;
+        mProtectedBroadcasts = orig.mProtectedBroadcasts;
         synchronized (orig.mCacheLock) {
-            mShouldFilterCache = orig.mShouldFilterCacheSnapshot.snapshot();
-            mShouldFilterCacheSnapshot = new SnapshotCache.Sealed<>();
+            mShouldFilterCache = orig.mShouldFilterCache.snapshot();
         }
 
         mBackgroundExecutor = null;
         mPmInternal = null;
         mSnapshot = new SnapshotCache.Sealed<>();
-    }
-
-    @SuppressWarnings("GuardedBy")
-    private void registerObservers() {
-        mImplicitlyQueryable.registerObserver(mObserver);
-        mRetainedImplicitlyQueryable.registerObserver(mObserver);
-        mQueriesViaPackage.registerObserver(mObserver);
-        mQueriesViaComponent.registerObserver(mObserver);
-        mQueryableViaUsesLibrary.registerObserver(mObserver);
-        mForceQueryable.registerObserver(mObserver);
-        mProtectedBroadcasts.registerObserver(mObserver);
-        mShouldFilterCache.registerObserver(mObserver);
+        mSystemReady = true;
     }
 
     /**
@@ -392,7 +305,7 @@
      * the function ensures that this function returns a valid snapshot even if a race
      * condition causes the cached snapshot to be cleared asynchronously to this method.
      */
-    public AppsFilterSnapshot snapshot() {
+    public AppsFilter snapshot() {
         return mSnapshot.snapshot();
     }
 
@@ -453,7 +366,7 @@
 
         @Nullable
         private SparseBooleanArray mLoggingEnabled = null;
-        private AppsFilterImpl mAppsFilter;
+        private AppsFilter mAppsFilter;
 
         private FeatureConfigImpl(
                 PackageManagerInternal pmInternal, PackageManagerServiceInjector injector) {
@@ -461,7 +374,7 @@
             mInjector = injector;
         }
 
-        public void setAppsFilter(AppsFilterImpl filter) {
+        public void setAppsFilter(AppsFilter filter) {
             mAppsFilter = filter;
         }
 
@@ -565,9 +478,8 @@
 
         @Override
         public void updatePackageState(PackageStateInternal setting, boolean removed) {
-            final boolean enableLogging = setting.getPkg() != null
-                    && !removed && (setting.getPkg().isTestOnly()
-                    || setting.getPkg().isDebuggable());
+            final boolean enableLogging = setting.getPkg() != null &&
+                    !removed && (setting.getPkg().isTestOnly() || setting.getPkg().isDebuggable());
             enableLogging(setting.getAppId(), enableLogging);
             if (removed) {
                 mDisabledPackages.remove(setting.getPackageName());
@@ -578,7 +490,7 @@
     }
 
     /** Builder method for an AppsFilter */
-    public static AppsFilterImpl create(@NonNull PackageManagerServiceInjector injector,
+    public static AppsFilter create(@NonNull PackageManagerServiceInjector injector,
             @NonNull PackageManagerInternal pmInt) {
         final boolean forceSystemAppsQueryable =
                 injector.getContext().getResources()
@@ -602,7 +514,7 @@
                         injector.getUserManagerInternal().getUserInfos());
             }
         };
-        AppsFilterImpl appsFilter = new AppsFilterImpl(stateProvider, featureConfig,
+        AppsFilter appsFilter = new AppsFilter(stateProvider, featureConfig,
                 forcedQueryablePackageNames, forceSystemAppsQueryable, null,
                 injector.getBackgroundExecutor(), pmInt);
         featureConfig.setAppsFilter(appsFilter);
@@ -615,7 +527,7 @@
 
     /** Returns true if the querying package may query for the potential target package */
     private static boolean canQueryViaComponents(AndroidPackage querying,
-            AndroidPackage potentialTarget, WatchedArrayList<String> protectedBroadcasts) {
+            AndroidPackage potentialTarget, Set<String> protectedBroadcasts) {
         if (!querying.getQueriesIntents().isEmpty()) {
             for (Intent intent : querying.getQueriesIntents()) {
                 if (matchesPackage(intent, potentialTarget, protectedBroadcasts)) {
@@ -687,7 +599,7 @@
     }
 
     private static boolean matchesPackage(Intent intent, AndroidPackage potentialTarget,
-            WatchedArrayList<String> protectedBroadcasts) {
+            Set<String> protectedBroadcasts) {
         if (matchesAnyComponents(
                 intent, potentialTarget.getServices(), null /*protectedBroadcasts*/)) {
             return true;
@@ -708,7 +620,7 @@
 
     private static boolean matchesAnyComponents(Intent intent,
             List<? extends ParsedMainComponent> components,
-            WatchedArrayList<String> protectedBroadcasts) {
+            Set<String> protectedBroadcasts) {
         for (int i = ArrayUtils.size(components) - 1; i >= 0; i--) {
             ParsedMainComponent component = components.get(i);
             if (!component.isExported()) {
@@ -722,7 +634,7 @@
     }
 
     private static boolean matchesAnyFilter(Intent intent, ParsedComponent component,
-            WatchedArrayList<String> protectedBroadcasts) {
+            Set<String> protectedBroadcasts) {
         List<ParsedIntentInfo> intents = component.getIntents();
         for (int i = ArrayUtils.size(intents) - 1; i >= 0; i--) {
             IntentFilter intentFilter = intents.get(i).getIntentFilter();
@@ -734,10 +646,10 @@
     }
 
     private static boolean matchesIntentFilter(Intent intent, IntentFilter intentFilter,
-            @Nullable WatchedArrayList<String> protectedBroadcasts) {
+            @Nullable Set<String> protectedBroadcasts) {
         return intentFilter.match(intent.getAction(), intent.getType(), intent.getScheme(),
-                intent.getData(), intent.getCategories(), "AppsFilter", true,
-                protectedBroadcasts != null ? protectedBroadcasts.untrackedStorage() : null) > 0;
+                intent.getData(), intent.getCategories(), "AppsFilter", true, protectedBroadcasts)
+                > 0;
     }
 
     /**
@@ -753,19 +665,20 @@
         if (recipientUid == visibleUid) {
             return false;
         }
-        final boolean changed;
-        synchronized (mLock) {
-            changed = retainOnUpdate
-                    ? mRetainedImplicitlyQueryable.add(recipientUid, visibleUid)
-                    : mImplicitlyQueryable.add(recipientUid, visibleUid);
-            if (changed && DEBUG_LOGGING) {
-                Slog.i(TAG, (retainOnUpdate ? "retained " : "") + "implicit access granted: "
-                        + recipientUid + " -> " + visibleUid);
-            }
+        final boolean changed = retainOnUpdate
+                ? mRetainedImplicitlyQueryable.add(recipientUid, visibleUid)
+                : mImplicitlyQueryable.add(recipientUid, visibleUid);
+        if (changed && DEBUG_LOGGING) {
+            Slog.i(TAG, (retainOnUpdate ? "retained " : "") + "implicit access granted: "
+                    + recipientUid + " -> " + visibleUid);
         }
-        synchronized (mCacheLock) {
-            // update the cache in a one-off manner since we've got all the information we need.
-            mShouldFilterCache.put(recipientUid, visibleUid, false);
+
+        if (mSystemReady) {
+            synchronized (mCacheLock) {
+                // update the cache in a one-off manner since we've got all the information we
+                // need.
+                mShouldFilterCache.put(recipientUid, visibleUid, false);
+            }
         }
         if (changed) {
             onChanged();
@@ -779,6 +692,7 @@
 
         updateEntireShouldFilterCacheAsync();
         onChanged();
+        mSystemReady = true;
     }
 
     /**
@@ -799,8 +713,8 @@
             mStateProvider.runWithState((settings, users) -> {
                 ArraySet<String> additionalChangedPackages =
                         addPackageInternal(newPkgSetting, settings);
-                synchronized (mCacheLock) {
-                    updateShouldFilterCacheForPackage(mShouldFilterCache, null, newPkgSetting,
+                if (mSystemReady) {
+                    updateShouldFilterCacheForPackage(null, newPkgSetting,
                             settings, users, USER_ALL, settings.size());
                     if (additionalChangedPackages != null) {
                         for (int index = 0; index < additionalChangedPackages.size(); index++) {
@@ -814,12 +728,12 @@
                                 continue;
                             }
 
-                            updateShouldFilterCacheForPackage(mShouldFilterCache, null,
+                            updateShouldFilterCacheForPackage(null,
                                     changedPkgSetting, settings, users, USER_ALL,
                                     settings.size());
                         }
                     }
-                }
+                } // else, rebuild entire cache when system is ready
             });
         } finally {
             onChanged();
@@ -841,11 +755,9 @@
             mSystemSigningDetails = newPkgSetting.getSigningDetails();
             // and since we add overlays before we add the framework, let's revisit already added
             // packages for signature matches
-            synchronized (mLock) {
-                for (PackageStateInternal setting : existingSettings.values()) {
-                    if (isSystemSigned(mSystemSigningDetails, setting)) {
-                        mForceQueryable.add(setting.getAppId());
-                    }
+            for (PackageStateInternal setting : existingSettings.values()) {
+                if (isSystemSigned(mSystemSigningDetails, setting)) {
+                    mForceQueryable.add(setting.getAppId());
                 }
             }
         }
@@ -855,74 +767,67 @@
             return null;
         }
 
-        synchronized (mLock) {
-            if (mProtectedBroadcasts.addAll(newPkg.getProtectedBroadcasts())) {
-                mQueriesViaComponentRequireRecompute = true;
-            }
+        if (mProtectedBroadcasts.addAll(newPkg.getProtectedBroadcasts())) {
+            mQueriesViaComponentRequireRecompute = true;
+        }
 
-            final boolean newIsForceQueryable =
-                    mForceQueryable.contains(newPkgSetting.getAppId())
-                            /* shared user that is already force queryable */
-                            || newPkgSetting.isForceQueryableOverride() /* adb override */
-                            || (newPkgSetting.isSystem() && (mSystemAppsQueryable
-                            || newPkg.isForceQueryable()
-                            || ArrayUtils.contains(mForceQueryableByDevicePackageNames,
-                            newPkg.getPackageName())));
-            if (newIsForceQueryable
-                    || (mSystemSigningDetails != null
-                    && isSystemSigned(mSystemSigningDetails, newPkgSetting))) {
-                mForceQueryable.add(newPkgSetting.getAppId());
-            }
+        final boolean newIsForceQueryable =
+                mForceQueryable.contains(newPkgSetting.getAppId())
+                        /* shared user that is already force queryable */
+                        || newPkgSetting.isForceQueryableOverride() /* adb override */
+                        || (newPkgSetting.isSystem() && (mSystemAppsQueryable
+                        || newPkg.isForceQueryable()
+                        || ArrayUtils.contains(mForceQueryableByDevicePackageNames,
+                        newPkg.getPackageName())));
+        if (newIsForceQueryable
+                || (mSystemSigningDetails != null
+                && isSystemSigned(mSystemSigningDetails, newPkgSetting))) {
+            mForceQueryable.add(newPkgSetting.getAppId());
+        }
 
-            for (int i = existingSettings.size() - 1; i >= 0; i--) {
-                final PackageStateInternal existingSetting = existingSettings.valueAt(i);
-                if (existingSetting.getAppId() == newPkgSetting.getAppId()
-                        || existingSetting.getPkg()
-                        == null) {
-                    continue;
+        for (int i = existingSettings.size() - 1; i >= 0; i--) {
+            final PackageStateInternal existingSetting = existingSettings.valueAt(i);
+            if (existingSetting.getAppId() == newPkgSetting.getAppId() || existingSetting.getPkg()
+                    == null) {
+                continue;
+            }
+            final AndroidPackage existingPkg = existingSetting.getPkg();
+            // let's evaluate the ability of already added packages to see this new package
+            if (!newIsForceQueryable) {
+                if (!mQueriesViaComponentRequireRecompute && canQueryViaComponents(existingPkg,
+                        newPkg, mProtectedBroadcasts)) {
+                    mQueriesViaComponent.add(existingSetting.getAppId(), newPkgSetting.getAppId());
                 }
-                final AndroidPackage existingPkg = existingSetting.getPkg();
-                // let's evaluate the ability of already added packages to see this new package
-                if (!newIsForceQueryable) {
-                    if (!mQueriesViaComponentRequireRecompute && canQueryViaComponents(existingPkg,
-                            newPkg, mProtectedBroadcasts)) {
-                        mQueriesViaComponent.add(existingSetting.getAppId(),
-                                newPkgSetting.getAppId());
-                    }
-                    if (canQueryViaPackage(existingPkg, newPkg)
-                            || canQueryAsInstaller(existingSetting, newPkg)) {
-                        mQueriesViaPackage.add(existingSetting.getAppId(),
-                                newPkgSetting.getAppId());
-                    }
-                    if (canQueryViaUsesLibrary(existingPkg, newPkg)) {
-                        mQueryableViaUsesLibrary.add(existingSetting.getAppId(),
-                                newPkgSetting.getAppId());
-                    }
-                }
-                // now we'll evaluate our new package's ability to see existing packages
-                if (!mForceQueryable.contains(existingSetting.getAppId())) {
-                    if (!mQueriesViaComponentRequireRecompute && canQueryViaComponents(newPkg,
-                            existingPkg, mProtectedBroadcasts)) {
-                        mQueriesViaComponent.add(newPkgSetting.getAppId(),
-                                existingSetting.getAppId());
-                    }
-                    if (canQueryViaPackage(newPkg, existingPkg)
-                            || canQueryAsInstaller(newPkgSetting, existingPkg)) {
-                        mQueriesViaPackage.add(newPkgSetting.getAppId(),
-                                existingSetting.getAppId());
-                    }
-                    if (canQueryViaUsesLibrary(newPkg, existingPkg)) {
-                        mQueryableViaUsesLibrary.add(newPkgSetting.getAppId(),
-                                existingSetting.getAppId());
-                    }
-                }
-                // if either package instruments the other, mark both as visible to one another
-                if (newPkgSetting.getPkg() != null && existingSetting.getPkg() != null
-                        && (pkgInstruments(newPkgSetting.getPkg(), existingSetting.getPkg())
-                        || pkgInstruments(existingSetting.getPkg(), newPkgSetting.getPkg()))) {
-                    mQueriesViaPackage.add(newPkgSetting.getAppId(), existingSetting.getAppId());
+                if (canQueryViaPackage(existingPkg, newPkg)
+                        || canQueryAsInstaller(existingSetting, newPkg)) {
                     mQueriesViaPackage.add(existingSetting.getAppId(), newPkgSetting.getAppId());
                 }
+                if (canQueryViaUsesLibrary(existingPkg, newPkg)) {
+                    mQueryableViaUsesLibrary.add(existingSetting.getAppId(),
+                            newPkgSetting.getAppId());
+                }
+            }
+            // now we'll evaluate our new package's ability to see existing packages
+            if (!mForceQueryable.contains(existingSetting.getAppId())) {
+                if (!mQueriesViaComponentRequireRecompute && canQueryViaComponents(newPkg,
+                        existingPkg, mProtectedBroadcasts)) {
+                    mQueriesViaComponent.add(newPkgSetting.getAppId(), existingSetting.getAppId());
+                }
+                if (canQueryViaPackage(newPkg, existingPkg)
+                        || canQueryAsInstaller(newPkgSetting, existingPkg)) {
+                    mQueriesViaPackage.add(newPkgSetting.getAppId(), existingSetting.getAppId());
+                }
+                if (canQueryViaUsesLibrary(newPkg, existingPkg)) {
+                    mQueryableViaUsesLibrary.add(newPkgSetting.getAppId(),
+                            existingSetting.getAppId());
+                }
+            }
+            // if either package instruments the other, mark both as visible to one another
+            if (newPkgSetting.getPkg() != null && existingSetting.getPkg() != null
+                    && (pkgInstruments(newPkgSetting.getPkg(), existingSetting.getPkg())
+                    || pkgInstruments(existingSetting.getPkg(), newPkgSetting.getPkg()))) {
+                mQueriesViaPackage.add(newPkgSetting.getAppId(), existingSetting.getAppId());
+                mQueriesViaPackage.add(existingSetting.getAppId(), newPkgSetting.getAppId());
             }
         }
 
@@ -944,6 +849,9 @@
     }
 
     private void removeAppIdFromVisibilityCache(int appId) {
+        if (!mSystemReady) {
+            return;
+        }
         synchronized (mCacheLock) {
             for (int i = 0; i < mShouldFilterCache.size(); i++) {
                 if (UserHandle.getAppId(mShouldFilterCache.keyAt(i)) == appId) {
@@ -976,32 +884,23 @@
                         + "updating the whole cache");
                 userId = USER_ALL;
             }
-            WatchedSparseBooleanMatrix cache =
-                    updateEntireShouldFilterCacheInner(settings, users, userId);
-            synchronized (mCacheLock) {
-                mShouldFilterCache.copyFrom(cache);
-            }
+            updateEntireShouldFilterCacheInner(settings, users, userId);
         });
     }
 
-    @NonNull
-    private WatchedSparseBooleanMatrix updateEntireShouldFilterCacheInner(
+    private void updateEntireShouldFilterCacheInner(
             ArrayMap<String, ? extends PackageStateInternal> settings, UserInfo[] users,
             int subjectUserId) {
-        final WatchedSparseBooleanMatrix cache;
-        if (subjectUserId == USER_ALL) {
-            cache = new WatchedSparseBooleanMatrix(users.length * settings.size());
-        } else {
-            synchronized (mCacheLock) {
-                cache = mShouldFilterCache.snapshot();
+        synchronized (mCacheLock) {
+            if (subjectUserId == USER_ALL) {
+                mShouldFilterCache.clear();
             }
-            cache.setCapacity(users.length * settings.size());
+            mShouldFilterCache.setCapacity(users.length * settings.size());
         }
         for (int i = settings.size() - 1; i >= 0; i--) {
-            updateShouldFilterCacheForPackage(cache,
+            updateShouldFilterCacheForPackage(
                     null /*skipPackage*/, settings.valueAt(i), settings, users, subjectUserId, i);
         }
-        return cache;
     }
 
     private void updateEntireShouldFilterCacheAsync() {
@@ -1020,8 +919,7 @@
                     packagesCache.put(settings.keyAt(i), pkg);
                 }
             });
-            WatchedSparseBooleanMatrix cache = updateEntireShouldFilterCacheInner(
-                    settingsCopy, usersRef[0], USER_ALL);
+
             boolean[] changed = new boolean[1];
             // We have a cache, let's make sure the world hasn't changed out from under us.
             mStateProvider.runWithState((settings, users) -> {
@@ -1044,34 +942,39 @@
                     Slog.i(TAG, "Rebuilding cache with lock due to package change.");
                 }
             } else {
-                synchronized (mCacheLock) {
-                    mShouldFilterCache.copyFrom(cache);
-                }
+                updateEntireShouldFilterCacheInner(settingsCopy, usersRef[0], USER_ALL);
             }
         });
     }
 
     public void onUserCreated(int newUserId) {
+        if (!mSystemReady) {
+            return;
+        }
         updateEntireShouldFilterCache(newUserId);
         onChanged();
     }
 
     public void onUserDeleted(@UserIdInt int userId) {
+        if (!mSystemReady) {
+            return;
+        }
         removeShouldFilterCacheForUser(userId);
         onChanged();
     }
 
     private void updateShouldFilterCacheForPackage(String packageName) {
         mStateProvider.runWithState((settings, users) -> {
-            synchronized (mCacheLock) {
-                updateShouldFilterCacheForPackage(mShouldFilterCache, null /* skipPackage */,
-                        settings.get(packageName), settings, users, USER_ALL,
-                        settings.size() /*maxIndex*/);
+            if (!mSystemReady) {
+                return;
             }
+            updateShouldFilterCacheForPackage(null /* skipPackage */,
+                    settings.get(packageName), settings, users, USER_ALL,
+                    settings.size() /*maxIndex*/);
         });
     }
 
-    private void updateShouldFilterCacheForPackage(@NonNull WatchedSparseBooleanMatrix cache,
+    private void updateShouldFilterCacheForPackage(
             @Nullable String skipPackageName, PackageStateInternal subjectSetting, ArrayMap<String,
             ? extends PackageStateInternal> allSettings, UserInfo[] allUsers, int subjectUserId,
             int maxIndex) {
@@ -1087,29 +990,31 @@
             }
             if (subjectUserId == USER_ALL) {
                 for (int su = 0; su < allUsers.length; su++) {
-                    updateShouldFilterCacheForUser(cache, subjectSetting, allUsers, otherSetting,
+                    updateShouldFilterCacheForUser(subjectSetting, allUsers, otherSetting,
                             allUsers[su].id);
                 }
             } else {
-                updateShouldFilterCacheForUser(cache, subjectSetting, allUsers, otherSetting,
+                updateShouldFilterCacheForUser(subjectSetting, allUsers, otherSetting,
                         subjectUserId);
             }
         }
     }
 
-    private void updateShouldFilterCacheForUser(@NonNull WatchedSparseBooleanMatrix cache,
+    private void updateShouldFilterCacheForUser(
             PackageStateInternal subjectSetting, UserInfo[] allUsers,
             PackageStateInternal otherSetting, int subjectUserId) {
         for (int ou = 0; ou < allUsers.length; ou++) {
             int otherUser = allUsers[ou].id;
             int subjectUid = UserHandle.getUid(subjectUserId, subjectSetting.getAppId());
             int otherUid = UserHandle.getUid(otherUser, otherSetting.getAppId());
-            cache.put(subjectUid, otherUid,
-                    shouldFilterApplicationInternal(
-                            subjectUid, subjectSetting, otherSetting, otherUser));
-            cache.put(otherUid, subjectUid,
-                    shouldFilterApplicationInternal(
-                            otherUid, otherSetting, subjectSetting, subjectUserId));
+            final boolean shouldFilterSubjectToOther = shouldFilterApplicationInternal(
+                    subjectUid, subjectSetting, otherSetting, otherUser);
+            final boolean shouldFilterOtherToSubject = shouldFilterApplicationInternal(
+                    otherUid, otherSetting, subjectSetting, subjectUserId);
+            synchronized (mCacheLock) {
+                mShouldFilterCache.put(subjectUid, otherUid, shouldFilterSubjectToOther);
+                mShouldFilterCache.put(otherUid, subjectUid, shouldFilterOtherToSubject);
+            }
         }
     }
 
@@ -1143,30 +1048,28 @@
                 && pkgSetting.getSigningDetails().signaturesMatchExactly(sysSigningDetails);
     }
 
-    private void collectProtectedBroadcasts(
+    private ArraySet<String> collectProtectedBroadcasts(
             ArrayMap<String, ? extends PackageStateInternal> existingSettings,
             @Nullable String excludePackage) {
-        synchronized (mLock) {
-            mProtectedBroadcasts.clear();
-            for (int i = existingSettings.size() - 1; i >= 0; i--) {
-                PackageStateInternal setting = existingSettings.valueAt(i);
-                if (setting.getPkg() == null || setting.getPkg().getPackageName().equals(
-                        excludePackage)) {
-                    continue;
-                }
-                final List<String> protectedBroadcasts = setting.getPkg().getProtectedBroadcasts();
-                if (!protectedBroadcasts.isEmpty()) {
-                    mProtectedBroadcasts.addAll(protectedBroadcasts);
-                }
+        ArraySet<String> ret = new ArraySet<>();
+        for (int i = existingSettings.size() - 1; i >= 0; i--) {
+            PackageStateInternal setting = existingSettings.valueAt(i);
+            if (setting.getPkg() == null || setting.getPkg().getPackageName().equals(
+                    excludePackage)) {
+                continue;
+            }
+            final List<String> protectedBroadcasts = setting.getPkg().getProtectedBroadcasts();
+            if (!protectedBroadcasts.isEmpty()) {
+                ret.addAll(protectedBroadcasts);
             }
         }
+        return ret;
     }
 
     /**
      * This method recomputes all component / intent-based visibility and is intended to match the
      * relevant logic of {@link #addPackageInternal(PackageStateInternal, ArrayMap)}
      */
-    @GuardedBy("mLock")
     private void recomputeComponentVisibility(
             ArrayMap<String, ? extends PackageStateInternal> existingSettings) {
         mQueriesViaComponent.clear();
@@ -1194,16 +1097,24 @@
     }
 
     /**
-     * See {@link AppsFilterSnapshot#getVisibilityAllowList(PackageStateInternal, int[], ArrayMap)}
+     * Fetches all app Ids that a given setting is currently visible to, per provided user. This
+     * only includes UIDs >= {@link Process#FIRST_APPLICATION_UID} as all other UIDs can already see
+     * all applications.
+     *
+     * If the setting is visible to all UIDs, null is returned. If an app is not visible to any
+     * applications, the int array will be empty.
+     *
+     * @param users            the set of users that should be evaluated for this calculation
+     * @param existingSettings the set of all package settings that currently exist on device
+     * @return a SparseArray mapping userIds to a sorted int array of appIds that may view the
+     * provided setting or null if the app is visible to all and no allow list should be
+     * applied.
      */
-    @Override
     @Nullable
     public SparseArray<int[]> getVisibilityAllowList(PackageStateInternal setting, int[] users,
             ArrayMap<String, ? extends PackageStateInternal> existingSettings) {
-        synchronized (mLock) {
-            if (mForceQueryable.contains(setting.getAppId())) {
-                return null;
-            }
+        if (mForceQueryable.contains(setting.getAppId())) {
+            return null;
         }
         // let's reserve max memory to limit the number of allocations
         SparseArray<int[]> result = new SparseArray<>(users.length);
@@ -1253,7 +1164,7 @@
      * Equivalent to calling {@link #addPackage(PackageStateInternal, boolean)} with
      * {@code isReplace} equal to {@code false}.
      *
-     * @see AppsFilterImpl#addPackage(PackageStateInternal, boolean)
+     * @see AppsFilter#addPackage(PackageStateInternal, boolean)
      */
     public void addPackage(PackageStateInternal newPkgSetting) {
         addPackage(newPkgSetting, false /* isReplace */);
@@ -1267,64 +1178,58 @@
      */
     public void removePackage(PackageStateInternal setting, boolean isReplace) {
         mStateProvider.runWithState((settings, users) -> {
-            final ArraySet<String> additionalChangedPackages;
-            synchronized (mLock) {
-                final int userCount = users.length;
-                for (int u = 0; u < userCount; u++) {
-                    final int userId = users[u].id;
-                    final int removingUid = UserHandle.getUid(userId, setting.getAppId());
-                    mImplicitlyQueryable.remove(removingUid);
-                    for (int i = mImplicitlyQueryable.size() - 1; i >= 0; i--) {
-                        mImplicitlyQueryable.remove(mImplicitlyQueryable.keyAt(i), removingUid);
-                    }
-
-                    if (isReplace) {
-                        continue;
-                    }
-
-                    mRetainedImplicitlyQueryable.remove(removingUid);
-                    for (int i = mRetainedImplicitlyQueryable.size() - 1; i >= 0; i--) {
-                        mRetainedImplicitlyQueryable.remove(
-                                mRetainedImplicitlyQueryable.keyAt(i), removingUid);
-                    }
+            final int userCount = users.length;
+            for (int u = 0; u < userCount; u++) {
+                final int userId = users[u].id;
+                final int removingUid = UserHandle.getUid(userId, setting.getAppId());
+                mImplicitlyQueryable.remove(removingUid);
+                for (int i = mImplicitlyQueryable.size() - 1; i >= 0; i--) {
+                    mImplicitlyQueryable.remove(mImplicitlyQueryable.keyAt(i), removingUid);
                 }
 
-                if (!mQueriesViaComponentRequireRecompute) {
-                    mQueriesViaComponent.remove(setting.getAppId());
-                    for (int i = mQueriesViaComponent.size() - 1; i >= 0; i--) {
-                        mQueriesViaComponent.remove(mQueriesViaComponent.keyAt(i),
-                                setting.getAppId());
-                    }
-                }
-                mQueriesViaPackage.remove(setting.getAppId());
-                for (int i = mQueriesViaPackage.size() - 1; i >= 0; i--) {
-                    mQueriesViaPackage.remove(mQueriesViaPackage.keyAt(i), setting.getAppId());
-                }
-                mQueryableViaUsesLibrary.remove(setting.getAppId());
-                for (int i = mQueryableViaUsesLibrary.size() - 1; i >= 0; i--) {
-                    mQueryableViaUsesLibrary.remove(mQueryableViaUsesLibrary.keyAt(i),
-                            setting.getAppId());
+                if (isReplace) {
+                    continue;
                 }
 
-                mForceQueryable.remove(setting.getAppId());
-
-                if (setting.getPkg() != null
-                        && !setting.getPkg().getProtectedBroadcasts().isEmpty()) {
-                    final String removingPackageName = setting.getPkg().getPackageName();
-                    final ArrayList<String> protectedBroadcasts = new ArrayList<>();
-                    protectedBroadcasts.addAll(mProtectedBroadcasts.untrackedStorage());
-                    collectProtectedBroadcasts(settings, removingPackageName);
-                    if (!mProtectedBroadcasts.containsAll(protectedBroadcasts)) {
-                        mQueriesViaComponentRequireRecompute = true;
-                    }
+                mRetainedImplicitlyQueryable.remove(removingUid);
+                for (int i = mRetainedImplicitlyQueryable.size() - 1; i >= 0; i--) {
+                    mRetainedImplicitlyQueryable.remove(
+                            mRetainedImplicitlyQueryable.keyAt(i), removingUid);
                 }
-
-                additionalChangedPackages =
-                        mOverlayReferenceMapper.removePkg(setting.getPackageName());
-
-                mFeatureConfig.updatePackageState(setting, true /*removed*/);
             }
 
+            if (!mQueriesViaComponentRequireRecompute) {
+                mQueriesViaComponent.remove(setting.getAppId());
+                for (int i = mQueriesViaComponent.size() - 1; i >= 0; i--) {
+                    mQueriesViaComponent.remove(mQueriesViaComponent.keyAt(i), setting.getAppId());
+                }
+            }
+            mQueriesViaPackage.remove(setting.getAppId());
+            for (int i = mQueriesViaPackage.size() - 1; i >= 0; i--) {
+                mQueriesViaPackage.remove(mQueriesViaPackage.keyAt(i), setting.getAppId());
+            }
+            mQueryableViaUsesLibrary.remove(setting.getAppId());
+            for (int i = mQueryableViaUsesLibrary.size() - 1; i >= 0; i--) {
+                mQueryableViaUsesLibrary.remove(mQueryableViaUsesLibrary.keyAt(i),
+                        setting.getAppId());
+            }
+
+            mForceQueryable.remove(setting.getAppId());
+
+            if (setting.getPkg() != null && !setting.getPkg().getProtectedBroadcasts().isEmpty()) {
+                final String removingPackageName = setting.getPkg().getPackageName();
+                final Set<String> protectedBroadcasts = mProtectedBroadcasts;
+                mProtectedBroadcasts = collectProtectedBroadcasts(settings, removingPackageName);
+                if (!mProtectedBroadcasts.containsAll(protectedBroadcasts)) {
+                    mQueriesViaComponentRequireRecompute = true;
+                }
+            }
+
+            ArraySet<String> additionalChangedPackages =
+                    mOverlayReferenceMapper.removePkg(setting.getPackageName());
+
+            mFeatureConfig.updatePackageState(setting, true /*removed*/);
+
             // After removing all traces of the package, if it's part of a shared user, re-add other
             // shared user members to re-establish visibility between them and other packages.
             // NOTE: this must come after all removals from data structures but before we update the
@@ -1342,7 +1247,7 @@
             }
 
             removeAppIdFromVisibilityCache(setting.getAppId());
-            if (setting.hasSharedUser()) {
+            if (mSystemReady && setting.hasSharedUser()) {
                 final ArraySet<PackageStateInternal> sharedUserPackages =
                         mPmInternal.getSharedUserPackages(setting.getSharedUserAppId());
                 for (int i = sharedUserPackages.size() - 1; i >= 0; i--) {
@@ -1351,39 +1256,44 @@
                     if (siblingSetting == setting) {
                         continue;
                     }
-                    synchronized (mCacheLock) {
-                        updateShouldFilterCacheForPackage(mShouldFilterCache,
-                                setting.getPackageName(), siblingSetting, settings, users,
-                                USER_ALL, settings.size());
-                    }
+                    updateShouldFilterCacheForPackage(
+                            setting.getPackageName(), siblingSetting, settings, users,
+                            USER_ALL, settings.size());
                 }
             }
 
-            if (additionalChangedPackages != null) {
-                for (int index = 0; index < additionalChangedPackages.size(); index++) {
-                    String changedPackage = additionalChangedPackages.valueAt(index);
-                    PackageStateInternal changedPkgSetting = settings.get(changedPackage);
-                    if (changedPkgSetting == null) {
-                        // It's possible for the overlay mapper to know that an actor
-                        // package changed via an explicit reference, even if the actor
-                        // isn't installed, so skip if that's the case.
-                        continue;
-                    }
-                    synchronized (mCacheLock) {
-                        updateShouldFilterCacheForPackage(mShouldFilterCache, null,
+            if (mSystemReady) {
+                if (additionalChangedPackages != null) {
+                    for (int index = 0; index < additionalChangedPackages.size(); index++) {
+                        String changedPackage = additionalChangedPackages.valueAt(index);
+                        PackageStateInternal changedPkgSetting = settings.get(changedPackage);
+                        if (changedPkgSetting == null) {
+                            // It's possible for the overlay mapper to know that an actor
+                            // package changed via an explicit reference, even if the actor
+                            // isn't installed, so skip if that's the case.
+                            continue;
+                        }
+
+                        updateShouldFilterCacheForPackage(null,
                                 changedPkgSetting, settings, users, USER_ALL, settings.size());
                     }
                 }
             }
+
             onChanged();
         });
     }
 
     /**
-     * See {@link AppsFilterSnapshot#shouldFilterApplication(int, Object, PackageStateInternal,
-     * int)}
+     * Returns true if the calling package should not be able to see the target package, false if no
+     * filtering should be done.
+     *
+     * @param callingUid       the uid of the caller attempting to access a package
+     * @param callingSetting   the setting attempting to access a package or null if it could not be
+     *                         found
+     * @param targetPkgSetting the package being accessed
+     * @param userId           the user in which this access is being attempted
      */
-    @Override
     public boolean shouldFilterApplication(int callingUid, @Nullable Object callingSetting,
             PackageStateInternal targetPkgSetting, int userId) {
         if (DEBUG_TRACING) {
@@ -1396,12 +1306,9 @@
                     || callingAppId == targetPkgSetting.getAppId()) {
                 return false;
             }
-            final boolean shouldUseCache;
-            synchronized (mCacheLock) {
-                shouldUseCache = mShouldFilterCache.size() != 0;
-            }
-            if (shouldUseCache) { // use cache
-                if (!shouldFilterApplicationUsingCache(callingUid, targetPkgSetting.getAppId(),
+            if (mSystemReady) { // use cache
+                if (!shouldFilterApplicationUsingCache(callingUid,
+                        targetPkgSetting.getAppId(),
                         userId)) {
                     return false;
                 }
@@ -1411,7 +1318,6 @@
                     return false;
                 }
             }
-
             if (DEBUG_LOGGING || mFeatureConfig.isLoggingEnabled(callingAppId)) {
                 log(callingSetting, targetPkgSetting, "BLOCKED");
             }
@@ -1560,147 +1466,142 @@
                 return false;
             }
 
-            synchronized (mLock) {
-                try {
-                    if (DEBUG_TRACING) {
-                        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "mForceQueryable");
-                    }
-                    if (mForceQueryable.contains(targetAppId)) {
-                        if (DEBUG_LOGGING) {
-                            log(callingSetting, targetPkgSetting, "force queryable");
-                        }
-                        return false;
-                    }
-                } finally {
-                    if (DEBUG_TRACING) {
-                        Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-                    }
+            try {
+                if (DEBUG_TRACING) {
+                    Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "mForceQueryable");
                 }
-                try {
-                    if (DEBUG_TRACING) {
-                        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "mQueriesViaPackage");
+                if (mForceQueryable.contains(targetAppId)) {
+                    if (DEBUG_LOGGING) {
+                        log(callingSetting, targetPkgSetting, "force queryable");
                     }
-                    if (mQueriesViaPackage.contains(callingAppId, targetAppId)) {
-                        if (DEBUG_LOGGING) {
-                            log(callingSetting, targetPkgSetting, "queries package");
-                        }
-                        return false;
-                    }
-                } finally {
-                    if (DEBUG_TRACING) {
-                        Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-                    }
+                    return false;
                 }
-                try {
-                    if (DEBUG_TRACING) {
-                        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "mQueriesViaComponent");
-                    }
-                    if (mQueriesViaComponentRequireRecompute) {
-                        mStateProvider.runWithState((settings, users) -> {
-                            synchronized (mLock) {
-                                recomputeComponentVisibility(settings);
-                            }
-                        });
-                    }
-                    if (mQueriesViaComponent.contains(callingAppId, targetAppId)) {
-                        if (DEBUG_LOGGING) {
-                            log(callingSetting, targetPkgSetting, "queries component");
-                        }
-                        return false;
-                    }
-                } finally {
-                    if (DEBUG_TRACING) {
-                        Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-                    }
+            } finally {
+                if (DEBUG_TRACING) {
+                    Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
                 }
+            }
+            try {
+                if (DEBUG_TRACING) {
+                    Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "mQueriesViaPackage");
+                }
+                if (mQueriesViaPackage.contains(callingAppId, targetAppId)) {
+                    if (DEBUG_LOGGING) {
+                        log(callingSetting, targetPkgSetting, "queries package");
+                    }
+                    return false;
+                }
+            } finally {
+                if (DEBUG_TRACING) {
+                    Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+                }
+            }
+            try {
+                if (DEBUG_TRACING) {
+                    Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "mQueriesViaComponent");
+                }
+                if (mQueriesViaComponentRequireRecompute) {
+                    mStateProvider.runWithState((settings, users) -> {
+                        recomputeComponentVisibility(settings);
+                    });
+                }
+                if (mQueriesViaComponent.contains(callingAppId, targetAppId)) {
+                    if (DEBUG_LOGGING) {
+                        log(callingSetting, targetPkgSetting, "queries component");
+                    }
+                    return false;
+                }
+            } finally {
+                if (DEBUG_TRACING) {
+                    Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+                }
+            }
 
-                try {
-                    if (DEBUG_TRACING) {
-                        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "mImplicitlyQueryable");
-                    }
-                    final int targetUid = UserHandle.getUid(targetUserId, targetAppId);
-                    if (mImplicitlyQueryable.contains(callingUid, targetUid)) {
-                        if (DEBUG_LOGGING) {
-                            log(callingSetting, targetPkgSetting, "implicitly queryable for user");
-                        }
-                        return false;
-                    }
-                } finally {
-                    if (DEBUG_TRACING) {
-                        Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-                    }
+            try {
+                if (DEBUG_TRACING) {
+                    Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "mImplicitlyQueryable");
                 }
-
-                try {
-                    if (DEBUG_TRACING) {
-                        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "mRetainedImplicitlyQueryable");
+                final int targetUid = UserHandle.getUid(targetUserId, targetAppId);
+                if (mImplicitlyQueryable.contains(callingUid, targetUid)) {
+                    if (DEBUG_LOGGING) {
+                        log(callingSetting, targetPkgSetting, "implicitly queryable for user");
                     }
-                    final int targetUid = UserHandle.getUid(targetUserId, targetAppId);
-                    if (mRetainedImplicitlyQueryable.contains(callingUid, targetUid)) {
-                        if (DEBUG_LOGGING) {
-                            log(callingSetting, targetPkgSetting,
-                                    "retained implicitly queryable for user");
-                        }
-                        return false;
-                    }
-                } finally {
-                    if (DEBUG_TRACING) {
-                        Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-                    }
+                    return false;
                 }
+            } finally {
+                if (DEBUG_TRACING) {
+                    Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+                }
+            }
 
-                try {
-                    if (DEBUG_TRACING) {
-                        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "mOverlayReferenceMapper");
+            try {
+                if (DEBUG_TRACING) {
+                    Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "mRetainedImplicitlyQueryable");
+                }
+                final int targetUid = UserHandle.getUid(targetUserId, targetAppId);
+                if (mRetainedImplicitlyQueryable.contains(callingUid, targetUid)) {
+                    if (DEBUG_LOGGING) {
+                        log(callingSetting, targetPkgSetting,
+                                "retained implicitly queryable for user");
                     }
-                    final String targetName = targetPkg.getPackageName();
-                    if (callingSharedPkgSettings != null) {
-                        int size = callingSharedPkgSettings.size();
-                        for (int index = 0; index < size; index++) {
-                            PackageStateInternal pkgSetting = callingSharedPkgSettings.valueAt(
-                                    index);
-                            if (mOverlayReferenceMapper.isValidActor(targetName,
-                                    pkgSetting.getPackageName())) {
-                                if (DEBUG_LOGGING) {
-                                    log(callingPkgSetting, targetPkgSetting,
-                                            "matches shared user of package that acts on target of "
-                                                    + "overlay");
-                                }
-                                return false;
-                            }
-                        }
-                    } else {
+                    return false;
+                }
+            } finally {
+                if (DEBUG_TRACING) {
+                    Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+                }
+            }
+
+            try {
+                if (DEBUG_TRACING) {
+                    Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "mOverlayReferenceMapper");
+                }
+                final String targetName = targetPkg.getPackageName();
+                if (callingSharedPkgSettings != null) {
+                    int size = callingSharedPkgSettings.size();
+                    for (int index = 0; index < size; index++) {
+                        PackageStateInternal pkgSetting = callingSharedPkgSettings.valueAt(index);
                         if (mOverlayReferenceMapper.isValidActor(targetName,
-                                callingPkgSetting.getPackageName())) {
+                                pkgSetting.getPackageName())) {
                             if (DEBUG_LOGGING) {
                                 log(callingPkgSetting, targetPkgSetting,
-                                        "acts on target of overlay");
+                                        "matches shared user of package that acts on target of "
+                                                + "overlay");
                             }
                             return false;
                         }
                     }
-                } finally {
-                    if (DEBUG_TRACING) {
-                        Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-                    }
-                }
-
-                try {
-                    if (DEBUG_TRACING) {
-                        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "mQueryableViaUsesLibrary");
-                    }
-                    if (mQueryableViaUsesLibrary.contains(callingAppId, targetAppId)) {
+                } else {
+                    if (mOverlayReferenceMapper.isValidActor(targetName,
+                            callingPkgSetting.getPackageName())) {
                         if (DEBUG_LOGGING) {
-                            log(callingSetting, targetPkgSetting, "queryable for library users");
+                            log(callingPkgSetting, targetPkgSetting, "acts on target of overlay");
                         }
                         return false;
                     }
-                } finally {
-                    if (DEBUG_TRACING) {
-                        Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-                    }
+                }
+            } finally {
+                if (DEBUG_TRACING) {
+                    Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
                 }
             }
+
+            try {
+                if (DEBUG_TRACING) {
+                    Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "mQueryableViaUsesLibrary");
+                }
+                if (mQueryableViaUsesLibrary.contains(callingAppId, targetAppId)) {
+                    if (DEBUG_LOGGING) {
+                        log(callingSetting, targetPkgSetting, "queryable for library users");
+                    }
+                    return false;
+                }
+            } finally {
+                if (DEBUG_TRACING) {
+                    Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+                }
+            }
+
             return true;
         } finally {
             if (DEBUG_TRACING) {
@@ -1709,11 +1610,7 @@
         }
     }
 
-    /**
-     * See {@link AppsFilterSnapshot#canQueryPackage(AndroidPackage, String)}
-     */
-    @Override
-    public boolean canQueryPackage(@NonNull AndroidPackage querying, String potentialTarget) {
+    boolean canQueryPackage(@NonNull AndroidPackage querying, String potentialTarget) {
         int appId = UserHandle.getAppId(querying.getUid());
         if (appId < Process.FIRST_APPLICATION_UID) {
             return true;
@@ -1768,11 +1665,6 @@
                         + targetPkgSetting + " " + description);
     }
 
-    /**
-     * See {@link AppsFilterSnapshot#dumpQueries(PrintWriter, Integer, DumpState, int[],
-     * QuadFunction)}
-     */
-    @Override
     public void dumpQueries(
             PrintWriter pw, @Nullable Integer filteringAppId, DumpState dumpState, int[] users,
             QuadFunction<Integer, Integer, Integer, Boolean, String[]> getPackagesForUid) {
@@ -1807,30 +1699,27 @@
             }
         }
         pw.println("  system apps queryable: " + mSystemAppsQueryable);
-        synchronized (mLock) {
-            dumpPackageSet(pw, filteringAppId, mForceQueryable.untrackedStorage(),
-                    "forceQueryable", "  ", expandPackages);
-            pw.println("  queries via package name:");
-            dumpQueriesMap(pw, filteringAppId, mQueriesViaPackage, "    ", expandPackages);
-            pw.println("  queries via component:");
-            dumpQueriesMap(pw, filteringAppId, mQueriesViaComponent, "    ", expandPackages);
-            pw.println("  queryable via interaction:");
-            for (int user : users) {
-                pw.append("    User ").append(Integer.toString(user)).println(":");
-                dumpQueriesMap(pw,
-                        filteringAppId == null ? null : UserHandle.getUid(user, filteringAppId),
-                        mImplicitlyQueryable, "      ", expandPackages);
-                dumpQueriesMap(pw,
-                        filteringAppId == null ? null : UserHandle.getUid(user, filteringAppId),
-                        mRetainedImplicitlyQueryable, "      ", expandPackages);
-            }
-            pw.println("  queryable via uses-library:");
-            dumpQueriesMap(pw, filteringAppId, mQueryableViaUsesLibrary, "    ", expandPackages);
+        dumpPackageSet(pw, filteringAppId, mForceQueryable, "forceQueryable", "  ", expandPackages);
+        pw.println("  queries via package name:");
+        dumpQueriesMap(pw, filteringAppId, mQueriesViaPackage, "    ", expandPackages);
+        pw.println("  queries via component:");
+        dumpQueriesMap(pw, filteringAppId, mQueriesViaComponent, "    ", expandPackages);
+        pw.println("  queryable via interaction:");
+        for (int user : users) {
+            pw.append("    User ").append(Integer.toString(user)).println(":");
+            dumpQueriesMap(pw,
+                    filteringAppId == null ? null : UserHandle.getUid(user, filteringAppId),
+                    mImplicitlyQueryable, "      ", expandPackages);
+            dumpQueriesMap(pw,
+                    filteringAppId == null ? null : UserHandle.getUid(user, filteringAppId),
+                    mRetainedImplicitlyQueryable, "      ", expandPackages);
         }
+        pw.println("  queryable via uses-library:");
+        dumpQueriesMap(pw, filteringAppId, mQueryableViaUsesLibrary, "    ", expandPackages);
     }
 
     private static void dumpQueriesMap(PrintWriter pw, @Nullable Integer filteringId,
-            WatchedSparseSetArray<Integer> queriesMap, String spacing,
+            SparseSetArray<Integer> queriesMap, String spacing,
             @Nullable ToString<Integer> toString) {
         for (int i = 0; i < queriesMap.size(); i++) {
             Integer callingId = queriesMap.keyAt(i);
@@ -1855,10 +1744,11 @@
 
     private interface ToString<T> {
         String toString(T input);
+
     }
 
     private static <T> void dumpPackageSet(PrintWriter pw, @Nullable T filteringId,
-            ArraySet<T> targetPkgSet, String subTitle, String spacing,
+            Set<T> targetPkgSet, String subTitle, String spacing,
             @Nullable ToString<T> toString) {
         if (targetPkgSet != null && targetPkgSet.size() > 0
                 && (filteringId == null || targetPkgSet.contains(filteringId))) {
diff --git a/services/core/java/com/android/server/pm/AppsFilterSnapshot.java b/services/core/java/com/android/server/pm/AppsFilterSnapshot.java
deleted file mode 100644
index cb8c649..0000000
--- a/services/core/java/com/android/server/pm/AppsFilterSnapshot.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * 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.pm;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.Process;
-import android.util.ArrayMap;
-import android.util.SparseArray;
-
-import com.android.internal.util.function.QuadFunction;
-import com.android.server.pm.parsing.pkg.AndroidPackage;
-import com.android.server.pm.pkg.PackageStateInternal;
-
-import java.io.PrintWriter;
-
-/**
- * Read-only interface used by computer and snapshots to query the visibility of packages
- */
-public interface AppsFilterSnapshot {
-    /**
-     * Fetches all app Ids that a given setting is currently visible to, per provided user. This
-     * only includes UIDs >= {@link Process#FIRST_APPLICATION_UID} as all other UIDs can already see
-     * all applications.
-     *
-     * If the setting is visible to all UIDs, null is returned. If an app is not visible to any
-     * applications, the int array will be empty.
-     *
-     * @param users            the set of users that should be evaluated for this calculation
-     * @param existingSettings the set of all package settings that currently exist on device
-     * @return a SparseArray mapping userIds to a sorted int array of appIds that may view the
-     * provided setting or null if the app is visible to all and no allow list should be
-     * applied.
-     */
-    SparseArray<int[]> getVisibilityAllowList(PackageStateInternal setting, int[] users,
-            ArrayMap<String, ? extends PackageStateInternal> existingSettings);
-
-    /**
-     * Returns true if the calling package should not be able to see the target package, false if no
-     * filtering should be done.
-     *
-     * @param callingUid       the uid of the caller attempting to access a package
-     * @param callingSetting   the setting attempting to access a package or null if it could not be
-     *                         found
-     * @param targetPkgSetting the package being accessed
-     * @param userId           the user in which this access is being attempted
-     */
-    boolean shouldFilterApplication(int callingUid, @Nullable Object callingSetting,
-            PackageStateInternal targetPkgSetting, int userId);
-
-    /**
-     * Returns whether the querying package is allowed to see the target package.
-     *
-     * @param querying        the querying package
-     * @param potentialTarget the package name of the target package
-     */
-    boolean canQueryPackage(@NonNull AndroidPackage querying, String potentialTarget);
-
-    /**
-     * Dump the packages that are queryable by the querying package.
-     *
-     * @param pw                the output print writer
-     * @param filteringAppId    the querying package's app ID
-     * @param dumpState         the state of the dumping
-     * @param users             the users for which the packages are installed
-     * @param getPackagesForUid the function that produces the package names for given uids
-     */
-    void dumpQueries(PrintWriter pw, @Nullable Integer filteringAppId, DumpState dumpState,
-            int[] users,
-            QuadFunction<Integer, Integer, Integer, Boolean, String[]> getPackagesForUid);
-
-}
diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java
index bf1196d..ed71f1e 100644
--- a/services/core/java/com/android/server/pm/BroadcastHelper.java
+++ b/services/core/java/com/android/server/pm/BroadcastHelper.java
@@ -20,7 +20,6 @@
 import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
 
 import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL;
-import static com.android.server.pm.PackageManagerService.EMPTY_INT_ARRAY;
 import static com.android.server.pm.PackageManagerService.PACKAGE_SCHEME;
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
 import static com.android.server.pm.PackageManagerService.TAG;
@@ -86,11 +85,14 @@
             } else {
                 resolvedUserIds = userIds;
             }
-            doSendBroadcast(action, pkg, extras, flags, targetPkg, finishedReceiver,
-                    resolvedUserIds, false, broadcastAllowList, bOptions);
-            if (instantUserIds != null && instantUserIds != EMPTY_INT_ARRAY) {
+
+            if (ArrayUtils.isEmpty(instantUserIds)) {
                 doSendBroadcast(action, pkg, extras, flags, targetPkg, finishedReceiver,
-                        instantUserIds, true, null, bOptions);
+                        resolvedUserIds, false /* isInstantApp */, broadcastAllowList, bOptions);
+            } else {
+                // send restricted broadcasts for instant apps
+                doSendBroadcast(action, pkg, extras, flags, targetPkg, finishedReceiver,
+                        instantUserIds, true /* isInstantApp */, null, bOptions);
             }
         } catch (RemoteException ex) {
         }
diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java
index 3e204b6..c259797 100644
--- a/services/core/java/com/android/server/pm/Computer.java
+++ b/services/core/java/com/android/server/pm/Computer.java
@@ -599,4 +599,7 @@
     void dumpPackagesProto(@NonNull ProtoOutputStream proto);
 
     void dumpSharedLibrariesProto(@NonNull ProtoOutputStream protoOutputStream);
+
+    @NonNull
+    List<? extends PackageStateInternal> getVolumePackages(@NonNull String volumeUuid);
 }
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 8ac3dca..df7c3ec 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -354,6 +354,11 @@
         public void dumpSharedUsersProto(ProtoOutputStream proto) {
             mSettings.dumpSharedUsersProto(proto);
         }
+
+        public List<? extends PackageStateInternal> getVolumePackages(
+                @NonNull String volumeUuid) {
+            return mSettings.getVolumePackagesLPr(volumeUuid);
+        }
     }
 
     private static final Comparator<ProviderInfo> sProviderInitOrderSorter = (p1, p2) -> {
@@ -380,7 +385,7 @@
     private final ResolveInfo mInstantAppInstallerInfo;
     private final InstantAppRegistry mInstantAppRegistry;
     private final ApplicationInfo mLocalAndroidApplication;
-    private final AppsFilterSnapshot mAppsFilter;
+    private final AppsFilter mAppsFilter;
     private final WatchedArrayMap<String, Integer> mFrozenPackages;
 
     // Immutable service attribute
@@ -5812,4 +5817,10 @@
     public void dumpSharedLibrariesProto(@NonNull ProtoOutputStream proto) {
         mSharedLibraries.dumpProto(proto);
     }
+
+    @NonNull
+    @Override
+    public List<? extends PackageStateInternal> getVolumePackages(@NonNull String volumeUuid) {
+        return mSettings.getVolumePackages(volumeUuid);
+    }
 }
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index bb2ba5c..249099d 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -358,9 +358,7 @@
         }
         final long callingId = Binder.clearCallingIdentity();
         try {
-            synchronized (mPm.mInstallLock) {
-                return performDexOptInternalWithDependenciesLI(p, pkgSetting, options);
-            }
+            return performDexOptInternalWithDependenciesLI(p, pkgSetting, options);
         } finally {
             Binder.restoreCallingIdentity(callingId);
         }
@@ -429,20 +427,18 @@
             throw new IllegalArgumentException("Unknown package: " + packageName);
         }
 
-        synchronized (mPm.mInstallLock) {
-            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
+        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
 
-            // Whoever is calling forceDexOpt wants a compiled package.
-            // Don't use profiles since that may cause compilation to be skipped.
-            final int res = performDexOptInternalWithDependenciesLI(pkg, packageState,
-                    new DexoptOptions(packageName,
-                            getDefaultCompilerFilter(),
-                            DexoptOptions.DEXOPT_FORCE | DexoptOptions.DEXOPT_BOOT_COMPLETE));
+        // Whoever is calling forceDexOpt wants a compiled package.
+        // Don't use profiles since that may cause compilation to be skipped.
+        final int res = performDexOptInternalWithDependenciesLI(pkg, packageState,
+                new DexoptOptions(packageName,
+                        getDefaultCompilerFilter(),
+                        DexoptOptions.DEXOPT_FORCE | DexoptOptions.DEXOPT_BOOT_COMPLETE));
 
-            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-            if (res != PackageDexOptimizer.DEX_OPT_PERFORMED) {
-                throw new IllegalStateException("Failed to dexopt: " + res);
-            }
+        Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+        if (res != PackageDexOptimizer.DEX_OPT_PERFORMED) {
+            throw new IllegalStateException("Failed to dexopt: " + res);
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/InitAppsHelper.java b/services/core/java/com/android/server/pm/InitAppsHelper.java
index 15f26e7..154f32a 100644
--- a/services/core/java/com/android/server/pm/InitAppsHelper.java
+++ b/services/core/java/com/android/server/pm/InitAppsHelper.java
@@ -30,7 +30,7 @@
 import static com.android.server.pm.PackageManagerService.SCAN_REQUIRE_KNOWN;
 import static com.android.server.pm.PackageManagerService.SYSTEM_PARTITIONS;
 import static com.android.server.pm.PackageManagerService.TAG;
-import static com.android.server.pm.pkg.parsing.ParsingPackageUtils.PARSE_CHECK_MAX_SDK_VERSION;
+import static com.android.server.pm.pkg.parsing.ParsingPackageUtils.PARSE_APK_IN_APEX;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -359,7 +359,7 @@
         try {
             if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0) {
                 // when scanning apk in apexes, we want to check the maxSdkVersion
-                parseFlags |= PARSE_CHECK_MAX_SDK_VERSION;
+                parseFlags |= PARSE_APK_IN_APEX;
             }
             mInstallPackageHelper.installPackagesFromDir(scanDir, frameworkSplits, parseFlags,
                     scanFlags, packageParser, executorService);
diff --git a/services/core/java/com/android/server/pm/IntentResolverInterceptor.java b/services/core/java/com/android/server/pm/IntentResolverInterceptor.java
index 603badb..f5eee5a 100644
--- a/services/core/java/com/android/server/pm/IntentResolverInterceptor.java
+++ b/services/core/java/com/android/server/pm/IntentResolverInterceptor.java
@@ -23,11 +23,12 @@
 import android.annotation.RequiresPermission;
 import android.content.ComponentName;
 import android.content.Context;
-import android.content.Intent;
 import android.content.res.Resources;
 import android.provider.DeviceConfig;
+import android.util.Slog;
 
 import com.android.internal.R;
+import com.android.internal.app.ChooserActivity;
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.server.LocalServices;
 import com.android.server.wm.ActivityInterceptorCallback;
@@ -35,25 +36,28 @@
 import com.android.server.wm.ActivityTaskManagerInternal;
 
 /**
- * Service to register an {@code ActivityInterceptorCallback} that modifies any {@code Intent}
- * that's being used to launch a user-space {@code ChooserActivity} by setting the destination
- * component to the delegated component when appropriate.
+ * Redirects Activity starts for the system bundled {@link ChooserActivity} to an external
+ * Sharesheet implementation by modifying the target component when appropriate.
+ * <p>
+ * Note: config_chooserActivity (Used also by ActivityTaskSupervisor) is already updated to point
+ * to the new instance. This value is read and used for the new target component.
  */
 public final class IntentResolverInterceptor {
     private static final String TAG = "IntentResolverIntercept";
-
     private final Context mContext;
-    private boolean mUseDelegateChooser;
+    private final ComponentName mFrameworkChooserComponent;
+    private final ComponentName mUnbundledChooserComponent;
+    private boolean mUseUnbundledSharesheet;
 
     private final ActivityInterceptorCallback mActivityInterceptorCallback =
             new ActivityInterceptorCallback() {
                 @Nullable
                 @Override
                 public ActivityInterceptResult intercept(ActivityInterceptorInfo info) {
-                    if (mUseDelegateChooser && isChooserActivity(info)) {
-                        return new ActivityInterceptResult(
-                                modifyChooserIntent(info.intent),
-                                info.checkedOptions);
+                    if (mUseUnbundledSharesheet && isSystemChooserActivity(info)) {
+                        Slog.d(TAG, "Redirecting to UNBUNDLED Sharesheet");
+                        info.intent.setComponent(mUnbundledChooserComponent);
+                        return new ActivityInterceptResult(info.intent, info.checkedOptions);
                     }
                     return null;
                 }
@@ -61,10 +65,13 @@
 
     public IntentResolverInterceptor(Context context) {
         mContext = context;
+        mFrameworkChooserComponent = new ComponentName(mContext, ChooserActivity.class);
+        mUnbundledChooserComponent =  ComponentName.unflattenFromString(
+                Resources.getSystem().getString(R.string.config_chooserActivity));
     }
 
     /**
-     * Start listening for intents and USE_DELEGATE_CHOOSER property changes.
+     * Start listening for intents and USE_UNBUNDLED_SHARESHEET property changes.
      */
     @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
     public void registerListeners() {
@@ -73,36 +80,25 @@
                         mActivityInterceptorCallback);
 
         DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
-                mContext.getMainExecutor(), properties -> updateUseDelegateChooser());
-        updateUseDelegateChooser();
+                mContext.getMainExecutor(), properties -> updateUseUnbundledSharesheet());
+        updateUseUnbundledSharesheet();
     }
 
     @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
-    private void updateUseDelegateChooser() {
-        mUseDelegateChooser = DeviceConfig.getBoolean(
+    private void updateUseUnbundledSharesheet() {
+        mUseUnbundledSharesheet = DeviceConfig.getBoolean(
                 DeviceConfig.NAMESPACE_SYSTEMUI,
                 SystemUiDeviceConfigFlags.USE_UNBUNDLED_SHARESHEET,
                 false);
+        if (mUseUnbundledSharesheet) {
+            Slog.d(TAG, "using UNBUNDLED Sharesheet");
+        } else {
+            Slog.d(TAG, "using FRAMEWORK Sharesheet");
+        }
     }
 
-    private Intent modifyChooserIntent(Intent intent) {
-        intent.setComponent(getUnbundledChooserComponentName());
-        return intent;
-    }
-
-    private static boolean isChooserActivity(ActivityInterceptorInfo info) {
-        ComponentName targetComponent = new ComponentName(info.aInfo.packageName, info.aInfo.name);
-
-        return targetComponent.equals(getSystemChooserComponentName())
-                || targetComponent.equals(getUnbundledChooserComponentName());
-    }
-
-    private static ComponentName getSystemChooserComponentName() {
-        return new ComponentName("android", "com.android.internal.app.ChooserActivity");
-    }
-
-    private static ComponentName getUnbundledChooserComponentName() {
-        return ComponentName.unflattenFromString(
-                Resources.getSystem().getString(R.string.config_chooserActivity));
+    private boolean isSystemChooserActivity(ActivityInterceptorInfo info) {
+        return mFrameworkChooserComponent.getPackageName().equals(info.aInfo.packageName)
+                && mFrameworkChooserComponent.getClassName().equals(info.aInfo.name);
     }
 }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 002d500..9b5984e 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -410,7 +410,10 @@
                     // Their staging dirs will be removed too
                     PackageInstallerSession root = !session.hasParentSessionId()
                             ? session : mSessions.get(session.getParentSessionId());
-                    if (!root.isDestroyed()) {
+                    if (root == null) {
+                        Slog.e(TAG, "freeStageDirs: found an orphaned session: "
+                                + session.sessionId + " parent=" + session.getParentSessionId());
+                    } else if (!root.isDestroyed()) {
                         root.abandon();
                     }
                 } else {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 4f152d6..f6d8d72 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -720,7 +720,7 @@
     }
 
     @Watched
-    final AppsFilterImpl mAppsFilter;
+    final AppsFilter mAppsFilter;
 
     final PackageParser2.Callback mPackageParserCallback;
 
@@ -979,7 +979,7 @@
         public final InstantAppRegistry instantAppRegistry;
         public final ApplicationInfo androidApplication;
         public final String appPredictionServicePackage;
-        public final AppsFilterSnapshot appsFilter;
+        public final AppsFilter appsFilter;
         public final ComponentResolverApi componentResolver;
         public final PackageManagerService service;
         public final WatchedArrayMap<String, Integer> frozenPackages;
@@ -1431,8 +1431,7 @@
                         RuntimePermissionsPersistence.createInstance(),
                         i.getPermissionManagerServiceInternal(),
                         domainVerificationService, lock),
-                (i, pm) -> AppsFilterImpl.create(i,
-                        i.getLocalService(PackageManagerInternal.class)),
+                (i, pm) -> AppsFilter.create(i, i.getLocalService(PackageManagerInternal.class)),
                 (i, pm) -> (PlatformCompat) ServiceManager.getService("platform_compat"),
                 (i, pm) -> SystemConfig.getInstance(),
                 (i, pm) -> new PackageDexOptimizer(i.getInstaller(), i.getInstallLock(),
@@ -3537,6 +3536,8 @@
                     EventLog.writeEvent(0x534e4554, "145981139", packageInfo.applicationInfo.uid,
                             "");
                 }
+                Log.w(TAG, "Missing required system package: " + packageName + (packageInfo != null
+                        ? ", but found with extended search." : "."));
                 return null;
             }
         } finally {
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
index 396994b..a02237f 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
@@ -99,7 +99,7 @@
     private final Singleton<UserManagerService>
             mUserManagerProducer;
     private final Singleton<Settings> mSettingsProducer;
-    private final Singleton<AppsFilterImpl> mAppsFilterProducer;
+    private final Singleton<AppsFilter> mAppsFilterProducer;
     private final Singleton<PlatformCompat>
             mPlatformCompatProducer;
     private final Singleton<SystemConfig> mSystemConfigProducer;
@@ -148,7 +148,7 @@
             Producer<PermissionManagerServiceInternal> permissionManagerServiceProducer,
             Producer<UserManagerService> userManagerProducer,
             Producer<Settings> settingsProducer,
-            Producer<AppsFilterImpl> appsFilterProducer,
+            Producer<AppsFilter> appsFilterProducer,
             Producer<PlatformCompat> platformCompatProducer,
             Producer<SystemConfig> systemConfigProducer,
             Producer<PackageDexOptimizer> packageDexOptimizerProducer,
@@ -282,7 +282,7 @@
         return mSettingsProducer.get(this, mPackageManager);
     }
 
-    public AppsFilterImpl getAppsFilter() {
+    public AppsFilter getAppsFilter() {
         return mAppsFilterProducer.get(this, mPackageManager);
     }
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index e142ed6..d6ab78b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -3694,7 +3694,7 @@
             }
             List<PermissionInfo> ps = mPermissionManager
                     .queryPermissionsByGroup(groupList.get(i), 0 /*flags*/);
-            final int count = ps.size();
+            final int count = (ps == null ? 0 : ps.size());
             boolean first = true;
             for (int p = 0 ; p < count ; p++) {
                 PermissionInfo pi = ps.get(p);
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 698dbe9..b53cfc5 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -4342,8 +4342,8 @@
      * Return all {@link PackageSetting} that are actively installed on the
      * given {@link VolumeInfo#fsUuid}.
      */
-    List<PackageSetting> getVolumePackagesLPr(String volumeUuid) {
-        ArrayList<PackageSetting> res = new ArrayList<>();
+    List<? extends PackageStateInternal> getVolumePackagesLPr(String volumeUuid) {
+        ArrayList<PackageStateInternal> res = new ArrayList<>();
         for (int i = 0; i < mPackages.size(); i++) {
             final PackageSetting setting = mPackages.valueAt(i);
             if (Objects.equals(volumeUuid, setting.getVolumeUuid())) {
diff --git a/services/core/java/com/android/server/pm/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java
index 8c6b19b..666776b 100644
--- a/services/core/java/com/android/server/pm/StorageEventHelper.java
+++ b/services/core/java/com/android/server/pm/StorageEventHelper.java
@@ -109,8 +109,9 @@
 
         // Remove any apps installed on the forgotten volume
         synchronized (mPm.mLock) {
-            final List<PackageSetting> packages = mPm.mSettings.getVolumePackagesLPr(fsUuid);
-            for (PackageSetting ps : packages) {
+            final List<? extends PackageStateInternal> packages =
+                    mPm.mSettings.getVolumePackagesLPr(fsUuid);
+            for (PackageStateInternal ps : packages) {
                 Slog.d(TAG, "Destroying " + ps.getPackageName()
                         + " because volume was forgotten");
                 mPm.deletePackageVersioned(new VersionedPackage(ps.getPackageName(),
@@ -145,14 +146,14 @@
         final int parseFlags = mPm.getDefParseFlags() | ParsingPackageUtils.PARSE_EXTERNAL_STORAGE;
 
         final Settings.VersionInfo ver;
-        final List<PackageSetting> packages;
+        final List<? extends PackageStateInternal> packages;
         final InstallPackageHelper installPackageHelper = new InstallPackageHelper(mPm);
         synchronized (mPm.mLock) {
             ver = mPm.mSettings.findOrCreateVersion(volumeUuid);
             packages = mPm.mSettings.getVolumePackagesLPr(volumeUuid);
         }
 
-        for (PackageSetting ps : packages) {
+        for (PackageStateInternal ps : packages) {
             freezers.add(mPm.freezePackage(ps.getPackageName(), "loadPrivatePackagesInner"));
             synchronized (mPm.mInstallLock) {
                 final AndroidPackage pkg;
@@ -243,9 +244,9 @@
         final ArrayList<AndroidPackage> unloaded = new ArrayList<>();
         synchronized (mPm.mInstallLock) {
             synchronized (mPm.mLock) {
-                final List<PackageSetting> packages =
+                final List<? extends PackageStateInternal> packages =
                         mPm.mSettings.getVolumePackagesLPr(volumeUuid);
-                for (PackageSetting ps : packages) {
+                for (PackageStateInternal ps : packages) {
                     if (ps.getPkg() == null) continue;
 
                     final AndroidPackage pkg = ps.getPkg();
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index d340561..ee0fdc0 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -5413,190 +5413,170 @@
         (new Shell()).exec(this, in, out, err, args, callback, resultReceiver);
     }
 
-    private static final String PREFIX_HELP_COMMAND = "  ";
-    private static final String PREFIX_HELP_DESCRIPTION = "    ";
-    private static final String PREFIX_HELP_DESCRIPTION_EXTRA_LINES = "      ";
-
-    private static final String CMD_HELP = "help";
-    private static final String CMD_LIST = "list";
-    private static final String CMD_REPORT_SYSTEM_USER_PACKAGE_ALLOWLIST_PROBLEMS =
-            "report-system-user-package-whitelist-problems";
-
-    private static final String ARG_V = "-v";
-    private static final String ARG_VERBOSE = "--verbose";
-    private static final String ARG_ALL = "--all";
-    private static final String ARG_CRITICAL_ONLY = "--critical-only";
-    private static final String ARG_MODE = "--mode";
-
     private final class Shell extends ShellCommand {
 
-    @Override
-    public void onHelp() {
-        final PrintWriter pw = getOutPrintWriter();
-        pw.printf("User manager (user) commands:\n");
-
-        pw.printf("%s%s\n", PREFIX_HELP_COMMAND, CMD_HELP);
-        pw.printf("%sPrints this help text.\n\n", PREFIX_HELP_DESCRIPTION);
-
-        pw.printf("%s%s [%s] [%s]\n", PREFIX_HELP_COMMAND, CMD_LIST, ARG_V, ARG_ALL);
-        pw.printf("%sPrints all users on the system.\n\n", PREFIX_HELP_DESCRIPTION);
-
-        pw.printf("%s%s [%s | %s] [%s] [%s MODE]\n", PREFIX_HELP_COMMAND,
-                CMD_REPORT_SYSTEM_USER_PACKAGE_ALLOWLIST_PROBLEMS,
-                ARG_V, ARG_VERBOSE, ARG_CRITICAL_ONLY, ARG_MODE);
-
-        pw.printf("%sReports all issues on user-type package allowlist XML files. Options:\n",
-                PREFIX_HELP_DESCRIPTION);
-        pw.printf("%s%s | %s: shows extra info, like number of issues\n",
-                PREFIX_HELP_DESCRIPTION, ARG_V, ARG_VERBOSE);
-        pw.printf("%s%s: show only critical issues, excluding warnings\n",
-                PREFIX_HELP_DESCRIPTION, ARG_CRITICAL_ONLY);
-        pw.printf("%s%s MODE: shows what errors would be if device used mode MODE\n"
-                + "%s(where MODE is the allowlist mode integer as defined by "
-                + "config_userTypePackageWhitelistMode)\n\n",
-                PREFIX_HELP_DESCRIPTION, ARG_MODE, PREFIX_HELP_DESCRIPTION_EXTRA_LINES);
-    }
-
-    @Override
-    public int onCommand(String cmd) {
-        if (cmd == null) {
-            return handleDefaultCommands(cmd);
+        @Override
+        public void onHelp() {
+            final PrintWriter pw = getOutPrintWriter();
+            pw.println("User manager (user) commands:");
+            pw.println("  help");
+            pw.println("    Prints this help text.");
+            pw.println();
+            pw.println("  list [-v | --verbose] [--all]");
+            pw.println("    Prints all users on the system.");
+            pw.println();
+            pw.println("  report-system-user-package-whitelist-problems [-v | --verbose] "
+                    + "[--critical-only] [--mode MODE]");
+            pw.println("    Reports all issues on user-type package allowlist XML files. Options:");
+            pw.println("    -v | --verbose: shows extra info, like number of issues");
+            pw.println("    --critical-only: show only critical issues, excluding warnings");
+            pw.println("    --mode MODE: shows what errors would be if device used mode MODE");
+            pw.println("      (where MODE is the allowlist mode integer as defined by "
+                    + "config_userTypePackageWhitelistMode)");
         }
 
-        try {
-            switch(cmd) {
-                case CMD_LIST:
-                    return runList();
-                case CMD_REPORT_SYSTEM_USER_PACKAGE_ALLOWLIST_PROBLEMS:
-                    return runReportPackageAllowlistProblems();
-                default:
-                    return handleDefaultCommands(cmd);
+        @Override
+        public int onCommand(String cmd) {
+            if (cmd == null) {
+                return handleDefaultCommands(cmd);
             }
-        } catch (RemoteException e) {
-            getOutPrintWriter().println("Remote exception: " + e);
-        }
-        return -1;
-    }
 
-    private int runList() throws RemoteException {
-        final PrintWriter pw = getOutPrintWriter();
-        boolean all = false;
-        boolean verbose = false;
-        String opt;
-        while ((opt = getNextOption()) != null) {
-            switch (opt) {
-                case ARG_V:
-                    verbose = true;
-                    break;
-                case ARG_ALL:
-                    all = true;
-                    break;
-                default:
-                    pw.println("Invalid option: " + opt);
-                    return -1;
+            try {
+                switch(cmd) {
+                    case "list":
+                        return runList();
+                    case "report-system-user-package-whitelist-problems":
+                        return runReportPackageAllowlistProblems();
+                    default:
+                        return handleDefaultCommands(cmd);
+                }
+            } catch (RemoteException e) {
+                getOutPrintWriter().println("Remote exception: " + e);
             }
+            return -1;
         }
-        final IActivityManager am = ActivityManager.getService();
-        final List<UserInfo> users = getUsers(/* excludePartial= */ !all,
-                /* excludingDying=*/ false, /* excludePreCreated= */ !all);
-        if (users == null) {
-            pw.println("Error: couldn't get users");
-            return 1;
-        } else {
-            final int size = users.size();
-            int currentUser = UserHandle.USER_NULL;
-            if (verbose) {
-                pw.printf("%d users:\n\n", size);
-                currentUser = am.getCurrentUser().id;
+
+        private int runList() throws RemoteException {
+            final PrintWriter pw = getOutPrintWriter();
+            boolean all = false;
+            boolean verbose = false;
+            String opt;
+            while ((opt = getNextOption()) != null) {
+                switch (opt) {
+                    case "-v":
+                    case "--verbose":
+                        verbose = true;
+                        break;
+                    case "--all":
+                        all = true;
+                        break;
+                    default:
+                        pw.println("Invalid option: " + opt);
+                        return -1;
+                }
+            }
+            final IActivityManager am = ActivityManager.getService();
+            final List<UserInfo> users = getUsers(/* excludePartial= */ !all,
+                    /* excludeDying= */ false, /* excludePreCreated= */ !all);
+            if (users == null) {
+                pw.println("Error: couldn't get users");
+                return 1;
             } else {
-                // NOTE: the standard "list users" command is used by integration tests and
-                // hence should not be changed. If you need to add more info, use the
-                // verbose option.
-                pw.println("Users:");
-            }
-            for (int i = 0; i < size; i++) {
-                final UserInfo user = users.get(i);
-                final boolean running = am.isUserRunning(user.id, 0);
-                final boolean current = user.id == currentUser;
-                final boolean hasParent = user.profileGroupId != user.id
-                        && user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID;
+                final int size = users.size();
+                int currentUser = UserHandle.USER_NULL;
                 if (verbose) {
-                    final DevicePolicyManagerInternal dpm = getDevicePolicyManagerInternal();
-                    String deviceOwner = "";
-                    String profileOwner = "";
-                    if (dpm != null) {
-                        final long ident = Binder.clearCallingIdentity();
-                        // NOTE: dpm methods below CANNOT be called while holding the mUsersLock
-                        try {
-                            if (dpm.getDeviceOwnerUserId() == user.id) {
-                                deviceOwner = " (device-owner)";
-                            }
-                            if (dpm.getProfileOwnerAsUser(user.id) != null) {
-                                profileOwner = " (profile-owner)";
-                            }
-                        } finally {
-                            Binder.restoreCallingIdentity(ident);
-                        }
-                    }
-                    pw.printf("%d: id=%d, name=%s, type=%s, flags=%s%s%s%s%s%s%s%s%s\n",
-                            i,
-                            user.id,
-                            user.name,
-                            user.userType.replace("android.os.usertype.", ""),
-                            UserInfo.flagsToString(user.flags),
-                            hasParent ? " (parentId=" + user.profileGroupId + ")" : "",
-                            running ? " (running)" : "",
-                            user.partial ? " (partial)" : "",
-                            user.preCreated ? " (pre-created)" : "",
-                            user.convertedFromPreCreated ? " (converted)" : "",
-                            deviceOwner, profileOwner,
-                            current ? " (current)" : "");
+                    pw.printf("%d users:\n\n", size);
+                    currentUser = am.getCurrentUser().id;
                 } else {
                     // NOTE: the standard "list users" command is used by integration tests and
                     // hence should not be changed. If you need to add more info, use the
                     // verbose option.
-                    pw.printf("\t%s%s\n", user, running ? " running" : "");
+                    pw.println("Users:");
                 }
+                for (int i = 0; i < size; i++) {
+                    final UserInfo user = users.get(i);
+                    final boolean running = am.isUserRunning(user.id, 0);
+                    final boolean current = user.id == currentUser;
+                    final boolean hasParent = user.profileGroupId != user.id
+                            && user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID;
+                    if (verbose) {
+                        final DevicePolicyManagerInternal dpm = getDevicePolicyManagerInternal();
+                        String deviceOwner = "";
+                        String profileOwner = "";
+                        if (dpm != null) {
+                            final long ident = Binder.clearCallingIdentity();
+                            // NOTE: dpm methods below CANNOT be called while holding the mUsersLock
+                            try {
+                                if (dpm.getDeviceOwnerUserId() == user.id) {
+                                    deviceOwner = " (device-owner)";
+                                }
+                                if (dpm.getProfileOwnerAsUser(user.id) != null) {
+                                    profileOwner = " (profile-owner)";
+                                }
+                            } finally {
+                                Binder.restoreCallingIdentity(ident);
+                            }
+                        }
+                        pw.printf("%d: id=%d, name=%s, type=%s, flags=%s%s%s%s%s%s%s%s%s\n",
+                                i,
+                                user.id,
+                                user.name,
+                                user.userType.replace("android.os.usertype.", ""),
+                                UserInfo.flagsToString(user.flags),
+                                hasParent ? " (parentId=" + user.profileGroupId + ")" : "",
+                                running ? " (running)" : "",
+                                user.partial ? " (partial)" : "",
+                                user.preCreated ? " (pre-created)" : "",
+                                user.convertedFromPreCreated ? " (converted)" : "",
+                                deviceOwner, profileOwner,
+                                current ? " (current)" : "");
+                    } else {
+                        // NOTE: the standard "list users" command is used by integration tests and
+                        // hence should not be changed. If you need to add more info, use the
+                        // verbose option.
+                        pw.printf("\t%s%s\n", user, running ? " running" : "");
+                    }
+                }
+                return 0;
+            }
+        }
+
+        private int runReportPackageAllowlistProblems() {
+            final PrintWriter pw = getOutPrintWriter();
+            boolean verbose = false;
+            boolean criticalOnly = false;
+            int mode = UserSystemPackageInstaller.USER_TYPE_PACKAGE_WHITELIST_MODE_NONE;
+            String opt;
+            while ((opt = getNextOption()) != null) {
+                switch (opt) {
+                    case "-v":
+                    case "--verbose":
+                        verbose = true;
+                        break;
+                    case "--critical-only":
+                        criticalOnly = true;
+                        break;
+                    case "--mode":
+                        mode = Integer.parseInt(getNextArgRequired());
+                        break;
+                    default:
+                        pw.println("Invalid option: " + opt);
+                        return -1;
+                }
+            }
+
+            Slog.d(LOG_TAG, "runReportPackageAllowlistProblems(): verbose=" + verbose
+                    + ", criticalOnly=" + criticalOnly
+                    + ", mode=" + UserSystemPackageInstaller.modeToString(mode));
+
+            try (IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ")) {
+                mSystemPackageInstaller.dumpPackageWhitelistProblems(ipw, mode, verbose,
+                        criticalOnly);
             }
             return 0;
         }
-    }
 
-    private int runReportPackageAllowlistProblems() {
-        final PrintWriter pw = getOutPrintWriter();
-        boolean verbose = false;
-        boolean criticalOnly = false;
-        int mode = UserSystemPackageInstaller.USER_TYPE_PACKAGE_WHITELIST_MODE_NONE;
-        String opt;
-        while ((opt = getNextOption()) != null) {
-            switch (opt) {
-                case ARG_V:
-                case ARG_VERBOSE:
-                    verbose = true;
-                    break;
-                case ARG_CRITICAL_ONLY:
-                    criticalOnly = true;
-                    break;
-                case ARG_MODE:
-                    mode = Integer.parseInt(getNextArgRequired());
-                    break;
-                default:
-                    pw.println("Invalid option: " + opt);
-                    return -1;
-            }
-        }
-
-        Slog.d(LOG_TAG, "runReportPackageAllowlistProblems(): verbose=" + verbose
-                + ", criticalOnly=" + criticalOnly
-                + ", mode=" + UserSystemPackageInstaller.modeToString(mode));
-
-        try (IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ")) {
-            mSystemPackageInstaller.dumpPackageWhitelistProblems(ipw, mode, verbose,
-                    criticalOnly);
-        }
-        return 0;
-    }
-    }
+    } // class Shell
 
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
index 7e4da94..4fae6b8 100644
--- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java
+++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
@@ -45,7 +45,6 @@
 import android.util.Log;
 import android.util.Slog;
 
-import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.os.RoSystemProperties;
 import com.android.internal.util.ArrayUtils;
@@ -94,8 +93,6 @@
 
     private final Context mContext;
     private IPackageManager mPackageManager;
-    private final Object mInstallLock;
-    @GuardedBy("mInstallLock")
     private final Installer mInstaller;
 
     private final Handler mHandler;
@@ -105,10 +102,9 @@
     }
 
     public ArtManagerService(Context context, Installer installer,
-            Object installLock) {
+            Object ignored) {
         mContext = context;
         mInstaller = installer;
-        mInstallLock = installLock;
         mHandler = new Handler(BackgroundThread.getHandler().getLooper());
 
         LocalServices.addService(ArtManagerInternal.class, new ArtManagerInternalImpl());
@@ -273,16 +269,14 @@
     private void createProfileSnapshot(String packageName, String profileName, String classpath,
             int appId, ISnapshotRuntimeProfileCallback callback) {
         // Ask the installer to snapshot the profile.
-        synchronized (mInstallLock) {
-            try {
-                if (!mInstaller.createProfileSnapshot(appId, packageName, profileName, classpath)) {
-                    postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
-                    return;
-                }
-            } catch (InstallerException e) {
+        try {
+            if (!mInstaller.createProfileSnapshot(appId, packageName, profileName, classpath)) {
                 postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
                 return;
             }
+        } catch (InstallerException e) {
+            postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
+            return;
         }
 
         // Open the snapshot and invoke the callback.
@@ -308,13 +302,11 @@
             Slog.d(TAG, "Destroying profile snapshot for" + packageName + ":" + profileName);
         }
 
-        synchronized (mInstallLock) {
-            try {
-                mInstaller.destroyProfileSnapshot(packageName, profileName);
-            } catch (InstallerException e) {
-                Slog.e(TAG, "Failed to destroy profile snapshot for " +
-                    packageName + ":" + profileName, e);
-            }
+        try {
+            mInstaller.destroyProfileSnapshot(packageName, profileName);
+        } catch (InstallerException e) {
+            Slog.e(TAG, "Failed to destroy profile snapshot for " + packageName + ":" + profileName,
+                    e);
         }
     }
 
@@ -480,9 +472,7 @@
             for (int i = packageProfileNames.size() - 1; i >= 0; i--) {
                 String codePath = packageProfileNames.keyAt(i);
                 String profileName = packageProfileNames.valueAt(i);
-                synchronized (mInstallLock) {
-                    mInstaller.dumpProfiles(sharedGid, pkg.getPackageName(), profileName, codePath);
-                }
+                mInstaller.dumpProfiles(sharedGid, pkg.getPackageName(), profileName, codePath);
             }
         } catch (InstallerException e) {
             Slog.w(TAG, "Failed to dump profiles", e);
@@ -512,10 +502,8 @@
                     ") to " + outDexFile);
             final long callingId = Binder.clearCallingIdentity();
             try {
-                synchronized (mInstallLock) {
-                    return mInstaller.compileLayouts(apkPath, packageName, outDexFile,
-                            pkg.getUid());
-                }
+                return mInstaller.compileLayouts(apkPath, packageName, outDexFile,
+                        pkg.getUid());
             } finally {
                 Binder.restoreCallingIdentity(callingId);
             }
diff --git a/services/core/java/com/android/server/pm/parsing/library/ApexSharedLibraryUpdater.java b/services/core/java/com/android/server/pm/parsing/library/ApexSharedLibraryUpdater.java
index 0418afb..1a2ff26 100644
--- a/services/core/java/com/android/server/pm/parsing/library/ApexSharedLibraryUpdater.java
+++ b/services/core/java/com/android/server/pm/parsing/library/ApexSharedLibraryUpdater.java
@@ -19,6 +19,7 @@
 import android.util.ArrayMap;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.build.UnboundedSdkLevel;
 import com.android.server.SystemConfig;
 import com.android.server.pm.parsing.pkg.ParsedPackage;
 
@@ -51,8 +52,11 @@
 
     private void updateSharedLibraryForPackage(SystemConfig.SharedLibraryEntry entry,
             ParsedPackage parsedPackage) {
-        if (entry.onBootclasspathBefore != 0
-                && parsedPackage.getTargetSdkVersion() < entry.onBootclasspathBefore) {
+        if (entry.onBootclasspathBefore != null
+                && isTargetSdkAtMost(
+                        parsedPackage.getTargetSdkVersion(),
+                        entry.onBootclasspathBefore)
+                && UnboundedSdkLevel.isAtLeast(entry.onBootclasspathBefore)) {
             // this package targets an API where this library was in the BCP, so add
             // the library transparently in case the package is using it
             prefixRequiredLibrary(parsedPackage, entry.name);
@@ -64,4 +68,19 @@
             removeLibrary(parsedPackage, entry.name);
         }
     }
+
+    private static boolean isTargetSdkAtMost(int targetSdk, String onBcpBefore) {
+        if (isCodename(onBcpBefore)) {
+            return targetSdk < 10000;
+        }
+        return targetSdk < Integer.parseInt(onBcpBefore);
+    }
+
+    private static boolean isCodename(String version) {
+        if (version.length() == 0) {
+            throw new IllegalArgumentException();
+        }
+        // assume Android codenames start with upper case letters.
+        return Character.isUpperCase((version.charAt(0)));
+    }
 }
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
index f255db4..9897c42 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
@@ -209,8 +209,6 @@
 
     public static final int SDK_VERSION = Build.VERSION.SDK_INT;
     public static final String[] SDK_CODENAMES = Build.VERSION.ACTIVE_CODENAMES;
-    public static final String[] PREVIOUS_CODENAMES =
-            Build.VERSION.KNOWN_CODENAMES.toArray(new String[]{});
 
     public static boolean sCompatibilityModeEnabled = true;
     public static boolean sUseRoundIcon = false;
@@ -238,7 +236,7 @@
      */
     public static final int PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY = 1 << 7;
     public static final int PARSE_FRAMEWORK_RES_SPLITS = 1 << 8;
-    public static final int PARSE_CHECK_MAX_SDK_VERSION = 1 << 9;
+    public static final int PARSE_APK_IN_APEX = 1 << 9;
 
     public static final int PARSE_CHATTY = 1 << 31;
 
@@ -1534,7 +1532,7 @@
             ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags)
             throws IOException, XmlPullParserException {
         if (SDK_VERSION > 0) {
-            final boolean checkMaxSdkVersion = (flags & PARSE_CHECK_MAX_SDK_VERSION) != 0;
+            final boolean isApkInApex = (flags & PARSE_APK_IN_APEX) != 0;
             TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestUsesSdk);
             try {
                 int minVers = ParsingUtils.DEFAULT_MIN_SDK_VERSION;
@@ -1569,7 +1567,7 @@
                     targetCode = minCode;
                 }
 
-                if (checkMaxSdkVersion) {
+                if (isApkInApex) {
                     val = sa.peekValue(R.styleable.AndroidManifestUsesSdk_maxSdkVersion);
                     if (val != null) {
                         // maxSdkVersion only supports integer
@@ -1578,7 +1576,8 @@
                 }
 
                 ParseResult<Integer> targetSdkVersionResult = FrameworkParsingPackageUtils
-                        .computeTargetSdkVersion(targetVers, targetCode, SDK_CODENAMES, input);
+                        .computeTargetSdkVersion(targetVers, targetCode, SDK_CODENAMES, input,
+                                isApkInApex);
                 if (targetSdkVersionResult.isError()) {
                     return input.error(targetSdkVersionResult);
                 }
@@ -1601,7 +1600,7 @@
 
                 pkg.setMinSdkVersion(minSdkVersion)
                         .setTargetSdkVersion(targetSdkVersion);
-                if (checkMaxSdkVersion) {
+                if (isApkInApex) {
                     ParseResult<Integer> maxSdkVersionResult = FrameworkParsingPackageUtils
                             .computeMaxSdkVersion(maxVers, SDK_VERSION, input);
                     if (maxSdkVersionResult.isError()) {
diff --git a/services/core/java/com/android/server/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java
index 70d69c6..e157a27 100644
--- a/services/core/java/com/android/server/policy/AppOpsPolicy.java
+++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java
@@ -187,8 +187,12 @@
 
         initializeActivityRecognizersTags();
 
-        // If this device does not have telephony, restrict the phone call ops
-        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+        // Restrict phone call ops if the TelecomService will not start (conditioned on having
+        // FEATURE_MICROPHONE, FEATURE_TELECOM, or FEATURE_TELEPHONY).
+        PackageManager pm = mContext.getPackageManager();
+        if (!pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
+                && !pm.hasSystemFeature(PackageManager.FEATURE_MICROPHONE)
+                && !pm.hasSystemFeature(PackageManager.FEATURE_TELECOM)) {
             AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class);
             appOps.setUserRestrictionForUser(AppOpsManager.OP_PHONE_CALL_MICROPHONE, true, mToken,
                     null, UserHandle.USER_ALL);
diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
index 2f68f56..bce1cce 100644
--- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
@@ -29,6 +29,7 @@
 import static android.content.Intent.EXTRA_PACKAGE_NAME;
 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
 import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
+import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.hardware.SensorPrivacyManager.EXTRA_ALL_SENSORS;
 import static android.hardware.SensorPrivacyManager.EXTRA_SENSOR;
@@ -77,6 +78,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
 import android.content.res.Configuration;
 import android.graphics.drawable.Icon;
 import android.hardware.ISensorPrivacyListener;
@@ -155,6 +157,7 @@
     private final AppOpsManager mAppOpsManager;
     private final AppOpsManagerInternal mAppOpsManagerInternal;
     private final TelephonyManager mTelephonyManager;
+    private final PackageManagerInternal mPackageManagerInternal;
 
     private CameraPrivacyLightController mCameraPrivacyLightController;
 
@@ -178,6 +181,7 @@
         mActivityManagerInternal = getLocalService(ActivityManagerInternal.class);
         mActivityTaskManager = context.getSystemService(ActivityTaskManager.class);
         mTelephonyManager = context.getSystemService(TelephonyManager.class);
+        mPackageManagerInternal = getLocalService(PackageManagerInternal.class);
         mSensorPrivacyServiceImpl = new SensorPrivacyServiceImpl();
     }
 
@@ -828,6 +832,12 @@
          * sensor privacy.
          */
         private void enforceObserveSensorPrivacyPermission() {
+            String systemUIPackage = mContext.getString(R.string.config_systemUi);
+            if (Binder.getCallingUid() == mPackageManagerInternal
+                    .getPackageUid(systemUIPackage, MATCH_SYSTEM_ONLY, UserHandle.USER_SYSTEM)) {
+                // b/221782106, possible race condition with role grant might bootloop device.
+                return;
+            }
             enforcePermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY,
                     "Observing sensor privacy changes requires the following permission: "
                             + android.Manifest.permission.OBSERVE_SENSOR_PRIVACY);
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandler.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandler.java
index c0ab65a..05d92be 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandler.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandler.java
@@ -17,7 +17,6 @@
 package com.android.server.soundtrigger_middleware;
 
 import android.annotation.NonNull;
-import android.media.permission.SafeCloseable;
 import android.media.soundtrigger.ModelParameterRange;
 import android.media.soundtrigger.PhraseRecognitionEvent;
 import android.media.soundtrigger.PhraseSoundModel;
@@ -30,6 +29,7 @@
 import android.os.IBinder;
 
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.Map;
 import java.util.Queue;
@@ -63,18 +63,24 @@
  */
 public class SoundTriggerHalConcurrentCaptureHandler implements ISoundTriggerHal,
         ICaptureStateNotifier.Listener {
-    private final @NonNull ISoundTriggerHal mDelegate;
+    @NonNull private final ISoundTriggerHal mDelegate;
     private GlobalCallback mGlobalCallback;
+    /**
+     * This lock must be held to synchronize forward calls (start/stop/onCaptureStateChange) that
+     * update the mActiveModels set and mCaptureState.
+     * It must not be locked in HAL callbacks to avoid deadlocks.
+     */
+    @NonNull private final Object mStartStopLock = new Object();
 
     /**
      * Information about a model that is currently loaded. This is needed in order to be able to
      * send abort events to its designated callback.
      */
     private static class LoadedModel {
-        final int type;
-        final @NonNull ModelCallback callback;
+        public final int type;
+        @NonNull public final ModelCallback callback;
 
-        private LoadedModel(int type, @NonNull ModelCallback callback) {
+        LoadedModel(int type, @NonNull ModelCallback callback) {
             this.type = type;
             this.callback = callback;
         }
@@ -83,19 +89,19 @@
     /**
      * This map holds the model type for every model that is loaded.
      */
-    private final @NonNull Map<Integer, LoadedModel> mLoadedModels = new ConcurrentHashMap<>();
+    @NonNull private final Map<Integer, LoadedModel> mLoadedModels = new ConcurrentHashMap<>();
 
     /**
      * A set of all models that are currently active.
      * We use this in order to know which models to stop in case of external capture.
      * Used as a lock to synchronize operations that effect activity.
      */
-    private final @NonNull Set<Integer> mActiveModels = new HashSet<>();
+    @NonNull private final Set<Integer> mActiveModels = new HashSet<>();
 
     /**
      * Notifier for changes in capture state.
      */
-    private final @NonNull ICaptureStateNotifier mNotifier;
+    @NonNull private final ICaptureStateNotifier mNotifier;
 
     /**
      * Whether capture is active.
@@ -106,10 +112,10 @@
      * Since we're wrapping the death recipient, we need to keep a translation map for unlinking.
      * Key is the client recipient, value is the wrapper.
      */
-    private final @NonNull Map<IBinder.DeathRecipient, IBinder.DeathRecipient>
+    @NonNull private final Map<IBinder.DeathRecipient, IBinder.DeathRecipient>
             mDeathRecipientMap = new ConcurrentHashMap<>();
 
-    private final @NonNull CallbackThread mCallbackThread = new CallbackThread();
+    @NonNull private final CallbackThread mCallbackThread = new CallbackThread();
 
     public SoundTriggerHalConcurrentCaptureHandler(
             @NonNull ISoundTriggerHal delegate,
@@ -122,20 +128,28 @@
     @Override
     public void startRecognition(int modelHandle, int deviceHandle, int ioHandle,
             RecognitionConfig config) {
-        synchronized (mActiveModels) {
-            if (mCaptureState) {
-                throw new RecoverableException(Status.RESOURCE_CONTENTION);
+        synchronized (mStartStopLock) {
+            synchronized (mActiveModels) {
+                if (mCaptureState) {
+                    throw new RecoverableException(Status.RESOURCE_CONTENTION);
+                }
+                mDelegate.startRecognition(modelHandle, deviceHandle, ioHandle, config);
+                mActiveModels.add(modelHandle);
             }
-            mDelegate.startRecognition(modelHandle, deviceHandle, ioHandle, config);
-            mActiveModels.add(modelHandle);
         }
     }
 
     @Override
     public void stopRecognition(int modelHandle) {
-        synchronized (mActiveModels) {
-            mDelegate.stopRecognition(modelHandle);
-            mActiveModels.remove(modelHandle);
+        synchronized (mStartStopLock) {
+            boolean wasActive;
+            synchronized (mActiveModels) {
+                wasActive = mActiveModels.remove(modelHandle);
+            }
+            if (wasActive) {
+                // Must be done outside of the lock, since it may trigger synchronous callbacks.
+                mDelegate.stopRecognition(modelHandle);
+            }
         }
         // Block until all previous events are delivered. Since this is potentially blocking on
         // upward calls, it must be done outside the lock.
@@ -144,27 +158,38 @@
 
     @Override
     public void onCaptureStateChange(boolean active) {
-        synchronized (mActiveModels) {
+        synchronized (mStartStopLock) {
             if (active) {
-                // Abort all active models. This must be done as one transaction to the event
-                // thread, in order to be able to dedupe events before they are delivered.
-                try (SafeCloseable ignored = mCallbackThread.stallReader()) {
-                    for (int modelHandle : mActiveModels) {
-                        mDelegate.stopRecognition(modelHandle);
-                        LoadedModel model = mLoadedModels.get(modelHandle);
-                        // An abort event must be the last one for its model.
-                        mCallbackThread.pushWithDedupe(modelHandle, true,
-                                () -> notifyAbort(modelHandle, model));
-                    }
-                }
+                abortAllActiveModels();
             } else {
-                mGlobalCallback.onResourcesAvailable();
+                if (mGlobalCallback != null) {
+                    mGlobalCallback.onResourcesAvailable();
+                }
             }
-
             mCaptureState = active;
         }
     }
 
+    private void abortAllActiveModels() {
+        while (true) {
+            int toStop;
+            synchronized (mActiveModels) {
+                Iterator<Integer> iterator = mActiveModels.iterator();
+                if (!iterator.hasNext()) {
+                    return;
+                }
+                toStop = iterator.next();
+                mActiveModels.remove(toStop);
+            }
+            // Invoke stop outside of the lock.
+            mDelegate.stopRecognition(toStop);
+
+            LoadedModel model = mLoadedModels.get(toStop);
+            // Queue an abort event (no need to flush).
+            mCallbackThread.push(() -> notifyAbort(toStop, model));
+        }
+    }
+
     @Override
     public int loadSoundModel(SoundModel soundModel, ModelCallback callback) {
         int handle = mDelegate.loadSoundModel(soundModel, new CallbackWrapper(callback));
@@ -188,23 +213,13 @@
 
     @Override
     public void registerCallback(GlobalCallback callback) {
-        mGlobalCallback = new GlobalCallback() {
-            @Override
-            public void onResourcesAvailable() {
-                mCallbackThread.push(callback::onResourcesAvailable);
-            }
-        };
+        mGlobalCallback = () -> mCallbackThread.push(callback::onResourcesAvailable);
         mDelegate.registerCallback(mGlobalCallback);
     }
 
     @Override
     public void linkToDeath(IBinder.DeathRecipient recipient) {
-        IBinder.DeathRecipient wrapper = new IBinder.DeathRecipient() {
-            @Override
-            public void binderDied() {
-                mCallbackThread.push(() -> recipient.binderDied());
-            }
-        };
+        IBinder.DeathRecipient wrapper = () -> mCallbackThread.push(recipient::binderDied);
         mDelegate.linkToDeath(wrapper);
         mDeathRecipientMap.put(recipient, wrapper);
     }
@@ -215,7 +230,7 @@
     }
 
     private class CallbackWrapper implements ISoundTriggerHal.ModelCallback {
-        private final @NonNull ISoundTriggerHal.ModelCallback mDelegateCallback;
+        @NonNull private final ISoundTriggerHal.ModelCallback mDelegateCallback;
 
         private CallbackWrapper(@NonNull ModelCallback delegateCallback) {
             mDelegateCallback = delegateCallback;
@@ -223,18 +238,36 @@
 
         @Override
         public void recognitionCallback(int modelHandle, RecognitionEvent event) {
-            // A recognition event must be the last one for its model, unless it is a forced one
-            // (those leave the model active).
-            mCallbackThread.pushWithDedupe(modelHandle, !event.recognitionStillActive,
-                    () -> mDelegateCallback.recognitionCallback(modelHandle, event));
+            synchronized (mActiveModels) {
+                if (!mActiveModels.contains(modelHandle)) {
+                    // Discard the event.
+                    return;
+                }
+                if (!event.recognitionStillActive) {
+                    mActiveModels.remove(modelHandle);
+                }
+                // A recognition event must be the last one for its model, unless it indicates that
+                // recognition is still active.
+                mCallbackThread.push(
+                        () -> mDelegateCallback.recognitionCallback(modelHandle, event));
+            }
         }
 
         @Override
         public void phraseRecognitionCallback(int modelHandle, PhraseRecognitionEvent event) {
-            // A recognition event must be the last one for its model, unless it is a forced one
-            // (those leave the model active).
-            mCallbackThread.pushWithDedupe(modelHandle, !event.common.recognitionStillActive,
-                    () -> mDelegateCallback.phraseRecognitionCallback(modelHandle, event));
+            synchronized (mActiveModels) {
+                if (!mActiveModels.contains(modelHandle)) {
+                    // Discard the event.
+                    return;
+                }
+                if (!event.common.recognitionStillActive) {
+                    mActiveModels.remove(modelHandle);
+                }
+                // A recognition event must be the last one for its model, unless it indicates that
+                // recognition is still active.
+                mCallbackThread.push(
+                        () -> mDelegateCallback.phraseRecognitionCallback(modelHandle, event));
+            }
         }
 
         @Override
@@ -254,36 +287,12 @@
      * <ul>
      * <li>Events are processed on a separate thread than the thread that pushed them, in the order
      * they were pushed.
-     * <li>Events can be deduped upon entry to the queue. This is achieved as follows:
-     * <ul>
-     *     <li>Temporarily stall the reader via {@link #stallReader()}.
-     *     <li>Within this scope, push as many events as needed via
-     *     {@link #pushWithDedupe(int, boolean, Runnable)}.
-     *     If an event with the same model handle as the one being pushed is already in the queue
-     *     and has been marked as "lastForModel", the new event will be discarded before entering
-     *     the queue.
-     *     <li>Finally, un-stall the reader by existing the scope.
-     *     <li>Events that do not require deduping can be pushed via {@link #push(Runnable)}.
-     * </ul>
      * <li>Events can be flushed via {@link #flush()}. This will block until all events pushed prior
      * to this call have been fully processed.
      * </ul>
      */
     private static class CallbackThread {
-        private static class Entry {
-            final boolean lastForModel;
-            final int modelHandle;
-            final Runnable runnable;
-
-            private Entry(boolean lastForModel, int modelHandle, Runnable runnable) {
-                this.lastForModel = lastForModel;
-                this.modelHandle = modelHandle;
-                this.runnable = runnable;
-            }
-        }
-
-        private boolean mStallReader = false;
-        private final Queue<Entry> mList = new LinkedList<>();
+        private final Queue<Runnable> mList = new LinkedList<>();
         private int mPushCount = 0;
         private int mProcessedCount = 0;
 
@@ -312,23 +321,11 @@
          * @param runnable The runnable to push.
          */
         void push(Runnable runnable) {
-            pushEntry(new Entry(false, 0, runnable), false);
-        }
-
-
-        /**
-         * Push a new runnable to the queue, with deduping.
-         * If an entry with the same model handle is already in the queue and was designated as
-         * last for model, this one will be discarded.
-         *
-         * @param modelHandle The model handle, used for deduping purposes.
-         * @param lastForModel If true, this entry will be considered the last one for this model
-         *                     and any subsequence calls for this handle (whether lastForModel or
-         *                     not) will be discarded while this entry is in the queue.
-         * @param runnable    The runnable to push.
-         */
-        void pushWithDedupe(int modelHandle, boolean lastForModel, Runnable runnable) {
-            pushEntry(new Entry(lastForModel, modelHandle, runnable), true);
+            synchronized (mList) {
+                mList.add(runnable);
+                mPushCount++;
+                mList.notifyAll();
+            }
         }
 
         /**
@@ -346,45 +343,15 @@
             }
         }
 
-        /**
-         * Creates a scope (using a try-with-resources block), within which events that are pushed
-         * remain queued and processed. This is useful in order to utilize deduping.
-         */
-        SafeCloseable stallReader() {
-            synchronized (mList) {
-                mStallReader = true;
-                return () -> {
-                    synchronized (mList) {
-                        mStallReader = false;
-                        mList.notifyAll();
-                    }
-                };
-            }
-        }
-
-        private void pushEntry(Entry entry, boolean dedupe) {
-            synchronized (mList) {
-                if (dedupe) {
-                    for (Entry existing : mList) {
-                        if (existing.lastForModel && existing.modelHandle == entry.modelHandle) {
-                            return;
-                        }
-                    }
-                }
-                mList.add(entry);
-                mPushCount++;
-                mList.notifyAll();
-            }
-        }
-
         private Runnable pop() throws InterruptedException {
             synchronized (mList) {
-                while (mStallReader || mList.isEmpty()) {
+                while (mList.isEmpty()) {
                     mList.wait();
                 }
-                return mList.remove().runnable;
+                return mList.remove();
             }
         }
+
     }
 
     /** Notify the client that recognition has been aborted. */
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw3Compat.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw3Compat.java
index 0a085ba..ebe0ff8 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw3Compat.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw3Compat.java
@@ -206,6 +206,16 @@
         public void onResourcesAvailable() {
             mDelegate.onResourcesAvailable();
         }
+
+        @Override
+        public int getInterfaceVersion() {
+            return ISoundTriggerHwGlobalCallback.VERSION;
+        }
+
+        @Override
+        public String getInterfaceHash() {
+            return ISoundTriggerHwGlobalCallback.HASH;
+        }
     }
 
     private static class ModelCallbackAdaper extends ISoundTriggerHwCallback.Stub {
@@ -233,5 +243,15 @@
             event.recognitionStillActive |= event.status == RecognitionStatus.FORCED;
             mDelegate.recognitionCallback(model, event);
         }
+
+        @Override
+        public int getInterfaceVersion() {
+            return ISoundTriggerHwCallback.VERSION;
+        }
+
+        @Override
+        public String getInterfaceHash() {
+            return ISoundTriggerHwCallback.HASH;
+        }
     }
 }
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 8087738..b685572 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -903,11 +903,11 @@
     }
 
     @Override
-    public void hideAuthenticationDialog() {
+    public void hideAuthenticationDialog(long requestId) {
         enforceBiometricDialog();
         if (mBar != null) {
             try {
-                mBar.hideAuthenticationDialog();
+                mBar.hideAuthenticationDialog(requestId);
             } catch (RemoteException ex) {
             }
         }
diff --git a/services/core/java/com/android/server/telecom/OWNERS b/services/core/java/com/android/server/telecom/OWNERS
index 39be2c1..cad25a4 100644
--- a/services/core/java/com/android/server/telecom/OWNERS
+++ b/services/core/java/com/android/server/telecom/OWNERS
@@ -1,6 +1 @@
-breadley@google.com
-hallliu@google.com
-tgunn@google.com
-xiaotonj@google.com
-shuoq@google.com
-rgreenwalt@google.com
+file:platform/frameworks/base:/telecomm/OWNERS
diff --git a/services/core/java/com/android/server/utils/WatchedArrayList.java b/services/core/java/com/android/server/utils/WatchedArrayList.java
index 6059f96..bb0ba13 100644
--- a/services/core/java/com/android/server/utils/WatchedArrayList.java
+++ b/services/core/java/com/android/server/utils/WatchedArrayList.java
@@ -273,13 +273,6 @@
     }
 
     /**
-     * Return true if all the objects in the given collection are in this array list.
-     */
-    public boolean containsAll(Collection<?> c) {
-        return mStorage.containsAll(c);
-    }
-
-    /**
      * Ensure capacity.
      */
     public void ensureCapacity(int min) {
diff --git a/services/core/java/com/android/server/utils/WatchedSparseBooleanMatrix.java b/services/core/java/com/android/server/utils/WatchedSparseBooleanMatrix.java
index c43e7f9..25ae000 100644
--- a/services/core/java/com/android/server/utils/WatchedSparseBooleanMatrix.java
+++ b/services/core/java/com/android/server/utils/WatchedSparseBooleanMatrix.java
@@ -18,7 +18,6 @@
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
 
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.Size;
 
@@ -169,19 +168,12 @@
      * A copy constructor that can be used for snapshotting.
      */
     private WatchedSparseBooleanMatrix(WatchedSparseBooleanMatrix r) {
-        copyFrom(r);
-    }
-
-    /**
-     * Copy from src to this.
-     */
-    public void copyFrom(@NonNull WatchedSparseBooleanMatrix src) {
-        mOrder = src.mOrder;
-        mSize = src.mSize;
-        mKeys = src.mKeys.clone();
-        mMap = src.mMap.clone();
-        mInUse = src.mInUse.clone();
-        mValues = src.mValues.clone();
+        mOrder = r.mOrder;
+        mSize = r.mSize;
+        mKeys = r.mKeys.clone();
+        mMap = r.mMap.clone();
+        mInUse = r.mInUse.clone();
+        mValues = r.mValues.clone();
     }
 
     /**
diff --git a/services/core/java/com/android/server/utils/WatchedSparseSetArray.java b/services/core/java/com/android/server/utils/WatchedSparseSetArray.java
deleted file mode 100644
index 05db12e..0000000
--- a/services/core/java/com/android/server/utils/WatchedSparseSetArray.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * 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.utils;
-
-import android.annotation.NonNull;
-import android.util.ArraySet;
-import android.util.SparseSetArray;
-
-
-/**
- * A watched variant of SparseSetArray.  Changes to the array are notified to
- * registered {@link Watcher}s.
- * @param <T> The element type, stored in the SparseSetArray.
- */
-public class WatchedSparseSetArray<T> extends WatchableImpl implements Snappable {
-    // The storage
-    private final SparseSetArray mStorage;
-
-    // A private convenience function
-    private void onChanged() {
-        dispatchChange(this);
-    }
-
-    public WatchedSparseSetArray() {
-        mStorage = new SparseSetArray();
-    }
-
-    /**
-     * Creates a new WatchedSparseSetArray from an existing WatchedSparseSetArray and copy its data
-     */
-    public WatchedSparseSetArray(@NonNull WatchedSparseSetArray<T> watchedSparseSetArray) {
-        mStorage = new SparseSetArray(watchedSparseSetArray.untrackedStorage());
-    }
-
-    /**
-     * Return the underlying storage.  This breaks the wrapper but is necessary when
-     * passing the array to distant methods.
-     */
-    public SparseSetArray<T> untrackedStorage() {
-        return mStorage;
-    }
-
-    /**
-     * Add a value for key n.
-     * @return FALSE when the value already existed for the given key, TRUE otherwise.
-     */
-    public boolean add(int n, T value) {
-        final boolean res = mStorage.add(n, value);
-        onChanged();
-        return res;
-    }
-
-    /**
-     * Removes all mappings from this SparseSetArray.
-     */
-    public void clear() {
-        mStorage.clear();
-        onChanged();
-    }
-
-    /**
-     * @return whether the value exists for the key n.
-     */
-    public boolean contains(int n, T value) {
-        return mStorage.contains(n, value);
-    }
-
-    /**
-     * @return the set of items of key n
-     */
-    public ArraySet<T> get(int n) {
-        return mStorage.get(n);
-    }
-
-    /**
-     * Remove a value for key n.
-     * @return TRUE when the value existed for the given key and removed, FALSE otherwise.
-     */
-    public boolean remove(int n, T value) {
-        if (mStorage.remove(n, value)) {
-            onChanged();
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Remove all values for key n.
-     */
-    public void remove(int n) {
-        mStorage.remove(n);
-        onChanged();
-    }
-
-    /**
-     * Return the size of the SparseSetArray.
-     */
-    public int size() {
-        return mStorage.size();
-    }
-
-    /**
-     * Return the key stored at the given index.
-     */
-    public int keyAt(int index) {
-        return mStorage.keyAt(index);
-    }
-
-    /**
-     * Return the size of the array at the given index.
-     */
-    public int sizeAt(int index) {
-        return mStorage.sizeAt(index);
-    }
-
-    /**
-     * Return the value in the SetArray at the given key index and value index.
-     */
-    public T valueAt(int intIndex, int valueIndex) {
-        return (T) mStorage.valueAt(intIndex, valueIndex);
-    }
-
-    @NonNull
-    @Override
-    public Object snapshot() {
-        WatchedSparseSetArray l = new WatchedSparseSetArray(this);
-        l.seal();
-        return l;
-    }
-
-    /**
-     * Make <this> a snapshot of the argument.  Note that <this> is immutable when the
-     * method returns.  <this> must be empty when the function is called.
-     * @param r The source array, which is copied into <this>
-     */
-    public void snapshot(@NonNull WatchedSparseSetArray<T> r) {
-        snapshot(this, r);
-    }
-
-    /**
-     * Make the destination a copy of the source.  If the element is a subclass of Snapper then the
-     * copy contains snapshots of the elements.  Otherwise the copy contains references to the
-     * elements.  The destination must be initially empty.  Upon return, the destination is
-     * immutable.
-     * @param dst The destination array.  It must be empty.
-     * @param src The source array.  It is not modified.
-     */
-    public static void snapshot(@NonNull WatchedSparseSetArray dst,
-            @NonNull WatchedSparseSetArray src) {
-        if (dst.size() != 0) {
-            throw new IllegalArgumentException("snapshot destination is not empty");
-        }
-        final int arraySize = src.size();
-        for (int i = 0; i < arraySize; i++) {
-            final ArraySet set = src.get(i);
-            final int setSize = set.size();
-            for (int j = 0; j < setSize; j++) {
-                dst.add(src.keyAt(i), set.valueAt(j));
-            }
-        }
-        dst.seal();
-    }
-}
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index 597f7f2..be38005 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -544,6 +544,7 @@
     private final boolean mIsMobileDataEnabled;
 
     @NonNull private final IpSecManager mIpSecManager;
+    @NonNull private final ConnectivityManager mConnectivityManager;
 
     @Nullable private IpSecTunnelInterface mTunnelIface = null;
 
@@ -701,6 +702,7 @@
                         mLastSnapshot,
                         mUnderlyingNetworkControllerCallback);
         mIpSecManager = mVcnContext.getContext().getSystemService(IpSecManager.class);
+        mConnectivityManager = mVcnContext.getContext().getSystemService(ConnectivityManager.class);
 
         addState(mDisconnectedState);
         addState(mDisconnectingState);
@@ -1683,6 +1685,14 @@
                                         clearFailedAttemptCounterAndSafeModeAlarm();
                                         break;
                                     case NetworkAgent.VALIDATION_STATUS_NOT_VALID:
+                                        // Trigger re-validation of underlying networks; if it
+                                        // fails, the VCN will attempt to migrate away.
+                                        if (mUnderlying != null) {
+                                            mConnectivityManager.reportNetworkConnectivity(
+                                                    mUnderlying.network,
+                                                    false /* hasConnectivity */);
+                                        }
+
                                         // Will only set a new alarm if no safe mode alarm is
                                         // currently scheduled.
                                         setSafeModeAlarm();
@@ -1869,6 +1879,10 @@
                     IpSecManager.DIRECTION_OUT);
 
             updateNetworkAgent(mTunnelIface, mNetworkAgent, mChildConfig);
+
+            // Trigger re-validation after migration events.
+            mConnectivityManager.reportNetworkConnectivity(
+                    mNetworkAgent.getNetwork(), false /* hasConnectivity */);
         }
 
         private void handleUnderlyingNetworkChanged(@NonNull Message msg) {
diff --git a/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java b/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
index 3550bda..12e68b1 100644
--- a/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
@@ -141,8 +141,16 @@
      */
     protected List<Step> nextSteps(long nextStartTime, long vibratorOffTimeout,
             int segmentsPlayed) {
+        int nextSegmentIndex = segmentIndex + segmentsPlayed;
+        int effectSize = effect.getSegments().size();
+        int repeatIndex = effect.getRepeatIndex();
+        if (nextSegmentIndex >= effectSize && repeatIndex >= 0) {
+            // Count the loops that were played.
+            int loopSize = effectSize - repeatIndex;
+            nextSegmentIndex = repeatIndex + ((nextSegmentIndex - effectSize) % loopSize);
+        }
         Step nextStep = conductor.nextVibrateStep(nextStartTime, controller, effect,
-                segmentIndex + segmentsPlayed, vibratorOffTimeout);
+                nextSegmentIndex, vibratorOffTimeout);
         return nextStep == null ? VibrationStepConductor.EMPTY_STEP_LIST : Arrays.asList(nextStep);
     }
 }
diff --git a/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java b/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java
index d1ea805..3bc11c8 100644
--- a/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java
@@ -32,6 +32,11 @@
  * {@link PrimitiveSegment} starting at the current index.
  */
 final class ComposePrimitivesVibratorStep extends AbstractVibratorStep {
+    /**
+     * Default limit to the number of primitives in a composition, if none is defined by the HAL,
+     * to prevent repeating effects from generating an infinite list.
+     */
+    private static final int DEFAULT_COMPOSITION_SIZE_LIMIT = 100;
 
     ComposePrimitivesVibratorStep(VibrationStepConductor conductor, long startTime,
             VibratorController controller, VibrationEffect.Composed effect, int index,
@@ -49,18 +54,8 @@
             // Load the next PrimitiveSegments to create a single compose call to the vibrator,
             // limited to the vibrator composition maximum size.
             int limit = controller.getVibratorInfo().getCompositionSizeMax();
-            int segmentCount = limit > 0
-                    ? Math.min(effect.getSegments().size(), segmentIndex + limit)
-                    : effect.getSegments().size();
-            List<PrimitiveSegment> primitives = new ArrayList<>();
-            for (int i = segmentIndex; i < segmentCount; i++) {
-                VibrationEffectSegment segment = effect.getSegments().get(i);
-                if (segment instanceof PrimitiveSegment) {
-                    primitives.add((PrimitiveSegment) segment);
-                } else {
-                    break;
-                }
-            }
+            List<PrimitiveSegment> primitives = unrollPrimitiveSegments(effect, segmentIndex,
+                    limit > 0 ? limit : DEFAULT_COMPOSITION_SIZE_LIMIT);
 
             if (primitives.isEmpty()) {
                 Slog.w(VibrationThread.TAG, "Ignoring wrong segment for a ComposePrimitivesStep: "
@@ -81,4 +76,44 @@
             Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
         }
     }
+
+    /**
+     * Get the primitive segments to be played by this step as a single composition, starting at
+     * {@code startIndex} until:
+     *
+     * <ol>
+     *     <li>There are no more segments in the effect;
+     *     <li>The first non-primitive segment is found;
+     *     <li>The given limit to the composition size is reached.
+     * </ol>
+     *
+     * <p>If the effect is repeating then this method will generate the largest composition within
+     * given limit.
+     */
+    private List<PrimitiveSegment> unrollPrimitiveSegments(VibrationEffect.Composed effect,
+            int startIndex, int limit) {
+        List<PrimitiveSegment> segments = new ArrayList<>(limit);
+        int segmentCount = effect.getSegments().size();
+        int repeatIndex = effect.getRepeatIndex();
+
+        for (int i = startIndex; segments.size() < limit; i++) {
+            if (i == segmentCount) {
+                if (repeatIndex >= 0) {
+                    i = repeatIndex;
+                } else {
+                    // Non-repeating effect, stop collecting primitives.
+                    break;
+                }
+            }
+            VibrationEffectSegment segment = effect.getSegments().get(i);
+            if (segment instanceof PrimitiveSegment) {
+                segments.add((PrimitiveSegment) segment);
+            } else {
+                // First non-primitive segment, stop collecting primitives.
+                break;
+            }
+        }
+
+        return segments;
+    }
 }
diff --git a/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java b/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java
index 73bf933..919f1be 100644
--- a/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java
@@ -33,6 +33,11 @@
  * {@link StepSegment} or {@link RampSegment} starting at the current index.
  */
 final class ComposePwleVibratorStep extends AbstractVibratorStep {
+    /**
+     * Default limit to the number of PWLE segments, if none is defined by the HAL, to prevent
+     * repeating effects from generating an infinite list.
+     */
+    private static final int DEFAULT_PWLE_SIZE_LIMIT = 100;
 
     ComposePwleVibratorStep(VibrationStepConductor conductor, long startTime,
             VibratorController controller, VibrationEffect.Composed effect, int index,
@@ -50,18 +55,8 @@
             // Load the next RampSegments to create a single composePwle call to the vibrator,
             // limited to the vibrator PWLE maximum size.
             int limit = controller.getVibratorInfo().getPwleSizeMax();
-            int segmentCount = limit > 0
-                    ? Math.min(effect.getSegments().size(), segmentIndex + limit)
-                    : effect.getSegments().size();
-            List<RampSegment> pwles = new ArrayList<>();
-            for (int i = segmentIndex; i < segmentCount; i++) {
-                VibrationEffectSegment segment = effect.getSegments().get(i);
-                if (segment instanceof RampSegment) {
-                    pwles.add((RampSegment) segment);
-                } else {
-                    break;
-                }
-            }
+            List<RampSegment> pwles = unrollRampSegments(effect, segmentIndex,
+                    limit > 0 ? limit : DEFAULT_PWLE_SIZE_LIMIT);
 
             if (pwles.isEmpty()) {
                 Slog.w(VibrationThread.TAG, "Ignoring wrong segment for a ComposePwleStep: "
@@ -81,4 +76,88 @@
             Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
         }
     }
+
+    /**
+     * Get the ramp segments to be played by this step for a waveform, starting at
+     * {@code startIndex} until:
+     *
+     * <ol>
+     *     <li>There are no more segments in the effect;
+     *     <li>The first non-ramp segment is found;
+     *     <li>The given limit to the PWLE size is reached.
+     * </ol>
+     *
+     * <p>If the effect is repeating then this method will generate the largest PWLE within given
+     * limit. This will also optimize to end the list at a ramp to zero-amplitude, if possible, and
+     * avoid braking down the effect in non-zero amplitude.
+     */
+    private List<RampSegment> unrollRampSegments(VibrationEffect.Composed effect, int startIndex,
+            int limit) {
+        List<RampSegment> segments = new ArrayList<>(limit);
+        float bestBreakAmplitude = 1;
+        int bestBreakPosition = limit; // Exclusive index.
+
+        int segmentCount = effect.getSegments().size();
+        int repeatIndex = effect.getRepeatIndex();
+
+        // Loop once after reaching the limit to see if breaking it will really be necessary, then
+        // apply the best break position found, otherwise return the full list as it fits the limit.
+        for (int i = startIndex; segments.size() <= limit; i++) {
+            if (i == segmentCount) {
+                if (repeatIndex >= 0) {
+                    i = repeatIndex;
+                } else {
+                    // Non-repeating effect, stop collecting ramps.
+                    break;
+                }
+            }
+            VibrationEffectSegment segment = effect.getSegments().get(i);
+            if (segment instanceof RampSegment) {
+                RampSegment rampSegment = (RampSegment) segment;
+                segments.add(rampSegment);
+
+                if (isBetterBreakPosition(segments, bestBreakAmplitude, limit)) {
+                    // Mark this position as the best one so far to break a long waveform.
+                    bestBreakAmplitude = rampSegment.getEndAmplitude();
+                    bestBreakPosition = segments.size(); // Break after this ramp ends.
+                }
+            } else {
+                // First non-ramp segment, stop collecting ramps.
+                break;
+            }
+        }
+
+        return segments.size() > limit
+                // Remove excessive segments, using the best breaking position recorded.
+                ? segments.subList(0, bestBreakPosition)
+                // Return all collected ramp segments.
+                : segments;
+    }
+
+    /**
+     * Returns true if the current segment list represents a better break position for a PWLE,
+     * given the current amplitude being used for breaking it at a smaller size and the size limit.
+     */
+    private boolean isBetterBreakPosition(List<RampSegment> segments,
+            float currentBestBreakAmplitude, int limit) {
+        RampSegment lastSegment = segments.get(segments.size() - 1);
+        float breakAmplitudeCandidate = lastSegment.getEndAmplitude();
+        int breakPositionCandidate = segments.size();
+
+        if (breakPositionCandidate > limit) {
+            // We're beyond limit, last break position found should be used.
+            return false;
+        }
+        if (breakAmplitudeCandidate == 0) {
+            // Breaking at amplitude zero at any position is always preferable.
+            return true;
+        }
+        if (breakPositionCandidate < limit / 2) {
+            // Avoid breaking at the first half of the allowed maximum size, even if amplitudes are
+            // lower, to avoid creating PWLEs that are too small unless it's to break at zero.
+            return false;
+        }
+        // Prefer lower amplitudes at a later position for breaking the PWLE in a more subtle way.
+        return breakAmplitudeCandidate <= currentBestBreakAmplitude;
+    }
 }
diff --git a/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java b/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
index d5c1116..1f0d2d7 100644
--- a/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
@@ -33,6 +33,12 @@
  * and amplitude to simulate waveforms represented by a sequence of {@link StepSegment}.
  */
 final class SetAmplitudeVibratorStep extends AbstractVibratorStep {
+    /**
+     * The repeating waveform keeps the vibrator ON all the time. Use a minimum duration to
+     * prevent short patterns from turning the vibrator ON too frequently.
+     */
+    private static final int REPEATING_EFFECT_ON_DURATION = 5000; // 5s
+
     private long mNextOffTime;
 
     SetAmplitudeVibratorStep(VibrationStepConductor conductor, long startTime,
@@ -170,10 +176,7 @@
                 repeatIndex = -1;
             }
             if (i == startIndex) {
-                // The repeating waveform keeps the vibrator ON all the time. Use a minimum
-                // of 1s duration to prevent short patterns from turning the vibrator ON too
-                // frequently.
-                return Math.max(timing, 1000);
+                return Math.max(timing, REPEATING_EFFECT_ON_DURATION);
             }
         }
         if (i == segmentCount && effect.getRepeatIndex() < 0) {
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index 8ecc51b..3e36431 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -66,6 +66,7 @@
         IGNORED_FOR_ONGOING,
         IGNORED_FOR_POWER,
         IGNORED_FOR_RINGER_MODE,
+        IGNORED_FOR_RINGTONE,
         IGNORED_FOR_SETTINGS,
         IGNORED_SUPERSEDED,
     }
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 3ffca96..d7341cb 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -733,6 +733,12 @@
                             + attrs);
                 }
                 break;
+            case IGNORED_FOR_RINGTONE:
+                if (DEBUG) {
+                    Slog.d(TAG, "Ignoring incoming vibration in favor of ringtone vibration");
+                }
+                break;
+
             default:
                 if (DEBUG) {
                     Slog.d(TAG, "Vibration for uid=" + uid + " and with attrs=" + attrs
@@ -809,6 +815,10 @@
             return Vibration.Status.IGNORED_FOR_ALARM;
         }
 
+        if (currentVibration.attrs.getUsage() == VibrationAttributes.USAGE_RINGTONE) {
+            return Vibration.Status.IGNORED_FOR_RINGTONE;
+        }
+
         if (currentVibration.isRepeating()) {
             return Vibration.Status.IGNORED_FOR_ONGOING;
         }
@@ -989,12 +999,13 @@
      */
     @NonNull
     private VibrationAttributes fixupVibrationAttributes(@Nullable VibrationAttributes attrs,
-            CombinedVibration effect) {
+            @Nullable CombinedVibration effect) {
         if (attrs == null) {
             attrs = DEFAULT_ATTRIBUTES;
         }
         int usage = attrs.getUsage();
-        if ((usage == VibrationAttributes.USAGE_UNKNOWN) && effect.isHapticFeedbackCandidate()) {
+        if ((usage == VibrationAttributes.USAGE_UNKNOWN)
+                && (effect != null) && effect.isHapticFeedbackCandidate()) {
             usage = VibrationAttributes.USAGE_TOUCH;
         }
         int flags = attrs.getFlags();
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index d8ad2a5..22bfcc4 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -8087,7 +8087,11 @@
         final boolean orientationRequested = requestedOrientation != ORIENTATION_UNDEFINED;
         final int orientation = orientationRequested
                 ? requestedOrientation
-                : newParentConfiguration.orientation;
+                // We should use the original orientation of the activity when possible to avoid
+                // forcing the activity in the opposite orientation.
+                : mCompatDisplayInsets.mOriginalRequestedOrientation != ORIENTATION_UNDEFINED
+                        ? mCompatDisplayInsets.mOriginalRequestedOrientation
+                        : newParentConfiguration.orientation;
         int rotation = newParentConfiguration.windowConfiguration.getRotation();
         final boolean isFixedToUserRotation = mDisplayContent == null
                 || mDisplayContent.getDisplayRotation().isFixedToUserRotation();
@@ -9356,8 +9360,10 @@
      * compatibility mode activity compute the configuration without relying on its current display.
      */
     static class CompatDisplayInsets {
-        /** The original rotation the compat insets were computed in */
+        /** The original rotation the compat insets were computed in. */
         final @Rotation int mOriginalRotation;
+        /** The original requested orientation for the activity. */
+        final @Configuration.Orientation int mOriginalRequestedOrientation;
         /** The container width on rotation 0. */
         private final int mWidth;
         /** The container height on rotation 0. */
@@ -9386,6 +9392,7 @@
                 @Nullable Rect fixedOrientationBounds) {
             mOriginalRotation = display.getRotation();
             mIsFloating = container.getWindowConfiguration().tasksAreFloating();
+            mOriginalRequestedOrientation = container.getRequestedConfigurationOrientation();
             if (mIsFloating) {
                 final Rect containerBounds = container.getWindowConfiguration().getBounds();
                 mWidth = containerBounds.width();
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 003268b..602e416 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1935,6 +1935,14 @@
         } else if (mSourceRecord != null) {
             return mSourceRecord.getTask();
         } else if (mInTask != null) {
+            // The task is specified from AppTaskImpl, so it may not be attached yet.
+            if (!mInTask.isAttached()) {
+                // Clear reuse task so it can find a proper parent to add the task.
+                if (mReuseTask == mInTask) {
+                    mReuseTask = null;
+                }
+                return getOrCreateRootTask(mStartActivity, mLaunchFlags, mInTask, mOptions);
+            }
             return mInTask;
         } else {
             final Task rootTask = getOrCreateRootTask(mStartActivity, mLaunchFlags, null /* task */,
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index eb5ca9c..95de040 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -897,6 +897,7 @@
                         proc.getThread(), r.token);
 
                 final boolean isTransitionForward = r.isTransitionForward();
+                final IBinder fragmentToken = r.getTaskFragment().getFragmentToken();
                 clientTransaction.addCallback(LaunchActivityItem.obtain(new Intent(r.intent),
                         System.identityHashCode(r), r.info,
                         // TODO: Have this take the merged configuration instead of separate global
@@ -907,7 +908,7 @@
                         proc.getReportedProcState(), r.getSavedState(), r.getPersistentSavedState(),
                         results, newIntents, r.takeOptions(), isTransitionForward,
                         proc.createProfilerInfoIfNeeded(), r.assistToken, activityClientController,
-                        r.shareableActivityToken, r.getLaunchedFromBubble()));
+                        r.shareableActivityToken, r.getLaunchedFromBubble(), fragmentToken));
 
                 // Set desired final state.
                 final ActivityLifecycleItem lifecycleItem;
diff --git a/services/core/java/com/android/server/wm/AnimationAdapter.java b/services/core/java/com/android/server/wm/AnimationAdapter.java
index a743091..b039646 100644
--- a/services/core/java/com/android/server/wm/AnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/AnimationAdapter.java
@@ -43,7 +43,7 @@
 
     /**
      * @return Whether we should show a background behind the animating windows.
-     * @see Animation#getShowBackground()
+     * @see Animation#getShowBackdrop()
      */
     default boolean getShowBackground() {
         return false;
@@ -51,7 +51,7 @@
 
     /**
      * @return The background color to use during an animation if getShowBackground returns true.
-     * @see Animation#getBackgroundColor()
+     * @see Animation#getBackdropColor()
      */
     default int getBackgroundColor() {
         return 0;
diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java
index 22a2c41..6e46fa6 100644
--- a/services/core/java/com/android/server/wm/AppTaskImpl.java
+++ b/services/core/java/com/android/server/wm/AppTaskImpl.java
@@ -26,6 +26,8 @@
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.Parcel;
+import android.os.RemoteException;
 import android.os.UserHandle;
 
 /**
@@ -54,6 +56,16 @@
     }
 
     @Override
+    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+            throws RemoteException {
+        try {
+            return super.onTransact(code, data, reply, flags);
+        } catch (RuntimeException e) {
+            throw ActivityTaskManagerService.logAndRethrowRuntimeExceptionOnTransact(TAG, e);
+        }
+    }
+
+    @Override
     public void finishAndRemoveTask() {
         checkCaller();
 
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 5c09f09..68a09a6 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -843,7 +843,7 @@
         setAppTransitionFinishedCallbackIfNeeded(a);
 
         if (mNextAppTransitionBackgroundColor != 0) {
-            a.setBackgroundColor(mNextAppTransitionBackgroundColor);
+            a.setBackdropColor(mNextAppTransitionBackgroundColor);
         }
 
         return a;
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 0b4d887..cefc871 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -653,7 +653,7 @@
         final ITaskFragmentOrganizer organizer = findTaskFragmentOrganizer(task);
         final RemoteAnimationDefinition definition = organizer != null
                 ? mDisplayContent.mAtmService.mTaskFragmentOrganizerController
-                    .getRemoteAnimationDefinition(organizer)
+                    .getRemoteAnimationDefinition(organizer, task.mTaskId)
                 : null;
         final RemoteAnimationAdapter adapter = definition != null
                 ? definition.getAdapter(transit, activityTypes)
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index ef0b737..66c625e 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -47,12 +47,6 @@
 class BackNavigationController {
 
     private static final String TAG = "BackNavigationController";
-    // By default, enable new back dispatching without any animations.
-    private static final int BACK_PREDICTABILITY_PROP =
-            SystemProperties.getInt("persist.debug.back_predictability", 1);
-    private static final int ANIMATIONS_MASK = 1 << 1;
-    private static final int SCREENSHOT_MASK = 1 << 2;
-
     @Nullable
     private TaskSnapshotController mTaskSnapshotController;
 
@@ -60,15 +54,15 @@
      * Returns true if the back predictability feature is enabled
      */
     static boolean isEnabled() {
-        return BACK_PREDICTABILITY_PROP > 0;
+        return SystemProperties.getInt("persist.wm.debug.predictive_back", 1) != 0;
     }
 
     static boolean isScreenshotEnabled() {
-        return (BACK_PREDICTABILITY_PROP & SCREENSHOT_MASK) != 0;
+        return SystemProperties.getInt("persist.wm.debug.predictive_back_screenshot", 0) != 0;
     }
 
     private static boolean isAnimationEnabled() {
-        return (BACK_PREDICTABILITY_PROP & ANIMATIONS_MASK) != 0;
+        return SystemProperties.getInt("persist.wm.debug.predictive_back_anim", 0) != 0;
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e592774..81560d4 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -6369,10 +6369,11 @@
          * Notifies the remote insets controller that the top focused window has changed.
          *
          * @param packageName The name of the package that is open in the top focused window.
+         * @param requestedVisibilities The insets visibilities requested by the focussed window.
          */
-        void topFocusedWindowChanged(String packageName) {
+        void topFocusedWindowChanged(String packageName, InsetsVisibilities requestedVisibilities) {
             try {
-                mRemoteInsetsController.topFocusedWindowChanged(packageName);
+                mRemoteInsetsController.topFocusedWindowChanged(packageName, requestedVisibilities);
             } catch (RemoteException e) {
                 Slog.w(TAG, "Failed to deliver package in top focused window change", e);
             }
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 452a23f..566ed60 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1810,14 +1810,10 @@
     /**
      * Called when the resource overlays change.
      */
-    void onOverlayChanged() {
+    public void onOverlayChangedLw() {
         updateCurrentUserResources();
-        // Update the latest display size, cutout.
-        mDisplayContent.updateDisplayInfo();
-        // The height of status bar needs to update in case display cutout is changed.
         onConfigurationChanged();
-        // The height of status bar can affect screen size configuration.
-        mDisplayContent.reconfigureDisplayLocked();
+        mSystemGestures.onConfigurationChanged();
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index 8d1425d..67dd89e 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -29,6 +29,7 @@
 import android.os.Debug;
 import android.os.IBinder;
 import android.util.Slog;
+import android.view.Display;
 import android.view.InputApplicationHandle;
 import android.view.KeyEvent;
 import android.view.SurfaceControl;
@@ -194,6 +195,9 @@
             int firstExternalDisplayId = DEFAULT_DISPLAY;
             for (int i = mService.mRoot.mChildren.size() - 1; i >= 0; --i) {
                 final DisplayContent displayContent = mService.mRoot.mChildren.get(i);
+                if (displayContent.getDisplayInfo().state == Display.STATE_OFF) {
+                    continue;
+                }
                 // Heuristic solution here. Currently when "Freeform windows" developer option is
                 // enabled we automatically put secondary displays in freeform mode and emulating
                 // "desktop mode". It also makes sense to show the pointer on the same display.
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 00f7e636..65062e5 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -249,6 +249,8 @@
         inputWindowHandle.setPaused(w.mActivityRecord != null && w.mActivityRecord.paused);
         inputWindowHandle.setWindowToken(w.mClient);
 
+        inputWindowHandle.setName(w.getName());
+
         // Update layout params flags to force the window to be not touch modal. We do this to
         // restrict the window's touchable region to the task even if it requests touches outside
         // its window bounds. An example is a dialog in primary split should get touches outside its
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 9844cb5..a7b3728 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -533,7 +533,7 @@
         }
         if (remoteInsetsControllerControlsSystemBars(focusedWin)) {
             mDisplayContent.mRemoteInsetsControlTarget.topFocusedWindowChanged(
-                    focusedWin.mAttrs.packageName);
+                    focusedWin.mAttrs.packageName, focusedWin.getRequestedVisibilities());
             return mDisplayContent.mRemoteInsetsControlTarget;
         }
         if (mPolicy.areSystemBarsForcedShownLw()) {
@@ -590,7 +590,7 @@
         }
         if (remoteInsetsControllerControlsSystemBars(focusedWin)) {
             mDisplayContent.mRemoteInsetsControlTarget.topFocusedWindowChanged(
-                    focusedWin.mAttrs.packageName);
+                    focusedWin.mAttrs.packageName, focusedWin.getRequestedVisibilities());
             return mDisplayContent.mRemoteInsetsControlTarget;
         }
         if (mPolicy.areSystemBarsForcedShownLw()) {
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 0038c71..c162e8e 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -175,7 +175,7 @@
             final Rect spaceToFill = transformedBounds != null
                     ? transformedBounds
                     : mActivityRecord.inMultiWindowMode()
-                            ? mActivityRecord.getRootTask().getBounds()
+                            ? mActivityRecord.getTask().getBounds()
                             : mActivityRecord.getRootTask().getParent().getBounds();
             mLetterbox.layout(spaceToFill, w.getFrame(), mTmpPoint);
         } else if (mLetterbox != null) {
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index 6d96cf0..eca201d 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -304,10 +304,6 @@
                 mService.stopAppSwitches();
             }
 
-            if (mCaller != null) {
-                mCaller.setRunningRecentsAnimation(false);
-            }
-
             mWindowManager.inSurfaceTransaction(() -> {
                 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
                         "RecentsAnimation#onAnimationFinished_inSurfaceTransaction");
@@ -413,6 +409,9 @@
                     if (mWindowManager.mRoot.isLayoutNeeded()) {
                         mWindowManager.mRoot.performSurfacePlacement();
                     }
+                    if (mCaller != null) {
+                        mCaller.setRunningRecentsAnimation(false);
+                    }
                     Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
                 }
             });
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
index 65dca86..83be73a 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
@@ -176,13 +176,24 @@
                 t.addTransactionCommittedListener(Runnable::run, () -> {
                     final WindowAnimationSpec animationSpec = a.asWindowAnimationSpec();
 
+                    final Transaction edgeExtensionCreationTransaction = new Transaction();
                     edgeExtendWindow(animationLeash,
                             animationSpec.getRootTaskBounds(), animationSpec.getAnimation(),
-                            mFrameTransaction);
+                            edgeExtensionCreationTransaction);
 
                     synchronized (mLock) {
                         // only run if animation is not yet canceled by this point
                         if (mPreProcessingAnimations.get(animationLeash) == runningAnim) {
+                            // In the case the animation is cancelled, edge extensions are removed
+                            // onAnimationLeashLost which is called before onAnimationCancelled.
+                            // So we need to check if the edge extensions have already been removed
+                            // or not, and if so we don't want to apply the transaction.
+                            synchronized (mEdgeExtensionLock) {
+                                if (!mEdgeExtensions.isEmpty()) {
+                                    edgeExtensionCreationTransaction.apply();
+                                }
+                            }
+
                             mPreProcessingAnimations.remove(animationLeash);
                             mPendingAnimations.put(animationLeash, runningAnim);
                             if (!mAnimationStartDeferred) {
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 3a458fd..900963e 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2262,7 +2262,7 @@
                 mFragmentToken,
                 mRemoteToken.toWindowContainerToken(),
                 getConfiguration(),
-                getChildCount() == 0,
+                runningActivityCount[0] == 0,
                 runningActivityCount[0],
                 isVisible(),
                 childActivities,
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 0a92ffc..bd351ac 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -32,6 +32,7 @@
 import android.os.RemoteException;
 import android.util.ArrayMap;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.view.RemoteAnimationDefinition;
 import android.window.ITaskFragmentOrganizer;
 import android.window.ITaskFragmentOrganizerController;
@@ -83,11 +84,12 @@
                 new WeakHashMap<>();
 
         /**
-         * @see android.window.TaskFragmentOrganizer#registerRemoteAnimations(
-         * RemoteAnimationDefinition)
+         * Map from Task Id to {@link RemoteAnimationDefinition}.
+         * @see android.window.TaskFragmentOrganizer#registerRemoteAnimations(int,
+         * RemoteAnimationDefinition) )
          */
-        @Nullable
-        private RemoteAnimationDefinition mRemoteAnimationDefinition;
+        private final SparseArray<RemoteAnimationDefinition> mRemoteAnimationDefinitions =
+                new SparseArray<>();
 
         TaskFragmentOrganizerState(ITaskFragmentOrganizer organizer) {
             mOrganizer = organizer;
@@ -251,7 +253,7 @@
     }
 
     @Override
-    public void registerRemoteAnimations(ITaskFragmentOrganizer organizer,
+    public void registerRemoteAnimations(ITaskFragmentOrganizer organizer, int taskId,
             RemoteAnimationDefinition definition) {
         final int pid = Binder.getCallingPid();
         final int uid = Binder.getCallingUid();
@@ -264,19 +266,20 @@
             if (organizerState == null) {
                 throw new IllegalStateException("The organizer hasn't been registered.");
             }
-            if (organizerState.mRemoteAnimationDefinition != null) {
+            if (organizerState.mRemoteAnimationDefinitions.contains(taskId)) {
                 throw new IllegalStateException(
                         "The organizer has already registered remote animations="
-                                + organizerState.mRemoteAnimationDefinition);
+                                + organizerState.mRemoteAnimationDefinitions.get(taskId)
+                                + " for TaskId=" + taskId);
             }
 
             definition.setCallingPidUid(pid, uid);
-            organizerState.mRemoteAnimationDefinition = definition;
+            organizerState.mRemoteAnimationDefinitions.put(taskId, definition);
         }
     }
 
     @Override
-    public void unregisterRemoteAnimations(ITaskFragmentOrganizer organizer) {
+    public void unregisterRemoteAnimations(ITaskFragmentOrganizer organizer, int taskId) {
         final int pid = Binder.getCallingPid();
         final long uid = Binder.getCallingUid();
         synchronized (mGlobalLock) {
@@ -290,7 +293,7 @@
                 return;
             }
 
-            organizerState.mRemoteAnimationDefinition = null;
+            organizerState.mRemoteAnimationDefinitions.remove(taskId);
         }
     }
 
@@ -300,11 +303,13 @@
      */
     @Nullable
     public RemoteAnimationDefinition getRemoteAnimationDefinition(
-            ITaskFragmentOrganizer organizer) {
+            ITaskFragmentOrganizer organizer, int taskId) {
         synchronized (mGlobalLock) {
             final TaskFragmentOrganizerState organizerState =
                     mTaskFragmentOrganizerState.get(organizer.asBinder());
-            return organizerState != null ? organizerState.mRemoteAnimationDefinition : null;
+            return organizerState != null
+                    ? organizerState.mRemoteAnimationDefinitions.get(taskId)
+                    : null;
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowAnimationSpec.java b/services/core/java/com/android/server/wm/WindowAnimationSpec.java
index b6d668d..34b9913 100644
--- a/services/core/java/com/android/server/wm/WindowAnimationSpec.java
+++ b/services/core/java/com/android/server/wm/WindowAnimationSpec.java
@@ -87,12 +87,12 @@
 
     @Override
     public boolean getShowBackground() {
-        return mAnimation.getShowBackground();
+        return mAnimation.getShowBackdrop();
     }
 
     @Override
     public int getBackgroundColor() {
-        return mAnimation.getBackgroundColor();
+        return mAnimation.getBackdropColor();
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index d78fdaa..77d31df 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -181,7 +181,6 @@
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.input.InputManager;
-import android.hardware.input.InputManagerInternal;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
@@ -256,7 +255,6 @@
 import android.view.InputApplicationHandle;
 import android.view.InputChannel;
 import android.view.InputDevice;
-import android.view.InputEvent;
 import android.view.InputWindowHandle;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
@@ -6976,17 +6974,13 @@
     }
 
     public void onOverlayChanged() {
-        // Post to display thread so it can get the latest display info.
-        mH.post(() -> {
-            synchronized (mGlobalLock) {
-                mAtmService.deferWindowLayout();
-                try {
-                    mRoot.forAllDisplays(dc -> dc.getDisplayPolicy().onOverlayChanged());
-                } finally {
-                    mAtmService.continueWindowLayout();
-                }
-            }
-        });
+        synchronized (mGlobalLock) {
+            mRoot.forAllDisplays(displayContent -> {
+                displayContent.getDisplayPolicy().onOverlayChangedLw();
+                displayContent.updateDisplayInfo();
+            });
+            requestTraversal();
+        }
     }
 
     @Override
@@ -8324,37 +8318,6 @@
     }
 
     @Override
-    public boolean injectInputAfterTransactionsApplied(InputEvent ev, int mode,
-            boolean waitForAnimations) {
-        boolean isDown;
-        boolean isUp;
-
-        if (ev instanceof KeyEvent) {
-            KeyEvent keyEvent = (KeyEvent) ev;
-            isDown = keyEvent.getAction() == KeyEvent.ACTION_DOWN;
-            isUp = keyEvent.getAction() == KeyEvent.ACTION_UP;
-        } else {
-            MotionEvent motionEvent = (MotionEvent) ev;
-            isDown = motionEvent.getAction() == MotionEvent.ACTION_DOWN;
-            isUp = motionEvent.getAction() == MotionEvent.ACTION_UP;
-        }
-        final boolean isMouseEvent = ev.getSource() == InputDevice.SOURCE_MOUSE;
-
-        // For ACTION_DOWN, syncInputTransactions before injecting input.
-        // For all mouse events, also sync before injecting.
-        // For ACTION_UP, sync after injecting.
-        if (isDown || isMouseEvent) {
-            syncInputTransactions(waitForAnimations);
-        }
-        final boolean result =
-                LocalServices.getService(InputManagerInternal.class).injectInputEvent(ev, mode);
-        if (isUp) {
-            syncInputTransactions(waitForAnimations);
-        }
-        return result;
-    }
-
-    @Override
     public void syncInputTransactions(boolean waitForAnimations) {
         final long token = Binder.clearCallingIdentity();
         try {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 88d17e4..51d68bc 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -250,6 +250,7 @@
 import android.view.animation.Interpolator;
 import android.window.ClientWindowFrames;
 import android.window.IOnBackInvokedCallback;
+import android.window.WindowOnBackInvokedDispatcher;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.policy.KeyInterceptionInfo;
@@ -1084,9 +1085,10 @@
      */
     void setOnBackInvokedCallback(
             @Nullable IOnBackInvokedCallback onBackInvokedCallback, int priority) {
-        ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "%s: Setting back callback %s. Client IWindow %s",
-                this, onBackInvokedCallback, mClient);
-        if (priority >= 0) {
+        ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "WindowState: Setting back callback %s (priority: %d) "
+                        + "(Client IWindow: %s). (WindowState: %s)",
+                onBackInvokedCallback, priority, mClient, this);
+        if (priority >= WindowOnBackInvokedDispatcher.PRIORITY_DEFAULT) {
             mApplicationOnBackInvokedCallback = onBackInvokedCallback;
             mSystemOnBackInvokedCallback = null;
         } else {
@@ -6123,14 +6125,21 @@
             applyHere = true;
         }
 
-        for (int i = mDrawHandlers.size() - 1; i >= 0; i--) {
-            DrawHandler h = mDrawHandlers.get(i);
+        final List<DrawHandler> handlersToRemove = new ArrayList<>();
+        // Iterate forwards to ensure we process in the same order
+        // we added.
+        for (int i = 0; i < mDrawHandlers.size(); i++) {
+            final DrawHandler h = mDrawHandlers.get(i);
             if (h.mSeqId <= seqId) {
                 h.mConsumer.accept(t);
-                mDrawHandlers.remove(h);
+                handlersToRemove.add(h);
                 hadHandlers = true;
             }
         }
+        for (int i = 0; i < handlersToRemove.size(); i++) {
+            final DrawHandler h = handlersToRemove.get(i);
+            mDrawHandlers.remove(h);
+        }
 
         if (hadHandlers) {
             mWmService.mH.removeMessages(WINDOW_STATE_BLAST_SYNC_TIMEOUT, this);
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 31b5579..fa5e450 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -106,7 +106,6 @@
     jmethodID interceptMotionBeforeQueueingNonInteractive;
     jmethodID interceptKeyBeforeDispatching;
     jmethodID dispatchUnhandledKey;
-    jmethodID checkInjectEventsPermission;
     jmethodID onPointerDownOutsideFocus;
     jmethodID getVirtualKeyQuietTimeMillis;
     jmethodID getExcludedDeviceNames;
@@ -131,6 +130,11 @@
 
 static struct {
     jclass clazz;
+    jfieldID mPtr;
+} gNativeInputManagerServiceImpl;
+
+static struct {
+    jclass clazz;
 } gInputDeviceClassInfo;
 
 static struct {
@@ -260,17 +264,16 @@
 
     void setDisplayViewports(JNIEnv* env, jobjectArray viewportObjArray);
 
-    base::Result<std::unique_ptr<InputChannel>> createInputChannel(JNIEnv* env,
-                                                                   const std::string& name);
-    base::Result<std::unique_ptr<InputChannel>> createInputMonitor(JNIEnv* env, int32_t displayId,
+    base::Result<std::unique_ptr<InputChannel>> createInputChannel(const std::string& name);
+    base::Result<std::unique_ptr<InputChannel>> createInputMonitor(int32_t displayId,
                                                                    const std::string& name,
                                                                    int32_t pid);
-    status_t removeInputChannel(JNIEnv* env, const sp<IBinder>& connectionToken);
+    status_t removeInputChannel(const sp<IBinder>& connectionToken);
     status_t pilferPointers(const sp<IBinder>& token);
 
     void displayRemoved(JNIEnv* env, int32_t displayId);
     void setFocusedApplication(JNIEnv* env, int32_t displayId, jobject applicationHandleObj);
-    void setFocusedDisplay(JNIEnv* env, int32_t displayId);
+    void setFocusedDisplay(int32_t displayId);
     void setInputDispatchMode(bool enabled, bool frozen);
     void setSystemUiLightsOut(bool lightsOut);
     void setPointerSpeed(int32_t speed);
@@ -329,7 +332,6 @@
     bool dispatchUnhandledKey(const sp<IBinder>& token, const KeyEvent* keyEvent,
                               uint32_t policyFlags, KeyEvent* outFallbackKeyEvent) override;
     void pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) override;
-    bool checkInjectEventsPermissionNonReentrant(int32_t injectorPid, int32_t injectorUid) override;
     void onPointerDownOutsideFocus(const sp<IBinder>& touchedToken) override;
     void setPointerCapture(const PointerCaptureRequest& request) override;
     void notifyDropWindow(const sp<IBinder>& token, float x, float y) override;
@@ -512,19 +514,18 @@
 }
 
 base::Result<std::unique_ptr<InputChannel>> NativeInputManager::createInputChannel(
-        JNIEnv* /* env */, const std::string& name) {
+        const std::string& name) {
     ATRACE_CALL();
     return mInputManager->getDispatcher().createInputChannel(name);
 }
 
 base::Result<std::unique_ptr<InputChannel>> NativeInputManager::createInputMonitor(
-        JNIEnv* /* env */, int32_t displayId, const std::string& name, int32_t pid) {
+        int32_t displayId, const std::string& name, int32_t pid) {
     ATRACE_CALL();
     return mInputManager->getDispatcher().createInputMonitor(displayId, name, pid);
 }
 
-status_t NativeInputManager::removeInputChannel(JNIEnv* /* env */,
-                                                const sp<IBinder>& connectionToken) {
+status_t NativeInputManager::removeInputChannel(const sp<IBinder>& connectionToken) {
     ATRACE_CALL();
     return mInputManager->getDispatcher().removeInputChannel(connectionToken);
 }
@@ -1002,7 +1003,7 @@
     mInputManager->getDispatcher().setFocusedApplication(displayId, applicationHandle);
 }
 
-void NativeInputManager::setFocusedDisplay(JNIEnv* env, int32_t displayId) {
+void NativeInputManager::setFocusedDisplay(int32_t displayId) {
     mInputManager->getDispatcher().setFocusedDisplay(displayId);
 }
 
@@ -1368,18 +1369,6 @@
     android_server_PowerManagerService_userActivity(eventTime, eventType, displayId);
 }
 
-bool NativeInputManager::checkInjectEventsPermissionNonReentrant(
-        int32_t injectorPid, int32_t injectorUid) {
-    ATRACE_CALL();
-    JNIEnv* env = jniEnv();
-    jboolean result = env->CallBooleanMethod(mServiceObj,
-            gServiceClassInfo.checkInjectEventsPermission, injectorPid, injectorUid);
-    if (checkAndClearExceptionFromCallback(env, "checkInjectEventsPermission")) {
-        result = false;
-    }
-    return result;
-}
-
 void NativeInputManager::onPointerDownOutsideFocus(const sp<IBinder>& touchedToken) {
     ATRACE_CALL();
     JNIEnv* env = jniEnv();
@@ -1504,6 +1493,11 @@
 
 // ----------------------------------------------------------------------------
 
+static NativeInputManager* getNativeInputManager(JNIEnv* env, jobject clazz) {
+    return reinterpret_cast<NativeInputManager*>(
+            env->GetLongField(clazz, gNativeInputManagerServiceImpl.mPtr));
+}
+
 static jlong nativeInit(JNIEnv* env, jclass /* clazz */,
         jobject serviceObj, jobject contextObj, jobject messageQueueObj) {
     sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
@@ -1518,8 +1512,8 @@
     return reinterpret_cast<jlong>(im);
 }
 
-static void nativeStart(JNIEnv* env, jclass /* clazz */, jlong ptr) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeStart(JNIEnv* env, jobject nativeImplObj) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
     status_t result = im->getInputManager()->start();
     if (result) {
@@ -1527,39 +1521,39 @@
     }
 }
 
-static void nativeSetDisplayViewports(JNIEnv* env, jclass /* clazz */, jlong ptr,
-        jobjectArray viewportObjArray) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetDisplayViewports(JNIEnv* env, jobject nativeImplObj,
+                                      jobjectArray viewportObjArray) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
     im->setDisplayViewports(env, viewportObjArray);
 }
 
-static jint nativeGetScanCodeState(JNIEnv* /* env */, jclass /* clazz */,
-        jlong ptr, jint deviceId, jint sourceMask, jint scanCode) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jint nativeGetScanCodeState(JNIEnv* env, jobject nativeImplObj, jint deviceId,
+                                   jint sourceMask, jint scanCode) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
     return (jint)im->getInputManager()->getReader().getScanCodeState(deviceId, uint32_t(sourceMask),
                                                                      scanCode);
 }
 
-static jint nativeGetKeyCodeState(JNIEnv* /* env */, jclass /* clazz */,
-        jlong ptr, jint deviceId, jint sourceMask, jint keyCode) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jint nativeGetKeyCodeState(JNIEnv* env, jobject nativeImplObj, jint deviceId,
+                                  jint sourceMask, jint keyCode) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
     return (jint)im->getInputManager()->getReader().getKeyCodeState(deviceId, uint32_t(sourceMask),
                                                                     keyCode);
 }
 
-static jint nativeGetSwitchState(JNIEnv* /* env */, jclass /* clazz */,
-        jlong ptr, jint deviceId, jint sourceMask, jint sw) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jint nativeGetSwitchState(JNIEnv* env, jobject nativeImplObj, jint deviceId, jint sourceMask,
+                                 jint sw) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
     return (jint)im->getInputManager()->getReader().getSwitchState(deviceId, uint32_t(sourceMask),
                                                                    sw);
 }
 
-static jboolean nativeHasKeys(JNIEnv* env, jclass /* clazz */,
-        jlong ptr, jint deviceId, jint sourceMask, jintArray keyCodes, jbooleanArray outFlags) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jboolean nativeHasKeys(JNIEnv* env, jobject nativeImplObj, jint deviceId, jint sourceMask,
+                              jintArray keyCodes, jbooleanArray outFlags) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
     int32_t* codes = env->GetIntArrayElements(keyCodes, nullptr);
     uint8_t* flags = env->GetBooleanArrayElements(outFlags, nullptr);
@@ -1581,9 +1575,9 @@
     return result;
 }
 
-static jint nativeGetKeyCodeForKeyLocation(JNIEnv* env, jclass /* clazz */, jlong ptr,
-                                           jint deviceId, jint locationKeyCode) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jint nativeGetKeyCodeForKeyLocation(JNIEnv* env, jobject nativeImplObj, jint deviceId,
+                                           jint locationKeyCode) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
     return (jint)im->getInputManager()->getReader().getKeyCodeForKeyLocation(deviceId,
                                                                              locationKeyCode);
 }
@@ -1596,17 +1590,16 @@
     ALOGW("Input channel object '%s' was disposed without first being removed with "
           "the input manager!",
           inputChannel->getName().c_str());
-    im->removeInputChannel(env, inputChannel->getConnectionToken());
+    im->removeInputChannel(inputChannel->getConnectionToken());
 }
 
-static jobject nativeCreateInputChannel(JNIEnv* env, jclass /* clazz */, jlong ptr,
-                                        jstring nameObj) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jobject nativeCreateInputChannel(JNIEnv* env, jobject nativeImplObj, jstring nameObj) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
     ScopedUtfChars nameChars(env, nameObj);
     std::string name = nameChars.c_str();
 
-    base::Result<std::unique_ptr<InputChannel>> inputChannel = im->createInputChannel(env, name);
+    base::Result<std::unique_ptr<InputChannel>> inputChannel = im->createInputChannel(name);
 
     if (!inputChannel.ok()) {
         std::string message = inputChannel.error().message();
@@ -1626,9 +1619,9 @@
     return inputChannelObj;
 }
 
-static jobject nativeCreateInputMonitor(JNIEnv* env, jclass /* clazz */, jlong ptr, jint displayId,
+static jobject nativeCreateInputMonitor(JNIEnv* env, jobject nativeImplObj, jint displayId,
                                         jstring nameObj, jint pid) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
     if (displayId == ADISPLAY_ID_NONE) {
         std::string message = "InputChannel used as a monitor must be associated with a display";
@@ -1640,7 +1633,7 @@
     std::string name = nameChars.c_str();
 
     base::Result<std::unique_ptr<InputChannel>> inputChannel =
-            im->createInputMonitor(env, displayId, name, pid);
+            im->createInputMonitor(displayId, name, pid);
 
     if (!inputChannel.ok()) {
         std::string message = inputChannel.error().message();
@@ -1657,11 +1650,11 @@
     return inputChannelObj;
 }
 
-static void nativeRemoveInputChannel(JNIEnv* env, jclass /* clazz */, jlong ptr, jobject tokenObj) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeRemoveInputChannel(JNIEnv* env, jobject nativeImplObj, jobject tokenObj) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
     sp<IBinder> token = ibinderForJavaObject(env, tokenObj);
 
-    status_t status = im->removeInputChannel(env, token);
+    status_t status = im->removeInputChannel(token);
     if (status && status != BAD_VALUE) { // ignore already removed channel
         std::string message;
         message += StringPrintf("Failed to remove input channel.  status=%d", status);
@@ -1669,48 +1662,46 @@
     }
 }
 
-static void nativePilferPointers(JNIEnv* env, jclass /* clazz */, jlong ptr, jobject tokenObj) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativePilferPointers(JNIEnv* env, jobject nativeImplObj, jobject tokenObj) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
     sp<IBinder> token = ibinderForJavaObject(env, tokenObj);
     im->pilferPointers(token);
 }
 
-static void nativeSetInputFilterEnabled(JNIEnv* /* env */, jclass /* clazz */,
-        jlong ptr, jboolean enabled) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetInputFilterEnabled(JNIEnv* env, jobject nativeImplObj, jboolean enabled) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
     im->getInputManager()->getDispatcher().setInputFilterEnabled(enabled);
 }
 
-static jboolean nativeSetInTouchMode(JNIEnv* /* env */, jclass /* clazz */, jlong ptr,
-                                     jboolean inTouchMode, jint pid, jint uid,
-                                     jboolean hasPermission) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jboolean nativeSetInTouchMode(JNIEnv* env, jobject nativeImplObj, jboolean inTouchMode,
+                                     jint pid, jint uid, jboolean hasPermission) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
     return im->getInputManager()->getDispatcher().setInTouchMode(inTouchMode, pid, uid,
                                                                  hasPermission);
 }
 
-static void nativeSetMaximumObscuringOpacityForTouch(JNIEnv* /* env */, jclass /* clazz */,
-                                                     jlong ptr, jfloat opacity) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetMaximumObscuringOpacityForTouch(JNIEnv* env, jobject nativeImplObj,
+                                                     jfloat opacity) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
     im->getInputManager()->getDispatcher().setMaximumObscuringOpacityForTouch(opacity);
 }
 
-static void nativeSetBlockUntrustedTouchesMode(JNIEnv* env, jclass /* clazz */, jlong ptr,
-                                               jint mode) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetBlockUntrustedTouchesMode(JNIEnv* env, jobject nativeImplObj, jint mode) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
     im->getInputManager()->getDispatcher().setBlockUntrustedTouchesMode(
             static_cast<BlockUntrustedTouchesMode>(mode));
 }
 
-static jint nativeInjectInputEvent(JNIEnv* env, jclass /* clazz */,
-        jlong ptr, jobject inputEventObj, jint injectorPid, jint injectorUid,
-        jint syncMode, jint timeoutMillis, jint policyFlags) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jint nativeInjectInputEvent(JNIEnv* env, jobject nativeImplObj, jobject inputEventObj,
+                                   jboolean injectIntoUid, jint uid, jint syncMode,
+                                   jint timeoutMillis, jint policyFlags) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
+    const std::optional<int32_t> targetUid = injectIntoUid ? std::make_optional(uid) : std::nullopt;
     // static_cast is safe because the value was already checked at the Java layer
     InputEventInjectionSync mode = static_cast<InputEventInjectionSync>(syncMode);
 
@@ -1723,8 +1714,7 @@
         }
 
         const InputEventInjectionResult result =
-                im->getInputManager()->getDispatcher().injectInputEvent(&keyEvent, injectorPid,
-                                                                        injectorUid, mode,
+                im->getInputManager()->getDispatcher().injectInputEvent(&keyEvent, targetUid, mode,
                                                                         std::chrono::milliseconds(
                                                                                 timeoutMillis),
                                                                         uint32_t(policyFlags));
@@ -1737,8 +1727,8 @@
         }
 
         const InputEventInjectionResult result =
-                im->getInputManager()->getDispatcher().injectInputEvent(motionEvent, injectorPid,
-                                                                        injectorUid, mode,
+                im->getInputManager()->getDispatcher().injectInputEvent(motionEvent, targetUid,
+                                                                        mode,
                                                                         std::chrono::milliseconds(
                                                                                 timeoutMillis),
                                                                         uint32_t(policyFlags));
@@ -1749,9 +1739,8 @@
     }
 }
 
-static jobject nativeVerifyInputEvent(JNIEnv* env, jclass /* clazz */, jlong ptr,
-                                      jobject inputEventObj) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jobject nativeVerifyInputEvent(JNIEnv* env, jobject nativeImplObj, jobject inputEventObj) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
     if (env->IsInstanceOf(inputEventObj, gKeyEventClassInfo.clazz)) {
         KeyEvent keyEvent;
@@ -1792,56 +1781,53 @@
     }
 }
 
-static void nativeToggleCapsLock(JNIEnv* env, jclass /* clazz */,
-         jlong ptr, jint deviceId) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeToggleCapsLock(JNIEnv* env, jobject nativeImplObj, jint deviceId) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
     im->getInputManager()->getReader().toggleCapsLockState(deviceId);
 }
 
-static void nativeDisplayRemoved(JNIEnv* env, jclass /* clazz */, jlong ptr, jint displayId) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeDisplayRemoved(JNIEnv* env, jobject nativeImplObj, jint displayId) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
     im->displayRemoved(env, displayId);
 }
 
-static void nativeSetFocusedApplication(JNIEnv* env, jclass /* clazz */,
-        jlong ptr, jint displayId, jobject applicationHandleObj) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetFocusedApplication(JNIEnv* env, jobject nativeImplObj, jint displayId,
+                                        jobject applicationHandleObj) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
     im->setFocusedApplication(env, displayId, applicationHandleObj);
 }
 
-static void nativeSetFocusedDisplay(JNIEnv* env, jclass /* clazz */,
-        jlong ptr, jint displayId) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetFocusedDisplay(JNIEnv* env, jobject nativeImplObj, jint displayId) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
-    im->setFocusedDisplay(env, displayId);
+    im->setFocusedDisplay(displayId);
 }
 
-static void nativeRequestPointerCapture(JNIEnv* env, jclass /* clazz */, jlong ptr,
-                                        jobject tokenObj, jboolean enabled) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeRequestPointerCapture(JNIEnv* env, jobject nativeImplObj, jobject tokenObj,
+                                        jboolean enabled) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
     sp<IBinder> windowToken = ibinderForJavaObject(env, tokenObj);
 
     im->requestPointerCapture(windowToken, enabled);
 }
 
-static void nativeSetInputDispatchMode(JNIEnv* /* env */,
-        jclass /* clazz */, jlong ptr, jboolean enabled, jboolean frozen) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetInputDispatchMode(JNIEnv* env, jobject nativeImplObj, jboolean enabled,
+                                       jboolean frozen) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
     im->setInputDispatchMode(enabled, frozen);
 }
 
-static void nativeSetSystemUiLightsOut(JNIEnv* /* env */, jclass /* clazz */, jlong ptr,
-                                       jboolean lightsOut) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetSystemUiLightsOut(JNIEnv* env, jobject nativeImplObj, jboolean lightsOut) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
     im->setSystemUiLightsOut(lightsOut);
 }
 
-static jboolean nativeTransferTouchFocus(JNIEnv* env, jclass /* clazz */, jlong ptr,
+static jboolean nativeTransferTouchFocus(JNIEnv* env, jobject nativeImplObj,
                                          jobject fromChannelTokenObj, jobject toChannelTokenObj,
                                          jboolean isDragDrop) {
     if (fromChannelTokenObj == nullptr || toChannelTokenObj == nullptr) {
@@ -1851,7 +1837,7 @@
     sp<IBinder> fromChannelToken = ibinderForJavaObject(env, fromChannelTokenObj);
     sp<IBinder> toChannelToken = ibinderForJavaObject(env, toChannelTokenObj);
 
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
     if (im->getInputManager()->getDispatcher().transferTouchFocus(fromChannelToken, toChannelToken,
                                                                   isDragDrop)) {
         return JNI_TRUE;
@@ -1860,11 +1846,11 @@
     }
 }
 
-static jboolean nativeTransferTouch(JNIEnv* env, jclass /* clazz */, jlong ptr,
+static jboolean nativeTransferTouch(JNIEnv* env, jobject nativeImplObj,
                                     jobject destChannelTokenObj) {
     sp<IBinder> destChannelToken = ibinderForJavaObject(env, destChannelTokenObj);
 
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
     if (im->getInputManager()->getDispatcher().transferTouch(destChannelToken)) {
         return JNI_TRUE;
     } else {
@@ -1872,42 +1858,39 @@
     }
 }
 
-static void nativeSetPointerSpeed(JNIEnv* /* env */, jclass /* clazz */, jlong ptr, jint speed) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetPointerSpeed(JNIEnv* env, jobject nativeImplObj, jint speed) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
     im->setPointerSpeed(speed);
 }
 
-static void nativeSetPointerAcceleration(JNIEnv* /* env */, jclass /* clazz */, jlong ptr,
-                                         jfloat acceleration) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetPointerAcceleration(JNIEnv* env, jobject nativeImplObj, jfloat acceleration) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
     im->setPointerAcceleration(acceleration);
 }
 
-static void nativeSetShowTouches(JNIEnv* /* env */,
-        jclass /* clazz */, jlong ptr, jboolean enabled) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetShowTouches(JNIEnv* env, jobject nativeImplObj, jboolean enabled) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
     im->setShowTouches(enabled);
 }
 
-static void nativeSetInteractive(JNIEnv* env,
-        jclass clazz, jlong ptr, jboolean interactive) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetInteractive(JNIEnv* env, jobject nativeImplObj, jboolean interactive) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
     im->setInteractive(interactive);
 }
 
-static void nativeReloadCalibration(JNIEnv* env, jclass clazz, jlong ptr) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeReloadCalibration(JNIEnv* env, jobject nativeImplObj) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
     im->reloadCalibration();
 }
 
-static void nativeVibrate(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId,
-                          jlongArray patternObj, jintArray amplitudesObj, jint repeat, jint token) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeVibrate(JNIEnv* env, jobject nativeImplObj, jint deviceId, jlongArray patternObj,
+                          jintArray amplitudesObj, jint repeat, jint token) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
     size_t patternSize = env->GetArrayLength(patternObj);
     if (patternSize > MAX_VIBRATE_PATTERN_SIZE) {
@@ -1940,10 +1923,10 @@
     im->getInputManager()->getReader().vibrate(deviceId, sequence, repeat, token);
 }
 
-static void nativeVibrateCombined(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId,
+static void nativeVibrateCombined(JNIEnv* env, jobject nativeImplObj, jint deviceId,
                                   jlongArray patternObj, jobject amplitudesObj, jint repeat,
                                   jint token) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
     size_t patternSize = env->GetArrayLength(patternObj);
 
@@ -1990,21 +1973,20 @@
     im->getInputManager()->getReader().vibrate(deviceId, sequence, repeat, token);
 }
 
-static void nativeCancelVibrate(JNIEnv* /* env */,
-        jclass /* clazz */, jlong ptr, jint deviceId, jint token) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeCancelVibrate(JNIEnv* env, jobject nativeImplObj, jint deviceId, jint token) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
     im->getInputManager()->getReader().cancelVibrate(deviceId, token);
 }
 
-static bool nativeIsVibrating(JNIEnv* /* env */, jclass /* clazz */, jlong ptr, jint deviceId) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static bool nativeIsVibrating(JNIEnv* env, jobject nativeImplObj, jint deviceId) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
     return im->getInputManager()->getReader().isVibrating(deviceId);
 }
 
-static jintArray nativeGetVibratorIds(JNIEnv* env, jclass clazz, jlong ptr, jint deviceId) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jintArray nativeGetVibratorIds(JNIEnv* env, jobject nativeImplObj, jint deviceId) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
     std::vector<int32_t> vibrators = im->getInputManager()->getReader().getVibratorIds(deviceId);
 
     jintArray vibIdArray = env->NewIntArray(vibrators.size());
@@ -2014,8 +1996,8 @@
     return vibIdArray;
 }
 
-static jobject nativeGetLights(JNIEnv* env, jclass clazz, jlong ptr, jint deviceId) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jobject nativeGetLights(JNIEnv* env, jobject nativeImplObj, jint deviceId) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
     jobject jLights = env->NewObject(gArrayListClassInfo.clazz, gArrayListClassInfo.constructor);
 
     std::vector<InputDeviceLightInfo> lights =
@@ -2059,9 +2041,9 @@
     return jLights;
 }
 
-static jint nativeGetLightPlayerId(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId,
+static jint nativeGetLightPlayerId(JNIEnv* env, jobject nativeImplObj, jint deviceId,
                                    jint lightId) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
     std::optional<int32_t> ret =
             im->getInputManager()->getReader().getLightPlayerId(deviceId, lightId);
@@ -2069,54 +2051,51 @@
     return static_cast<jint>(ret.value_or(0));
 }
 
-static jint nativeGetLightColor(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId,
-                                jint lightId) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jint nativeGetLightColor(JNIEnv* env, jobject nativeImplObj, jint deviceId, jint lightId) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
     std::optional<int32_t> ret =
             im->getInputManager()->getReader().getLightColor(deviceId, lightId);
     return static_cast<jint>(ret.value_or(0));
 }
 
-static void nativeSetLightPlayerId(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId,
-                                   jint lightId, jint playerId) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetLightPlayerId(JNIEnv* env, jobject nativeImplObj, jint deviceId, jint lightId,
+                                   jint playerId) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
     im->getInputManager()->getReader().setLightPlayerId(deviceId, lightId, playerId);
 }
 
-static void nativeSetLightColor(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId,
-                                jint lightId, jint color) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetLightColor(JNIEnv* env, jobject nativeImplObj, jint deviceId, jint lightId,
+                                jint color) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
     im->getInputManager()->getReader().setLightColor(deviceId, lightId, color);
 }
 
-static jint nativeGetBatteryCapacity(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jint nativeGetBatteryCapacity(JNIEnv* env, jobject nativeImplObj, jint deviceId) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
     std::optional<int32_t> ret = im->getInputManager()->getReader().getBatteryCapacity(deviceId);
     return static_cast<jint>(ret.value_or(android::os::IInputConstants::INVALID_BATTERY_CAPACITY));
 }
 
-static jint nativeGetBatteryStatus(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jint nativeGetBatteryStatus(JNIEnv* env, jobject nativeImplObj, jint deviceId) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
     std::optional<int32_t> ret = im->getInputManager()->getReader().getBatteryStatus(deviceId);
     return static_cast<jint>(ret.value_or(BATTERY_STATUS_UNKNOWN));
 }
 
-static void nativeReloadKeyboardLayouts(JNIEnv* /* env */,
-        jclass /* clazz */, jlong ptr) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeReloadKeyboardLayouts(JNIEnv* env, jobject nativeImplObj) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
     im->getInputManager()->getReader().requestRefreshConfiguration(
             InputReaderConfiguration::CHANGE_KEYBOARD_LAYOUTS);
 }
 
-static void nativeReloadDeviceAliases(JNIEnv* /* env */,
-        jclass /* clazz */, jlong ptr) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeReloadDeviceAliases(JNIEnv* env, jobject nativeImplObj) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
     im->getInputManager()->getReader().requestRefreshConfiguration(
             InputReaderConfiguration::CHANGE_DEVICE_ALIAS);
@@ -2131,58 +2110,54 @@
     return out;
 }
 
-static jstring nativeDump(JNIEnv* env, jclass /* clazz */, jlong ptr) {
+static jstring nativeDump(JNIEnv* env, jobject nativeImplObj) {
     std::string dump = dumpInputProperties();
 
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
     im->dump(dump);
 
     return env->NewStringUTF(dump.c_str());
 }
 
-static void nativeMonitor(JNIEnv* /* env */, jclass /* clazz */, jlong ptr) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeMonitor(JNIEnv* env, jobject nativeImplObj) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
     im->getInputManager()->getReader().monitor();
     im->getInputManager()->getDispatcher().monitor();
 }
 
-static jboolean nativeIsInputDeviceEnabled(JNIEnv* env /* env */,
-        jclass /* clazz */, jlong ptr, jint deviceId) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jboolean nativeIsInputDeviceEnabled(JNIEnv* env, jobject nativeImplObj, jint deviceId) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
     return im->getInputManager()->getReader().isInputDeviceEnabled(deviceId);
 }
 
-static void nativeEnableInputDevice(JNIEnv* /* env */,
-        jclass /* clazz */, jlong ptr, jint deviceId) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeEnableInputDevice(JNIEnv* env, jobject nativeImplObj, jint deviceId) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
     im->setInputDeviceEnabled(deviceId, true);
 }
 
-static void nativeDisableInputDevice(JNIEnv* /* env */,
-        jclass /* clazz */, jlong ptr, jint deviceId) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeDisableInputDevice(JNIEnv* env, jobject nativeImplObj, jint deviceId) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
     im->setInputDeviceEnabled(deviceId, false);
 }
 
-static void nativeSetPointerIconType(JNIEnv* /* env */, jclass /* clazz */, jlong ptr, jint iconId) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetPointerIconType(JNIEnv* env, jobject nativeImplObj, jint iconId) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
     im->setPointerIconType(iconId);
 }
 
-static void nativeReloadPointerIcons(JNIEnv* /* env */, jclass /* clazz */, jlong ptr) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeReloadPointerIcons(JNIEnv* env, jobject nativeImplObj) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
     im->reloadPointerIcons();
 }
 
-static void nativeSetCustomPointerIcon(JNIEnv* env, jclass /* clazz */,
-                                       jlong ptr, jobject iconObj) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetCustomPointerIcon(JNIEnv* env, jobject nativeImplObj, jobject iconObj) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
     PointerIcon pointerIcon;
     status_t result = android_view_PointerIcon_getLoadedIcon(env, iconObj, &pointerIcon);
@@ -2196,40 +2171,38 @@
     im->setCustomPointerIcon(spriteIcon);
 }
 
-static jboolean nativeCanDispatchToDisplay(JNIEnv* env, jclass /* clazz */, jlong ptr,
-        jint deviceId, jint displayId) {
-
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jboolean nativeCanDispatchToDisplay(JNIEnv* env, jobject nativeImplObj, jint deviceId,
+                                           jint displayId) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
     return im->getInputManager()->getReader().canDispatchToDisplay(deviceId, displayId);
 }
 
-static void nativeNotifyPortAssociationsChanged(JNIEnv* env, jclass /* clazz */, jlong ptr) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeNotifyPortAssociationsChanged(JNIEnv* env, jobject nativeImplObj) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
     im->getInputManager()->getReader().requestRefreshConfiguration(
             InputReaderConfiguration::CHANGE_DISPLAY_INFO);
 }
 
-static void nativeNotifyPointerDisplayIdChanged(JNIEnv* env, jclass /* clazz */, jlong ptr) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeNotifyPointerDisplayIdChanged(JNIEnv* env, jobject nativeImplObj) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
     im->notifyPointerDisplayIdChanged();
 }
 
-static void nativeSetDisplayEligibilityForPointerCapture(JNIEnv* env, jclass /* clazz */, jlong ptr,
+static void nativeSetDisplayEligibilityForPointerCapture(JNIEnv* env, jobject nativeImplObj,
                                                          jint displayId, jboolean isEligible) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
     im->getInputManager()->getDispatcher().setDisplayEligibilityForPointerCapture(displayId,
                                                                                   isEligible);
 }
 
-static void nativeChangeUniqueIdAssociation(JNIEnv* env, jclass /* clazz */, jlong ptr) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeChangeUniqueIdAssociation(JNIEnv* env, jobject nativeImplObj) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
     im->getInputManager()->getReader().requestRefreshConfiguration(
             InputReaderConfiguration::CHANGE_DISPLAY_INFO);
 }
 
-static void nativeSetMotionClassifierEnabled(JNIEnv* /* env */, jclass /* clazz */, jlong ptr,
-                                             jboolean enabled) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetMotionClassifierEnabled(JNIEnv* env, jobject nativeImplObj, jboolean enabled) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
     im->setMotionClassifierEnabled(enabled);
 }
@@ -2265,8 +2238,8 @@
     return sensorInfo;
 }
 
-static jobjectArray nativeGetSensorList(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jobjectArray nativeGetSensorList(JNIEnv* env, jobject nativeImplObj, jint deviceId) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
     std::vector<InputDeviceSensorInfo> sensors =
             im->getInputManager()->getReader().getSensors(deviceId);
 
@@ -2295,10 +2268,10 @@
     return arr;
 }
 
-static jboolean nativeEnableSensor(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId,
+static jboolean nativeEnableSensor(JNIEnv* env, jobject nativeImplObj, jint deviceId,
                                    jint sensorType, jint samplingPeriodUs,
                                    jint maxBatchReportLatencyUs) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
     return im->getInputManager()
             ->getReader()
@@ -2307,18 +2280,18 @@
                           std::chrono::microseconds(maxBatchReportLatencyUs));
 }
 
-static void nativeDisableSensor(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId,
+static void nativeDisableSensor(JNIEnv* env, jobject nativeImplObj, jint deviceId,
                                 jint sensorType) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
     im->getInputManager()->getReader().disableSensor(deviceId,
                                                      static_cast<InputDeviceSensorType>(
                                                              sensorType));
 }
 
-static jboolean nativeFlushSensor(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId,
+static jboolean nativeFlushSensor(JNIEnv* env, jobject nativeImplObj, jint deviceId,
                                   jint sensorType) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
     im->getInputManager()->getReader().flushSensor(deviceId,
                                                    static_cast<InputDeviceSensorType>(sensorType));
@@ -2327,8 +2300,8 @@
                                                                       sensorType));
 }
 
-static void nativeCancelCurrentTouch(JNIEnv* env, jclass /* clazz */, jlong ptr) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeCancelCurrentTouch(JNIEnv* env, jobject nativeImplObj) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
     im->getInputManager()->getDispatcher().cancelCurrentTouch();
 }
 
@@ -2336,87 +2309,84 @@
 
 static const JNINativeMethod gInputManagerMethods[] = {
         /* name, signature, funcPtr */
-        {"nativeInit",
+        {"init",
          "(Lcom/android/server/input/InputManagerService;Landroid/content/Context;Landroid/os/"
          "MessageQueue;)J",
          (void*)nativeInit},
-        {"nativeStart", "(J)V", (void*)nativeStart},
-        {"nativeSetDisplayViewports", "(J[Landroid/hardware/display/DisplayViewport;)V",
+        {"start", "()V", (void*)nativeStart},
+        {"setDisplayViewports", "([Landroid/hardware/display/DisplayViewport;)V",
          (void*)nativeSetDisplayViewports},
-        {"nativeGetScanCodeState", "(JIII)I", (void*)nativeGetScanCodeState},
-        {"nativeGetKeyCodeState", "(JIII)I", (void*)nativeGetKeyCodeState},
-        {"nativeGetSwitchState", "(JIII)I", (void*)nativeGetSwitchState},
-        {"nativeHasKeys", "(JII[I[Z)Z", (void*)nativeHasKeys},
-        {"nativeGetKeyCodeForKeyLocation", "(JII)I", (void*)nativeGetKeyCodeForKeyLocation},
-        {"nativeCreateInputChannel", "(JLjava/lang/String;)Landroid/view/InputChannel;",
+        {"getScanCodeState", "(III)I", (void*)nativeGetScanCodeState},
+        {"getKeyCodeState", "(III)I", (void*)nativeGetKeyCodeState},
+        {"getSwitchState", "(III)I", (void*)nativeGetSwitchState},
+        {"hasKeys", "(II[I[Z)Z", (void*)nativeHasKeys},
+        {"getKeyCodeForKeyLocation", "(II)I", (void*)nativeGetKeyCodeForKeyLocation},
+        {"createInputChannel", "(Ljava/lang/String;)Landroid/view/InputChannel;",
          (void*)nativeCreateInputChannel},
-        {"nativeCreateInputMonitor", "(JILjava/lang/String;I)Landroid/view/InputChannel;",
+        {"createInputMonitor", "(ILjava/lang/String;I)Landroid/view/InputChannel;",
          (void*)nativeCreateInputMonitor},
-        {"nativeRemoveInputChannel", "(JLandroid/os/IBinder;)V", (void*)nativeRemoveInputChannel},
-        {"nativePilferPointers", "(JLandroid/os/IBinder;)V", (void*)nativePilferPointers},
-        {"nativeSetInputFilterEnabled", "(JZ)V", (void*)nativeSetInputFilterEnabled},
-        {"nativeSetInTouchMode", "(JZIIZ)Z", (void*)nativeSetInTouchMode},
-        {"nativeSetMaximumObscuringOpacityForTouch", "(JF)V",
+        {"removeInputChannel", "(Landroid/os/IBinder;)V", (void*)nativeRemoveInputChannel},
+        {"pilferPointers", "(Landroid/os/IBinder;)V", (void*)nativePilferPointers},
+        {"setInputFilterEnabled", "(Z)V", (void*)nativeSetInputFilterEnabled},
+        {"setInTouchMode", "(ZIIZ)Z", (void*)nativeSetInTouchMode},
+        {"setMaximumObscuringOpacityForTouch", "(F)V",
          (void*)nativeSetMaximumObscuringOpacityForTouch},
-        {"nativeSetBlockUntrustedTouchesMode", "(JI)V", (void*)nativeSetBlockUntrustedTouchesMode},
-        {"nativeInjectInputEvent", "(JLandroid/view/InputEvent;IIIII)I",
-         (void*)nativeInjectInputEvent},
-        {"nativeVerifyInputEvent", "(JLandroid/view/InputEvent;)Landroid/view/VerifiedInputEvent;",
+        {"setBlockUntrustedTouchesMode", "(I)V", (void*)nativeSetBlockUntrustedTouchesMode},
+        {"injectInputEvent", "(Landroid/view/InputEvent;ZIIII)I", (void*)nativeInjectInputEvent},
+        {"verifyInputEvent", "(Landroid/view/InputEvent;)Landroid/view/VerifiedInputEvent;",
          (void*)nativeVerifyInputEvent},
-        {"nativeToggleCapsLock", "(JI)V", (void*)nativeToggleCapsLock},
-        {"nativeDisplayRemoved", "(JI)V", (void*)nativeDisplayRemoved},
-        {"nativeSetFocusedApplication", "(JILandroid/view/InputApplicationHandle;)V",
+        {"toggleCapsLock", "(I)V", (void*)nativeToggleCapsLock},
+        {"displayRemoved", "(I)V", (void*)nativeDisplayRemoved},
+        {"setFocusedApplication", "(ILandroid/view/InputApplicationHandle;)V",
          (void*)nativeSetFocusedApplication},
-        {"nativeSetFocusedDisplay", "(JI)V", (void*)nativeSetFocusedDisplay},
-        {"nativeRequestPointerCapture", "(JLandroid/os/IBinder;Z)V",
-         (void*)nativeRequestPointerCapture},
-        {"nativeSetInputDispatchMode", "(JZZ)V", (void*)nativeSetInputDispatchMode},
-        {"nativeSetSystemUiLightsOut", "(JZ)V", (void*)nativeSetSystemUiLightsOut},
-        {"nativeTransferTouchFocus", "(JLandroid/os/IBinder;Landroid/os/IBinder;Z)Z",
+        {"setFocusedDisplay", "(I)V", (void*)nativeSetFocusedDisplay},
+        {"requestPointerCapture", "(Landroid/os/IBinder;Z)V", (void*)nativeRequestPointerCapture},
+        {"setInputDispatchMode", "(ZZ)V", (void*)nativeSetInputDispatchMode},
+        {"setSystemUiLightsOut", "(Z)V", (void*)nativeSetSystemUiLightsOut},
+        {"transferTouchFocus", "(Landroid/os/IBinder;Landroid/os/IBinder;Z)Z",
          (void*)nativeTransferTouchFocus},
-        {"nativeTransferTouch", "(JLandroid/os/IBinder;)Z", (void*)nativeTransferTouch},
-        {"nativeSetPointerSpeed", "(JI)V", (void*)nativeSetPointerSpeed},
-        {"nativeSetPointerAcceleration", "(JF)V", (void*)nativeSetPointerAcceleration},
-        {"nativeSetShowTouches", "(JZ)V", (void*)nativeSetShowTouches},
-        {"nativeSetInteractive", "(JZ)V", (void*)nativeSetInteractive},
-        {"nativeReloadCalibration", "(J)V", (void*)nativeReloadCalibration},
-        {"nativeVibrate", "(JI[J[III)V", (void*)nativeVibrate},
-        {"nativeVibrateCombined", "(JI[JLandroid/util/SparseArray;II)V",
-         (void*)nativeVibrateCombined},
-        {"nativeCancelVibrate", "(JII)V", (void*)nativeCancelVibrate},
-        {"nativeIsVibrating", "(JI)Z", (void*)nativeIsVibrating},
-        {"nativeGetVibratorIds", "(JI)[I", (void*)nativeGetVibratorIds},
-        {"nativeGetLights", "(JI)Ljava/util/List;", (void*)nativeGetLights},
-        {"nativeGetLightPlayerId", "(JII)I", (void*)nativeGetLightPlayerId},
-        {"nativeGetLightColor", "(JII)I", (void*)nativeGetLightColor},
-        {"nativeSetLightPlayerId", "(JIII)V", (void*)nativeSetLightPlayerId},
-        {"nativeSetLightColor", "(JIII)V", (void*)nativeSetLightColor},
-        {"nativeGetBatteryCapacity", "(JI)I", (void*)nativeGetBatteryCapacity},
-        {"nativeGetBatteryStatus", "(JI)I", (void*)nativeGetBatteryStatus},
-        {"nativeReloadKeyboardLayouts", "(J)V", (void*)nativeReloadKeyboardLayouts},
-        {"nativeReloadDeviceAliases", "(J)V", (void*)nativeReloadDeviceAliases},
-        {"nativeDump", "(J)Ljava/lang/String;", (void*)nativeDump},
-        {"nativeMonitor", "(J)V", (void*)nativeMonitor},
-        {"nativeIsInputDeviceEnabled", "(JI)Z", (void*)nativeIsInputDeviceEnabled},
-        {"nativeEnableInputDevice", "(JI)V", (void*)nativeEnableInputDevice},
-        {"nativeDisableInputDevice", "(JI)V", (void*)nativeDisableInputDevice},
-        {"nativeSetPointerIconType", "(JI)V", (void*)nativeSetPointerIconType},
-        {"nativeReloadPointerIcons", "(J)V", (void*)nativeReloadPointerIcons},
-        {"nativeSetCustomPointerIcon", "(JLandroid/view/PointerIcon;)V",
+        {"transferTouch", "(Landroid/os/IBinder;)Z", (void*)nativeTransferTouch},
+        {"setPointerSpeed", "(I)V", (void*)nativeSetPointerSpeed},
+        {"setPointerAcceleration", "(F)V", (void*)nativeSetPointerAcceleration},
+        {"setShowTouches", "(Z)V", (void*)nativeSetShowTouches},
+        {"setInteractive", "(Z)V", (void*)nativeSetInteractive},
+        {"reloadCalibration", "()V", (void*)nativeReloadCalibration},
+        {"vibrate", "(I[J[III)V", (void*)nativeVibrate},
+        {"vibrateCombined", "(I[JLandroid/util/SparseArray;II)V", (void*)nativeVibrateCombined},
+        {"cancelVibrate", "(II)V", (void*)nativeCancelVibrate},
+        {"isVibrating", "(I)Z", (void*)nativeIsVibrating},
+        {"getVibratorIds", "(I)[I", (void*)nativeGetVibratorIds},
+        {"getLights", "(I)Ljava/util/List;", (void*)nativeGetLights},
+        {"getLightPlayerId", "(II)I", (void*)nativeGetLightPlayerId},
+        {"getLightColor", "(II)I", (void*)nativeGetLightColor},
+        {"setLightPlayerId", "(III)V", (void*)nativeSetLightPlayerId},
+        {"setLightColor", "(III)V", (void*)nativeSetLightColor},
+        {"getBatteryCapacity", "(I)I", (void*)nativeGetBatteryCapacity},
+        {"getBatteryStatus", "(I)I", (void*)nativeGetBatteryStatus},
+        {"reloadKeyboardLayouts", "()V", (void*)nativeReloadKeyboardLayouts},
+        {"reloadDeviceAliases", "()V", (void*)nativeReloadDeviceAliases},
+        {"dump", "()Ljava/lang/String;", (void*)nativeDump},
+        {"monitor", "()V", (void*)nativeMonitor},
+        {"isInputDeviceEnabled", "(I)Z", (void*)nativeIsInputDeviceEnabled},
+        {"enableInputDevice", "(I)V", (void*)nativeEnableInputDevice},
+        {"disableInputDevice", "(I)V", (void*)nativeDisableInputDevice},
+        {"setPointerIconType", "(I)V", (void*)nativeSetPointerIconType},
+        {"reloadPointerIcons", "()V", (void*)nativeReloadPointerIcons},
+        {"setCustomPointerIcon", "(Landroid/view/PointerIcon;)V",
          (void*)nativeSetCustomPointerIcon},
-        {"nativeCanDispatchToDisplay", "(JII)Z", (void*)nativeCanDispatchToDisplay},
-        {"nativeNotifyPortAssociationsChanged", "(J)V", (void*)nativeNotifyPortAssociationsChanged},
-        {"nativeChangeUniqueIdAssociation", "(J)V", (void*)nativeChangeUniqueIdAssociation},
-        {"nativeNotifyPointerDisplayIdChanged", "(J)V", (void*)nativeNotifyPointerDisplayIdChanged},
-        {"nativeSetDisplayEligibilityForPointerCapture", "(JIZ)V",
+        {"canDispatchToDisplay", "(II)Z", (void*)nativeCanDispatchToDisplay},
+        {"notifyPortAssociationsChanged", "()V", (void*)nativeNotifyPortAssociationsChanged},
+        {"changeUniqueIdAssociation", "()V", (void*)nativeChangeUniqueIdAssociation},
+        {"notifyPointerDisplayIdChanged", "()V", (void*)nativeNotifyPointerDisplayIdChanged},
+        {"setDisplayEligibilityForPointerCapture", "(IZ)V",
          (void*)nativeSetDisplayEligibilityForPointerCapture},
-        {"nativeSetMotionClassifierEnabled", "(JZ)V", (void*)nativeSetMotionClassifierEnabled},
-        {"nativeGetSensorList", "(JI)[Landroid/hardware/input/InputSensorInfo;",
+        {"setMotionClassifierEnabled", "(Z)V", (void*)nativeSetMotionClassifierEnabled},
+        {"getSensorList", "(I)[Landroid/hardware/input/InputSensorInfo;",
          (void*)nativeGetSensorList},
-        {"nativeEnableSensor", "(JIIII)Z", (void*)nativeEnableSensor},
-        {"nativeDisableSensor", "(JII)V", (void*)nativeDisableSensor},
-        {"nativeFlushSensor", "(JII)Z", (void*)nativeFlushSensor},
-        {"nativeCancelCurrentTouch", "(J)V", (void*)nativeCancelCurrentTouch},
+        {"enableSensor", "(IIII)Z", (void*)nativeEnableSensor},
+        {"disableSensor", "(II)V", (void*)nativeDisableSensor},
+        {"flushSensor", "(II)Z", (void*)nativeFlushSensor},
+        {"cancelCurrentTouch", "()V", (void*)nativeCancelCurrentTouch},
 };
 
 #define FIND_CLASS(var, className) \
@@ -2436,11 +2406,21 @@
         LOG_FATAL_IF(! (var), "Unable to find field " fieldName);
 
 int register_android_server_InputManager(JNIEnv* env) {
-    int res = jniRegisterNativeMethods(env, "com/android/server/input/InputManagerService",
-            gInputManagerMethods, NELEM(gInputManagerMethods));
-    (void) res;  // Faked use when LOG_NDEBUG.
+    int res = jniRegisterNativeMethods(env,
+                                       "com/android/server/input/"
+                                       "NativeInputManagerService$NativeImpl",
+                                       gInputManagerMethods, NELEM(gInputManagerMethods));
+    (void)res; // Faked use when LOG_NDEBUG.
     LOG_FATAL_IF(res < 0, "Unable to register native methods.");
 
+    FIND_CLASS(gNativeInputManagerServiceImpl.clazz,
+               "com/android/server/input/"
+               "NativeInputManagerService$NativeImpl");
+    gNativeInputManagerServiceImpl.clazz =
+            jclass(env->NewGlobalRef(gNativeInputManagerServiceImpl.clazz));
+    gNativeInputManagerServiceImpl.mPtr =
+            env->GetFieldID(gNativeInputManagerServiceImpl.clazz, "mPtr", "J");
+
     // Callbacks
 
     jclass clazz;
@@ -2499,9 +2479,6 @@
             "dispatchUnhandledKey",
             "(Landroid/os/IBinder;Landroid/view/KeyEvent;I)Landroid/view/KeyEvent;");
 
-    GET_METHOD_ID(gServiceClassInfo.checkInjectEventsPermission, clazz,
-            "checkInjectEventsPermission", "(II)Z");
-
     GET_METHOD_ID(gServiceClassInfo.onPointerDownOutsideFocus, clazz,
             "onPointerDownOutsideFocus", "(Landroid/os/IBinder;)V");
 
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index 5b4febd..0e9a04f 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -26,7 +26,7 @@
     <xs:element name="displayConfiguration">
         <xs:complexType>
             <xs:sequence>
-                <xs:element type="densityMap" name="densityMap" minOccurs="0" maxOccurs="1">
+                <xs:element type="densityMapping" name="densityMapping" minOccurs="0" maxOccurs="1">
                     <xs:annotation name="nullable"/>
                     <xs:annotation name="final"/>
                 </xs:element>
@@ -281,7 +281,7 @@
         </xs:sequence>
     </xs:complexType>
 
-    <xs:complexType name="densityMap">
+    <xs:complexType name="densityMapping">
         <xs:sequence>
             <xs:element name="density" type="density" maxOccurs="unbounded" minOccurs="1">
             </xs:element>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index ba83c9f..075edd7 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -30,8 +30,8 @@
     method public final void setWidth(@NonNull java.math.BigInteger);
   }
 
-  public class DensityMap {
-    ctor public DensityMap();
+  public class DensityMapping {
+    ctor public DensityMapping();
     method public java.util.List<com.android.server.display.config.Density> getDensity();
   }
 
@@ -40,7 +40,7 @@
     method @NonNull public final com.android.server.display.config.Thresholds getAmbientBrightnessChangeThresholds();
     method public final java.math.BigInteger getAmbientLightHorizonLong();
     method public final java.math.BigInteger getAmbientLightHorizonShort();
-    method @Nullable public final com.android.server.display.config.DensityMap getDensityMap();
+    method @Nullable public final com.android.server.display.config.DensityMapping getDensityMapping();
     method @NonNull public final com.android.server.display.config.Thresholds getDisplayBrightnessChangeThresholds();
     method public com.android.server.display.config.HighBrightnessMode getHighBrightnessMode();
     method public final com.android.server.display.config.SensorDetails getLightSensor();
@@ -56,7 +56,7 @@
     method public final void setAmbientBrightnessChangeThresholds(@NonNull com.android.server.display.config.Thresholds);
     method public final void setAmbientLightHorizonLong(java.math.BigInteger);
     method public final void setAmbientLightHorizonShort(java.math.BigInteger);
-    method public final void setDensityMap(@Nullable com.android.server.display.config.DensityMap);
+    method public final void setDensityMapping(@Nullable com.android.server.display.config.DensityMapping);
     method public final void setDisplayBrightnessChangeThresholds(@NonNull com.android.server.display.config.Thresholds);
     method public void setHighBrightnessMode(com.android.server.display.config.HighBrightnessMode);
     method public final void setLightSensor(com.android.server.display.config.SensorDetails);
diff --git a/services/devicepolicy/TEST_MAPPING b/services/devicepolicy/TEST_MAPPING
index 3d86cf3..72bba11 100644
--- a/services/devicepolicy/TEST_MAPPING
+++ b/services/devicepolicy/TEST_MAPPING
@@ -16,5 +16,15 @@
     {
       "name": "CtsDevicePolicyManagerTestCases"
     }
+  ],
+  "presubmit": [
+    {
+      "name": "CtsDevicePolicyManagerTestCases",
+      "options": [
+        {
+          "include-filter": "com.android.cts.devicepolicy.ManagedProfileTest#testParentProfileApiDisabled"
+        }
+      ]
+    }
   ]
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 2b64be2..62447c0 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -13354,8 +13354,12 @@
                 if (receivers.isEmpty()) {
                     return;
                 }
-                packageIntent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-                mContext.sendBroadcastAsUser(packageIntent, userHandle);
+                for (ResolveInfo receiver : receivers) {
+                    final Intent componentIntent = new Intent(packageIntent)
+                            .setComponent(receiver.getComponentInfo().getComponentName())
+                            .addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+                    mContext.sendBroadcastAsUser(componentIntent, userHandle);
+                }
             } catch (RemoteException ex) {
                 Slogf.w(LOG_TAG, "Cannot get list of broadcast receivers for %s because: %s.",
                         intent.getAction(), ex);
diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java
index e1fe1d8..90fd8ed 100644
--- a/services/midi/java/com/android/server/midi/MidiService.java
+++ b/services/midi/java/com/android/server/midi/MidiService.java
@@ -685,11 +685,13 @@
 
     private boolean hasNonMidiUuids(BluetoothDevice btDevice) {
         ParcelUuid[] uuidParcels = btDevice.getUuids();
-        // The assumption is that these services are indicative of devices that
-        // ARE NOT MIDI devices.
-        for (ParcelUuid parcel : uuidParcels) {
-            if (mNonMidiUUIDs.contains(parcel)) {
-                return true;
+        if (uuidParcels != null) {
+            // The assumption is that these services are indicative of devices that
+            // ARE NOT MIDI devices.
+            for (ParcelUuid parcel : uuidParcels) {
+                if (mNonMidiUUIDs.contains(parcel)) {
+                    return true;
+                }
             }
         }
         return false;
diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java
index e6fd916..d305fc5 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -618,10 +618,17 @@
             IntentFilter intentFilter = new IntentFilter();
             intentFilter.addAction(TelecomManager.ACTION_DEFAULT_DIALER_CHANGED);
             intentFilter.addAction(SmsApplication.ACTION_DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL);
-            BroadcastReceiver broadcastReceiver = new PerUserBroadcastReceiver(userId);
-            mBroadcastReceivers.put(userId, broadcastReceiver);
-            mContext.registerReceiverAsUser(
-                    broadcastReceiver, UserHandle.of(userId), intentFilter, null, null);
+
+            if (mBroadcastReceivers.get(userId) == null) {
+                BroadcastReceiver broadcastReceiver = new PerUserBroadcastReceiver(userId);
+                mBroadcastReceivers.put(userId, broadcastReceiver);
+                mContext.registerReceiverAsUser(
+                        broadcastReceiver, UserHandle.of(userId), intentFilter, null, null);
+            } else {
+                // Stopped was not called on this user before setup is called again. This
+                // could happen during consecutive rapid user switching.
+                if (DEBUG) Log.d(TAG, "PerUserBroadcastReceiver was registered for: " + userId);
+            }
 
             ContentObserver contactsContentObserver = new ContactsContentObserver(
                     BackgroundThread.getHandler());
@@ -639,9 +646,15 @@
                 // Should never occur for local calls.
             }
 
-            PackageMonitor packageMonitor = new PerUserPackageMonitor();
-            packageMonitor.register(mContext, null, UserHandle.of(userId), true);
-            mPackageMonitors.put(userId, packageMonitor);
+            if (mPackageMonitors.get(userId) == null) {
+                PackageMonitor packageMonitor = new PerUserPackageMonitor();
+                packageMonitor.register(mContext, null, UserHandle.of(userId), true);
+                mPackageMonitors.put(userId, packageMonitor);
+            } else {
+                // Stopped was not called on this user before setup is called again. This
+                // could happen during consecutive rapid user switching.
+                if (DEBUG) Log.d(TAG, "PerUserPackageMonitor was registered for: " + userId);
+            }
 
             if (userId == UserHandle.USER_SYSTEM) {
                 // The call log and MMS/SMS messages are shared across user profiles. So only need
diff --git a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
index 7b15224..7017440 100644
--- a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
+++ b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
@@ -34,6 +34,7 @@
 import com.android.server.testutils.TestHandler
 import com.android.server.testutils.mock
 import com.android.server.testutils.mockThrowOnUnmocked
+import com.android.server.testutils.spy
 import com.android.server.testutils.whenever
 import com.android.server.wm.ActivityTaskManagerInternal
 import com.google.common.truth.Truth.assertThat
@@ -45,9 +46,13 @@
 import org.junit.runners.Parameterized
 import org.mockito.Mockito.any
 import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.doAnswer
 import org.mockito.Mockito.doReturn
 import org.mockito.Mockito.intThat
+import org.mockito.Mockito.never
 import org.mockito.Mockito.same
+import org.mockito.Mockito.verify
 import org.testng.Assert.assertThrows
 import java.io.File
 import java.util.UUID
@@ -360,7 +365,7 @@
         val mockActivityTaskManager: ActivityTaskManagerInternal = mockThrowOnUnmocked {
             whenever(this.isCallerRecents(anyInt())) { false }
         }
-        val mockAppsFilter: AppsFilterImpl = mockThrowOnUnmocked {
+        val mockAppsFilter: AppsFilter = mockThrowOnUnmocked {
             whenever(this.shouldFilterApplication(anyInt(), any<PackageSetting>(),
                     any<PackageSetting>(), anyInt())) { false }
             whenever(this.snapshot()) { this@mockThrowOnUnmocked }
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
index b512ac5..c0b4f0f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
@@ -671,7 +671,7 @@
             mAppFGSTracker.onForegroundServiceStateChanged(testPkgName, testUid,
                     testPid, true);
             mAppFGSTracker.onForegroundServiceNotificationUpdated(
-                    testPkgName, testUid, notificationId);
+                    testPkgName, testUid, notificationId, false);
             mAppFGSTracker.mNotificationListener.onNotificationPosted(new StatusBarNotification(
                     testPkgName, null, notificationId, null, testUid, testPid,
                     new Notification(), UserHandle.of(testUser), null, mCurrentTimeMillis), null);
@@ -848,7 +848,7 @@
 
             // Pretend we have the notification dismissed.
             mAppFGSTracker.onForegroundServiceNotificationUpdated(
-                    testPkgName, testUid, -notificationId);
+                    testPkgName, testUid, notificationId, true);
             clearInvocations(mInjector.getAppStandbyInternal());
             clearInvocations(mInjector.getNotificationManager());
             clearInvocations(mBgRestrictionController);
@@ -885,7 +885,7 @@
 
             // Pretend notification is back on.
             mAppFGSTracker.onForegroundServiceNotificationUpdated(
-                    testPkgName, testUid, notificationId);
+                    testPkgName, testUid, notificationId, false);
             // Now we'll prompt the user even it has a FGS with notification.
             bgPromptFgsWithNotiToBgRestricted.set(true);
             clearInvocations(mInjector.getAppStandbyInternal());
@@ -1224,7 +1224,7 @@
             mAppFGSTracker.onForegroundServiceStateChanged(testPkgName1, testUid1,
                     testPid1, true);
             mAppFGSTracker.onForegroundServiceNotificationUpdated(
-                    testPkgName1, testUid1, fgsNotificationId);
+                    testPkgName1, testUid1, fgsNotificationId, false);
             mAppFGSTracker.mNotificationListener.onNotificationPosted(new StatusBarNotification(
                     testPkgName1, null, fgsNotificationId, null, testUid1, testPid1,
                     new Notification(), UserHandle.of(testUser1), null, mCurrentTimeMillis), null);
@@ -1235,7 +1235,7 @@
 
             // Pretend we have the notification dismissed.
             mAppFGSTracker.onForegroundServiceNotificationUpdated(
-                    testPkgName1, testUid1, -fgsNotificationId);
+                    testPkgName1, testUid1, fgsNotificationId, true);
 
             // Verify we have the notification.
             notificationId = checkNotificationShown(
@@ -1500,7 +1500,7 @@
         if (withNotification) {
             final int notificationId = 1000;
             mAppFGSTracker.onForegroundServiceNotificationUpdated(
-                    packageName, uid, notificationId);
+                    packageName, uid, notificationId, false);
             final StatusBarNotification noti = new StatusBarNotification(
                     packageName, null, notificationId, null, uid, pid,
                     new Notification(), UserHandle.of(UserHandle.getUserId(uid)),
@@ -3056,6 +3056,11 @@
         long currentTimeMillis() {
             return mCurrentTimeMillis;
         }
+
+        @Override
+        boolean isTest() {
+            return true;
+        }
     }
 
     private class TestBaseTrackerInjector<T extends BaseAppStatePolicy>
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DensityMapTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DensityMapTest.java
deleted file mode 100644
index 3f69f1b..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/display/DensityMapTest.java
+++ /dev/null
@@ -1,143 +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.server.display;
-
-import static com.android.server.display.DensityMap.Entry;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThrows;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class DensityMapTest {
-
-    @Test
-    public void testConstructor_withBadConfig_throwsException() {
-        assertThrows(IllegalStateException.class, () ->
-                DensityMap.createByOwning(new Entry[]{
-                        new Entry(1080, 1920, 320),
-                        new Entry(1080, 1920, 320)})
-        );
-
-        assertThrows(IllegalStateException.class, () ->
-                DensityMap.createByOwning(new Entry[]{
-                        new Entry(1080, 1920, 320),
-                        new Entry(1920, 1080, 120)})
-        );
-
-        assertThrows(IllegalStateException.class, () ->
-                DensityMap.createByOwning(new Entry[]{
-                        new Entry(1080, 1920, 320),
-                        new Entry(2160, 3840, 120)})
-        );
-
-        assertThrows(IllegalStateException.class, () ->
-                DensityMap.createByOwning(new Entry[]{
-                        new Entry(1080, 1920, 320),
-                        new Entry(3840, 2160, 120)})
-        );
-
-        // Two entries with the same diagonal
-        assertThrows(IllegalStateException.class, () ->
-                DensityMap.createByOwning(new Entry[]{
-                        new Entry(500, 500, 123),
-                        new Entry(100, 700, 456)})
-        );
-    }
-
-    @Test
-    public void testGetDensityForResolution_withResolutionMatch_returnsDensityFromConfig() {
-        DensityMap densityMap = DensityMap.createByOwning(new Entry[]{
-                new Entry(720, 1280, 213),
-                new Entry(1080, 1920, 320),
-                new Entry(2160, 3840, 640)});
-
-        assertEquals(213, densityMap.getDensityForResolution(720, 1280));
-        assertEquals(213, densityMap.getDensityForResolution(1280, 720));
-
-        assertEquals(320, densityMap.getDensityForResolution(1080, 1920));
-        assertEquals(320, densityMap.getDensityForResolution(1920, 1080));
-
-        assertEquals(640, densityMap.getDensityForResolution(2160, 3840));
-        assertEquals(640, densityMap.getDensityForResolution(3840, 2160));
-    }
-
-    @Test
-    public void testGetDensityForResolution_withDiagonalMatch_returnsDensityFromConfig() {
-        DensityMap densityMap = DensityMap.createByOwning(
-                        new Entry[]{ new Entry(500, 500, 123)});
-
-        // 500x500 has the same diagonal as 100x700
-        assertEquals(123, densityMap.getDensityForResolution(100, 700));
-    }
-
-    @Test
-    public void testGetDensityForResolution_withOneEntry_withNoMatch_returnsExtrapolatedDensity() {
-        DensityMap densityMap = DensityMap.createByOwning(
-                new Entry[]{ new Entry(1080, 1920, 320)});
-
-        assertEquals(320, densityMap.getDensityForResolution(1081, 1920));
-        assertEquals(320, densityMap.getDensityForResolution(1080, 1921));
-
-        assertEquals(640, densityMap.getDensityForResolution(2160, 3840));
-        assertEquals(640, densityMap.getDensityForResolution(3840, 2160));
-
-        assertEquals(213, densityMap.getDensityForResolution(720, 1280));
-        assertEquals(213, densityMap.getDensityForResolution(1280, 720));
-    }
-
-    @Test
-    public void testGetDensityForResolution_withTwoEntries_withNoMatch_returnExtrapolatedDensity() {
-        DensityMap densityMap = DensityMap.createByOwning(new Entry[]{
-                new Entry(1080, 1920, 320),
-                new Entry(2160, 3840, 320)});
-
-        // Resolution is smaller than all entries
-        assertEquals(213, densityMap.getDensityForResolution(720, 1280));
-        assertEquals(213, densityMap.getDensityForResolution(1280, 720));
-
-        // Resolution is bigger than all entries
-        assertEquals(320 * 2, densityMap.getDensityForResolution(2160 * 2, 3840 * 2));
-        assertEquals(320 * 2, densityMap.getDensityForResolution(3840 * 2, 2160 * 2));
-    }
-
-    @Test
-    public void testGetDensityForResolution_withNoMatch_returnsInterpolatedDensity() {
-        {
-            DensityMap densityMap = DensityMap.createByOwning(new Entry[]{
-                    new Entry(1080, 1920, 320),
-                    new Entry(2160, 3840, 320)});
-
-            assertEquals(320, densityMap.getDensityForResolution(2000, 2000));
-        }
-
-        {
-            DensityMap densityMap = DensityMap.createByOwning(new Entry[]{
-                    new Entry(720, 1280, 213),
-                    new Entry(2160, 3840, 640)});
-
-            assertEquals(320, densityMap.getDensityForResolution(1080, 1920));
-            assertEquals(320, densityMap.getDensityForResolution(1920, 1080));
-        }
-    }
-}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DensityMappingTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DensityMappingTest.java
new file mode 100644
index 0000000..ae7a2a4
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DensityMappingTest.java
@@ -0,0 +1,143 @@
+/*
+ * 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.server.display;
+
+import static com.android.server.display.DensityMapping.Entry;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DensityMappingTest {
+
+    @Test
+    public void testConstructor_withBadConfig_throwsException() {
+        assertThrows(IllegalStateException.class, () ->
+                DensityMapping.createByOwning(new Entry[]{
+                        new Entry(1080, 1920, 320),
+                        new Entry(1080, 1920, 320)})
+        );
+
+        assertThrows(IllegalStateException.class, () ->
+                DensityMapping.createByOwning(new Entry[]{
+                        new Entry(1080, 1920, 320),
+                        new Entry(1920, 1080, 120)})
+        );
+
+        assertThrows(IllegalStateException.class, () ->
+                DensityMapping.createByOwning(new Entry[]{
+                        new Entry(1080, 1920, 320),
+                        new Entry(2160, 3840, 120)})
+        );
+
+        assertThrows(IllegalStateException.class, () ->
+                DensityMapping.createByOwning(new Entry[]{
+                        new Entry(1080, 1920, 320),
+                        new Entry(3840, 2160, 120)})
+        );
+
+        // Two entries with the same diagonal
+        assertThrows(IllegalStateException.class, () ->
+                DensityMapping.createByOwning(new Entry[]{
+                        new Entry(500, 500, 123),
+                        new Entry(100, 700, 456)})
+        );
+    }
+
+    @Test
+    public void testGetDensityForResolution_withResolutionMatch_returnsDensityFromConfig() {
+        DensityMapping densityMapping = DensityMapping.createByOwning(new Entry[]{
+                new Entry(720, 1280, 213),
+                new Entry(1080, 1920, 320),
+                new Entry(2160, 3840, 640)});
+
+        assertEquals(213, densityMapping.getDensityForResolution(720, 1280));
+        assertEquals(213, densityMapping.getDensityForResolution(1280, 720));
+
+        assertEquals(320, densityMapping.getDensityForResolution(1080, 1920));
+        assertEquals(320, densityMapping.getDensityForResolution(1920, 1080));
+
+        assertEquals(640, densityMapping.getDensityForResolution(2160, 3840));
+        assertEquals(640, densityMapping.getDensityForResolution(3840, 2160));
+    }
+
+    @Test
+    public void testGetDensityForResolution_withDiagonalMatch_returnsDensityFromConfig() {
+        DensityMapping densityMapping = DensityMapping.createByOwning(
+                        new Entry[]{ new Entry(500, 500, 123)});
+
+        // 500x500 has the same diagonal as 100x700
+        assertEquals(123, densityMapping.getDensityForResolution(100, 700));
+    }
+
+    @Test
+    public void testGetDensityForResolution_withOneEntry_withNoMatch_returnsExtrapolatedDensity() {
+        DensityMapping densityMapping = DensityMapping.createByOwning(
+                new Entry[]{ new Entry(1080, 1920, 320)});
+
+        assertEquals(320, densityMapping.getDensityForResolution(1081, 1920));
+        assertEquals(320, densityMapping.getDensityForResolution(1080, 1921));
+
+        assertEquals(640, densityMapping.getDensityForResolution(2160, 3840));
+        assertEquals(640, densityMapping.getDensityForResolution(3840, 2160));
+
+        assertEquals(213, densityMapping.getDensityForResolution(720, 1280));
+        assertEquals(213, densityMapping.getDensityForResolution(1280, 720));
+    }
+
+    @Test
+    public void testGetDensityForResolution_withTwoEntries_withNoMatch_returnExtrapolatedDensity() {
+        DensityMapping densityMapping = DensityMapping.createByOwning(new Entry[]{
+                new Entry(1080, 1920, 320),
+                new Entry(2160, 3840, 320)});
+
+        // Resolution is smaller than all entries
+        assertEquals(213, densityMapping.getDensityForResolution(720, 1280));
+        assertEquals(213, densityMapping.getDensityForResolution(1280, 720));
+
+        // Resolution is bigger than all entries
+        assertEquals(320 * 2, densityMapping.getDensityForResolution(2160 * 2, 3840 * 2));
+        assertEquals(320 * 2, densityMapping.getDensityForResolution(3840 * 2, 2160 * 2));
+    }
+
+    @Test
+    public void testGetDensityForResolution_withNoMatch_returnsInterpolatedDensity() {
+        {
+            DensityMapping densityMapping = DensityMapping.createByOwning(new Entry[]{
+                    new Entry(1080, 1920, 320),
+                    new Entry(2160, 3840, 320)});
+
+            assertEquals(320, densityMapping.getDensityForResolution(2000, 2000));
+        }
+
+        {
+            DensityMapping densityMapping = DensityMapping.createByOwning(new Entry[]{
+                    new Entry(720, 1280, 213),
+                    new Entry(2160, 3840, 640)});
+
+            assertEquals(320, densityMapping.getDensityForResolution(1080, 1920));
+            assertEquals(320, densityMapping.getDensityForResolution(1920, 1080));
+        }
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index 9a4f8e2..43ba39a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -2164,6 +2164,49 @@
         }
     }
 
+    @Test
+    public void testIsWithinEJQuotaLocked_TempAllowlisting_Restricted() {
+        setDischarging();
+        JobStatus js = createExpeditedJobStatus("testIsWithinEJQuotaLocked_TempAllowlisting_Restricted", 1);
+        setStandbyBucket(RESTRICTED_INDEX, js);
+        setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 10 * MINUTE_IN_MILLIS);
+        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+                createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true);
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+                createTimingSession(now - (30 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true);
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+                createTimingSession(now - (5 * MINUTE_IN_MILLIS), 4 * MINUTE_IN_MILLIS, 5), true);
+        synchronized (mQuotaController.mLock) {
+            assertFalse(mQuotaController.isWithinEJQuotaLocked(js));
+        }
+
+        setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
+        final long gracePeriodMs = 15 * SECOND_IN_MILLIS;
+        setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, gracePeriodMs);
+        Handler handler = mQuotaController.getHandler();
+        spyOn(handler);
+
+        // The temp allowlist should not enable RESTRICTED apps' to schedule & start EJs if they're
+        // out of quota.
+        mTempAllowlistListener.onAppAdded(mSourceUid);
+        synchronized (mQuotaController.mLock) {
+            assertFalse(mQuotaController.isWithinEJQuotaLocked(js));
+        }
+        advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        mTempAllowlistListener.onAppRemoved(mSourceUid);
+        advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        // Still in grace period
+        synchronized (mQuotaController.mLock) {
+            assertFalse(mQuotaController.isWithinEJQuotaLocked(js));
+        }
+        advanceElapsedClock(6 * SECOND_IN_MILLIS);
+        // Out of grace period.
+        synchronized (mQuotaController.mLock) {
+            assertFalse(mQuotaController.isWithinEJQuotaLocked(js));
+        }
+    }
+
     /**
      * Tests that Timers properly track sessions when an app becomes top and is closed.
      */
@@ -5559,6 +5602,111 @@
                 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
     }
 
+    @Test
+    public void testEJTimerTracking_TempAllowlisting_Restricted() {
+        setDischarging();
+        setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
+        final long gracePeriodMs = 15 * SECOND_IN_MILLIS;
+        setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, gracePeriodMs);
+        Handler handler = mQuotaController.getHandler();
+        spyOn(handler);
+
+        JobStatus job = createExpeditedJobStatus("testEJTimerTracking_TempAllowlisting_Restricted", 1);
+        setStandbyBucket(RESTRICTED_INDEX, job);
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.maybeStartTrackingJobLocked(job, null);
+        }
+        assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+        List<TimingSession> expected = new ArrayList<>();
+
+        long start = JobSchedulerService.sElapsedRealtimeClock.millis();
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.prepareForExecutionLocked(job);
+        }
+        advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.maybeStopTrackingJobLocked(job, job, true);
+        }
+        expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
+        assertEquals(expected,
+                mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+        advanceElapsedClock(SECOND_IN_MILLIS);
+
+        // Job starts after app is added to temp allowlist and stops before removal.
+        start = JobSchedulerService.sElapsedRealtimeClock.millis();
+        mTempAllowlistListener.onAppAdded(mSourceUid);
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.maybeStartTrackingJobLocked(job, null);
+            mQuotaController.prepareForExecutionLocked(job);
+        }
+        advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.maybeStopTrackingJobLocked(job, null, false);
+        }
+        expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
+        assertEquals(expected,
+                mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+        // Job starts after app is added to temp allowlist and stops after removal,
+        // before grace period ends.
+        start = JobSchedulerService.sElapsedRealtimeClock.millis();
+        mTempAllowlistListener.onAppAdded(mSourceUid);
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.maybeStartTrackingJobLocked(job, null);
+            mQuotaController.prepareForExecutionLocked(job);
+        }
+        advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        mTempAllowlistListener.onAppRemoved(mSourceUid);
+        long elapsedGracePeriodMs = 2 * SECOND_IN_MILLIS;
+        advanceElapsedClock(elapsedGracePeriodMs);
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.maybeStopTrackingJobLocked(job, null, false);
+        }
+        expected.add(createTimingSession(start, 12 * SECOND_IN_MILLIS, 1));
+        assertEquals(expected,
+                mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+        advanceElapsedClock(SECOND_IN_MILLIS);
+        elapsedGracePeriodMs += SECOND_IN_MILLIS;
+
+        // Job starts during grace period and ends after grace period ends
+        start = JobSchedulerService.sElapsedRealtimeClock.millis();
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.maybeStartTrackingJobLocked(job, null);
+            mQuotaController.prepareForExecutionLocked(job);
+        }
+        final long remainingGracePeriod = gracePeriodMs - elapsedGracePeriodMs;
+        advanceElapsedClock(remainingGracePeriod);
+        // Wait for handler to update Timer
+        // Can't directly evaluate the message because for some reason, the captured message returns
+        // the wrong 'what' even though the correct message goes to the handler and the correct
+        // path executes.
+        verify(handler, timeout(gracePeriodMs + 5 * SECOND_IN_MILLIS)).handleMessage(any());
+        advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS + remainingGracePeriod, 1));
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.maybeStopTrackingJobLocked(job, job, true);
+        }
+        assertEquals(expected,
+                mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+        // Job starts and runs completely after temp allowlist grace period.
+        advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        start = JobSchedulerService.sElapsedRealtimeClock.millis();
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.maybeStartTrackingJobLocked(job, null);
+            mQuotaController.prepareForExecutionLocked(job);
+        }
+        advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.maybeStopTrackingJobLocked(job, job, true);
+        }
+        expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
+        assertEquals(expected,
+                mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+    }
+
     /**
      * Tests that Timers properly track sessions when TOP state and temp allowlisting overlaps.
      */
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeSettingsHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeSettingsHelper.java
index cd70020..b76abe6 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeSettingsHelper.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeSettingsHelper.java
@@ -84,6 +84,8 @@
     private final Setting mBackgroundThrottlePackageWhitelistSetting = new Setting(
             Collections.emptySet());
     private final Setting mGnssMeasurementsFullTrackingSetting = new Setting(Boolean.FALSE);
+    private final Setting mAdasPackageAllowlist = new Setting(
+            new PackageTagsList.Builder().build());
     private final Setting mIgnoreSettingsAllowlist = new Setting(
             new PackageTagsList.Builder().build());
     private final Setting mBackgroundThrottleProximityAlertIntervalSetting = new Setting(
@@ -194,10 +196,29 @@
     }
 
     @Override
+    public PackageTagsList getAdasAllowlist() {
+        return mAdasPackageAllowlist.getValue(PackageTagsList.class);
+    }
+
+    @Override
+    public void addAdasAllowlistChangedListener(GlobalSettingChangedListener listener) {
+        mAdasPackageAllowlist.addListener(listener);
+    }
+
+    @Override
+    public void removeAdasAllowlistChangedListener(GlobalSettingChangedListener listener) {
+        mAdasPackageAllowlist.removeListener(listener);
+    }
+
+    @Override
     public PackageTagsList getIgnoreSettingsAllowlist() {
         return mIgnoreSettingsAllowlist.getValue(PackageTagsList.class);
     }
 
+    public void setAdasSettingsAllowlist(PackageTagsList newValue) {
+        mAdasPackageAllowlist.setValue(newValue);
+    }
+
     public void setIgnoreSettingsAllowlist(PackageTagsList newValue) {
         mIgnoreSettingsAllowlist.setValue(newValue);
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
index d8f409d..71cc65b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
@@ -1107,6 +1107,10 @@
         doReturn(true).when(mPackageManager).hasSystemFeature(FEATURE_AUTOMOTIVE);
         doReturn(true).when(mResources).getBoolean(R.bool.config_defaultAdasGnssLocationEnabled);
 
+        mInjector.getSettingsHelper().setAdasSettingsAllowlist(
+                new PackageTagsList.Builder().add(
+                        IDENTITY.getPackageName()).build());
+
         createManager(GPS_PROVIDER);
 
         ILocationListener listener1 = createMockLocationListener();
@@ -1136,6 +1140,10 @@
         doReturn(true).when(mPackageManager).hasSystemFeature(FEATURE_AUTOMOTIVE);
         doReturn(true).when(mResources).getBoolean(R.bool.config_defaultAdasGnssLocationEnabled);
 
+        mInjector.getSettingsHelper().setAdasSettingsAllowlist(
+                new PackageTagsList.Builder().add(
+                        IDENTITY.getPackageName()).build());
+
         createManager(GPS_PROVIDER);
 
         ILocationListener listener1 = createMockLocationListener();
@@ -1160,11 +1168,16 @@
 
     @Test
     public void testProviderRequest_AdasGnssBypass_ProviderDisabled_AdasDisabled() {
+        doReturn(true).when(mPackageManager).hasSystemFeature(FEATURE_AUTOMOTIVE);
+        doReturn(true).when(mResources).getBoolean(R.bool.config_defaultAdasGnssLocationEnabled);
+
         mInjector.getSettingsHelper().setIgnoreSettingsAllowlist(
                 new PackageTagsList.Builder().add(
                         IDENTITY.getPackageName()).build());
-        doReturn(true).when(mPackageManager).hasSystemFeature(FEATURE_AUTOMOTIVE);
-        doReturn(true).when(mResources).getBoolean(R.bool.config_defaultAdasGnssLocationEnabled);
+
+        mInjector.getSettingsHelper().setAdasSettingsAllowlist(
+                new PackageTagsList.Builder().add(
+                        IDENTITY.getPackageName()).build());
 
         createManager(GPS_PROVIDER);
 
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 353c8e2..0567f58 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -194,7 +194,7 @@
         val packageParser: PackageParser2 = mock()
         val keySetManagerService: KeySetManagerService = mock()
         val packageAbiHelper: PackageAbiHelper = mock()
-        val appsFilter: AppsFilterImpl = mock {
+        val appsFilter: AppsFilter = mock {
             whenever(snapshot()) { this@mock }
         }
         val dexManager: DexManager = mock()
diff --git a/services/tests/servicestests/src/com/android/server/am/DropboxRateLimiterTest.java b/services/tests/servicestests/src/com/android/server/am/DropboxRateLimiterTest.java
new file mode 100644
index 0000000..00f4c39
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/DropboxRateLimiterTest.java
@@ -0,0 +1,87 @@
+/*
+ * 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.am;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.SystemClock;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test class for {@link DropboxRateLimiter}.
+ *
+ * Build/Install/Run:
+ *  atest DropboxRateLimiterTest
+ */
+public class DropboxRateLimiterTest {
+    private DropboxRateLimiter mRateLimiter;
+    private TestClock mClock;
+
+    @Before
+    public void setUp() {
+        mClock = new TestClock();
+        mRateLimiter = new DropboxRateLimiter(mClock);
+    }
+
+    @Test
+    public void testMultipleProcesses() {
+        // The first 5 entries should not be rate limited.
+        assertFalse(mRateLimiter.shouldRateLimit("tag", "process"));
+        assertFalse(mRateLimiter.shouldRateLimit("tag", "process"));
+        assertFalse(mRateLimiter.shouldRateLimit("tag", "process"));
+        assertFalse(mRateLimiter.shouldRateLimit("tag", "process"));
+        assertFalse(mRateLimiter.shouldRateLimit("tag", "process"));
+        // Different processes and tags should not get rate limited either.
+        assertFalse(mRateLimiter.shouldRateLimit("tag", "process2"));
+        assertFalse(mRateLimiter.shouldRateLimit("tag2", "process"));
+        // The 6th entry of the same process should be rate limited.
+        assertTrue(mRateLimiter.shouldRateLimit("tag", "process"));
+    }
+
+    @Test
+    public void testBufferClearing() throws Exception {
+        // The first 5 entries should not be rate limited.
+        assertFalse(mRateLimiter.shouldRateLimit("tag", "process"));
+        assertFalse(mRateLimiter.shouldRateLimit("tag", "process"));
+        assertFalse(mRateLimiter.shouldRateLimit("tag", "process"));
+        assertFalse(mRateLimiter.shouldRateLimit("tag", "process"));
+        assertFalse(mRateLimiter.shouldRateLimit("tag", "process"));
+        // The 6th entry of the same process should be rate limited.
+        assertTrue(mRateLimiter.shouldRateLimit("tag", "process"));
+
+        // After 11 seconds there should be nothing left in the buffer and the same type of entry
+        // should not get rate limited anymore.
+        mClock.setOffsetMillis(11000);
+
+        assertFalse(mRateLimiter.shouldRateLimit("tag", "process"));
+    }
+
+    private static class TestClock implements DropboxRateLimiter.Clock {
+        long mOffsetMillis = 0L;
+
+        public long uptimeMillis() {
+            return mOffsetMillis + SystemClock.uptimeMillis();
+        }
+
+        public void setOffsetMillis(long millis) {
+            mOffsetMillis = millis;
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/backup/restore/FullRestoreEngineTest.java b/services/tests/servicestests/src/com/android/server/backup/restore/FullRestoreEngineTest.java
new file mode 100644
index 0000000..049c745
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/backup/restore/FullRestoreEngineTest.java
@@ -0,0 +1,150 @@
+/*
+ * 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.backup.restore;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.backup.BackupAgent;
+import android.platform.test.annotations.Presubmit;
+import android.system.OsConstants;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.backup.FileMetadata;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class FullRestoreEngineTest {
+    private static final String DEFAULT_PACKAGE_NAME = "package";
+    private static final String DEFAULT_DOMAIN_NAME = "domain";
+    private static final String NEW_PACKAGE_NAME = "new_package";
+    private static final String NEW_DOMAIN_NAME = "new_domain";
+
+    private FullRestoreEngine mRestoreEngine;
+
+    @Before
+    public void setUp() {
+        mRestoreEngine = new FullRestoreEngine();
+    }
+
+    @Test
+    public void shouldSkipReadOnlyDir_skipsAllReadonlyDirsAndTheirChildren() {
+        // Create the file tree.
+        TestFile[] testFiles = new TestFile[] {
+                TestFile.dir("root"),
+                TestFile.file("root/auth_token"),
+                TestFile.dir("root/media"),
+                TestFile.file("root/media/picture1.png"),
+                TestFile.file("root/push_token.txt"),
+                TestFile.dir("root/read-only-dir-1").markReadOnly().expectSkipped(),
+                TestFile.dir("root/read-only-dir-1/writable-subdir").expectSkipped(),
+                TestFile.file("root/read-only-dir-1/writable-subdir/writable-file").expectSkipped(),
+                TestFile.dir("root/read-only-dir-1/writable-subdir/read-only-subdir-2")
+                        .markReadOnly().expectSkipped(),
+                TestFile.file("root/read-only-dir-1/writable-file").expectSkipped(),
+                TestFile.file("root/random-stuff.txt"),
+                TestFile.dir("root/database"),
+                TestFile.file("root/database/users.db"),
+                TestFile.dir("root/read-only-dir-2").markReadOnly().expectSkipped(),
+                TestFile.file("root/read-only-dir-2/writable-file-1").expectSkipped(),
+                TestFile.file("root/read-only-dir-2/writable-file-2").expectSkipped(),
+        };
+
+        assertCorrectItemsAreSkipped(testFiles);
+    }
+
+    @Test
+    public void shouldSkipReadOnlyDir_onlySkipsChildrenUnderTheSamePackage() {
+        TestFile[] testFiles = new TestFile[]{
+                TestFile.dir("read-only-dir").markReadOnly().expectSkipped(),
+                TestFile.file("read-only-dir/file").expectSkipped(),
+                TestFile.file("read-only-dir/file-from-different-package")
+                        .setPackage(NEW_PACKAGE_NAME),
+        };
+
+        assertCorrectItemsAreSkipped(testFiles);
+    }
+
+    @Test
+    public void shouldSkipReadOnlyDir_onlySkipsChildrenUnderTheSameDomain() {
+        TestFile[] testFiles = new TestFile[]{
+                TestFile.dir("read-only-dir").markReadOnly().expectSkipped(),
+                TestFile.file("read-only-dir/file").expectSkipped(),
+                TestFile.file("read-only-dir/file-from-different-domain")
+                        .setDomain(NEW_DOMAIN_NAME),
+        };
+
+        assertCorrectItemsAreSkipped(testFiles);
+    }
+
+    private void assertCorrectItemsAreSkipped(TestFile[] testFiles) {
+        // Verify all directories marked with .expectSkipped are skipped.
+        for (TestFile testFile : testFiles) {
+            boolean actualExcluded = mRestoreEngine.shouldSkipReadOnlyDir(testFile.mMetadata);
+            boolean expectedExcluded = testFile.mShouldSkip;
+            assertWithMessage(testFile.mMetadata.path).that(actualExcluded).isEqualTo(
+                    expectedExcluded);
+        }
+    }
+
+    private static class TestFile {
+        private final FileMetadata mMetadata;
+        private boolean mShouldSkip;
+
+        static TestFile dir(String path) {
+            return new TestFile(path, BackupAgent.TYPE_DIRECTORY);
+        }
+
+        static TestFile file(String path) {
+            return new TestFile(path, BackupAgent.TYPE_FILE);
+        }
+
+        TestFile markReadOnly() {
+            mMetadata.mode = 0;
+            return this;
+        }
+
+        TestFile expectSkipped() {
+            mShouldSkip = true;
+            return this;
+        }
+
+        TestFile setPackage(String packageName) {
+            mMetadata.packageName = packageName;
+            return this;
+        }
+
+        TestFile setDomain(String domain) {
+            mMetadata.domain = domain;
+            return this;
+        }
+
+        private TestFile(String path, int type) {
+            FileMetadata metadata = new FileMetadata();
+            metadata.path = path;
+            metadata.type = type;
+            metadata.packageName = DEFAULT_PACKAGE_NAME;
+            metadata.domain = DEFAULT_DOMAIN_NAME;
+            metadata.mode = OsConstants.S_IWUSR; // Mark as writable.
+            mMetadata = metadata;
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java b/services/tests/servicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java
index b154d6f..1171518 100644
--- a/services/tests/servicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java
@@ -110,10 +110,7 @@
 
         Thread thread = new Thread(() -> {
             try {
-                /*String name =*/ client.transportDirName();
-                fail("transportDirName should be cancelled");
-            } catch (CancellationException ex) {
-                // This is expected.
+                assertThat(client.transportDirName()).isNull();
             } catch (Exception ex) {
                 fail("unexpected Exception: " + ex.getClass().getCanonicalName());
             }
@@ -189,7 +186,7 @@
     }
 
     @Test
-    public void testFinishBackup_canceledBeforeCompletion_throwsException() throws Exception {
+    public void testFinishBackup_canceledBeforeCompletion_returnsError() throws Exception {
         TestCallbacksFakeTransportBinder binder = new TestCallbacksFakeTransportBinder();
         BackupTransportClient client = new BackupTransportClient(binder);
 
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 2ad5eae..85d8aba 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -192,7 +192,7 @@
         waitForIdle();
 
         assertNull(mBiometricService.mAuthSession);
-        verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();
+        verify(mBiometricService.mStatusBarService).hideAuthenticationDialog(eq(TEST_REQUEST_ID));
         verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
     }
 
@@ -211,7 +211,8 @@
         waitForIdle();
 
         assertNotNull(mBiometricService.mAuthSession);
-        verify(mBiometricService.mStatusBarService, never()).hideAuthenticationDialog();
+        verify(mBiometricService.mStatusBarService, never())
+                .hideAuthenticationDialog(eq(TEST_REQUEST_ID));
         assertEquals(STATE_CLIENT_DIED_CANCELLING,
                 mBiometricService.mAuthSession.getState());
 
@@ -225,7 +226,7 @@
                 BiometricConstants.BIOMETRIC_ERROR_CANCELED,
                 0 /* vendorCode */);
         waitForIdle();
-        verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();
+        verify(mBiometricService.mStatusBarService).hideAuthenticationDialog(eq(TEST_REQUEST_ID));
         verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
         assertNull(mBiometricService.mAuthSession);
     }
@@ -666,7 +667,7 @@
                 eq(BiometricAuthenticator.TYPE_FACE),
                 eq(BiometricPrompt.BIOMETRIC_ERROR_CANCELED),
                 eq(0) /* vendorCode */);
-        verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();
+        verify(mBiometricService.mStatusBarService).hideAuthenticationDialog(eq(TEST_REQUEST_ID));
 
         verify(mReceiver2, never()).onError(anyInt(), anyInt(), anyInt());
     }
@@ -745,7 +746,7 @@
                 eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED),
                 eq(0 /* vendorCode */));
         // Dialog is hidden immediately
-        verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();
+        verify(mBiometricService.mStatusBarService).hideAuthenticationDialog(eq(TEST_REQUEST_ID));
         // Auth session is over
         assertNull(mBiometricService.mAuthSession);
     }
@@ -773,7 +774,8 @@
                 eq(TYPE_FINGERPRINT),
                 eq(BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS),
                 eq(0 /* vendorCode */));
-        verify(mBiometricService.mStatusBarService, never()).hideAuthenticationDialog();
+        verify(mBiometricService.mStatusBarService, never())
+                .hideAuthenticationDialog(eq(TEST_REQUEST_ID));
         verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
 
         // SystemUI animation completed, client is notified, auth session is over
@@ -1152,7 +1154,7 @@
         verify(mReceiver1).onError(eq(TYPE_FINGERPRINT),
                 eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED),
                 eq(0 /* vendorCode */));
-        verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();
+        verify(mBiometricService.mStatusBarService).hideAuthenticationDialog(eq(TEST_REQUEST_ID));
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
index fe023374..0b8e8ad 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
@@ -23,6 +23,7 @@
 import static org.mockito.Mockito.anyLong;
 import static org.mockito.Mockito.eq;
 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;
@@ -250,4 +251,18 @@
         callback.onClientFinished(mClient, true /* success */);
         verify(mSensorManager, never()).registerListener(any(), any(), anyInt());
     }
+
+    @Test
+    public void testALSCallbackDestroyed() {
+        mLogger = createLogger();
+        final CallbackWithProbe<Probe> callback =
+                mLogger.createALSCallback(true /* startWithClient */);
+
+        callback.onClientStarted(mClient);
+        callback.onClientFinished(mClient, false /* success */);
+
+        reset(mSensorManager);
+        callback.getProbe().enable();
+        verify(mSensorManager, never()).registerListener(any(), any(), anyInt());
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index 6c50ca3..7463022 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -31,9 +31,11 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.common.OperationContext;
 import android.hardware.biometrics.fingerprint.ISession;
 import android.hardware.biometrics.fingerprint.PointerContext;
+import android.hardware.fingerprint.Fingerprint;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.ISidefpsController;
 import android.hardware.fingerprint.IUdfpsOverlayController;
@@ -63,12 +65,14 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
+import java.util.ArrayList;
 import java.util.function.Consumer;
 
 @Presubmit
 @SmallTest
 public class FingerprintAuthenticationClientTest {
 
+    private static final int SENSOR_ID = 4;
     private static final int USER_ID = 8;
     private static final long OP_ID = 7;
     private static final long REQUEST_ID = 88;
@@ -93,6 +97,8 @@
     @Mock
     private BiometricContext mBiometricContext;
     @Mock
+    private BiometricManager mBiometricManager;
+    @Mock
     private LockoutCache mLockoutCache;
     @Mock
     private IUdfpsOverlayController mUdfpsOverlayController;
@@ -118,6 +124,7 @@
 
     @Before
     public void setup() {
+        mContext.addMockSystemService(BiometricManager.class, mBiometricManager);
         when(mBiometricLogger.createALSCallback(anyBoolean())).thenAnswer(i ->
                 new CallbackWithProbe<>(mLuxProbe, i.getArgument(0)));
         when(mBiometricContext.updateContext(any(), anyBoolean())).thenAnswer(
@@ -212,6 +219,18 @@
     }
 
     @Test
+    public void luxProbeWhenFingerDown_unlessDestroyed() throws RemoteException {
+        final FingerprintAuthenticationClient client = createClient();
+        client.start(mCallback);
+        client.onAuthenticated(new Fingerprint("name", 2 /* enrollmentId */, SENSOR_ID),
+                true /* authenticated */, new ArrayList<>());
+        verify(mLuxProbe).destroy();
+
+        client.onAcquired(2, 0);
+        verify(mLuxProbe, never()).enable();
+    }
+
+    @Test
     public void notifyHalWhenContextChanges() throws RemoteException {
         final FingerprintAuthenticationClient client = createClient();
         client.start(mCallback);
@@ -284,6 +303,6 @@
         true /* isStrongBiometric */,
         null /* taskStackListener */, mLockoutCache,
         mUdfpsOverlayController, mSideFpsController,
-        false /* allowBackgroundAuthentication */, mSensorProps);
+        true /* allowBackgroundAuthentication */, mSensorProps);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index 40c0392..864f315 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -20,8 +20,8 @@
 import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_HIGH_DISPLAY_BRIGHTNESS_THRESHOLDS;
 import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_LOW_AMBIENT_BRIGHTNESS_THRESHOLDS;
 import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_LOW_DISPLAY_BRIGHTNESS_THRESHOLDS;
-import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_SUNLIGHT;
 import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_HDR;
+import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_SUNLIGHT;
 import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE;
 import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE;
 
@@ -919,16 +919,6 @@
         // Should be no vote initially
         Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_UDFPS);
         assertNull(vote);
-
-        // Enabling GHBM votes for 60hz
-        hbmListener.onHbmEnabled(IUdfpsHbmListener.GLOBAL_HBM, DISPLAY_ID);
-        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_UDFPS);
-        assertVoteForRefreshRate(vote, 60.f);
-
-        // Disabling GHBM removes the vote
-        hbmListener.onHbmDisabled(IUdfpsHbmListener.GLOBAL_HBM, DISPLAY_ID);
-        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_UDFPS);
-        assertNull(vote);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
index 18f2642..112db76 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
@@ -61,7 +61,8 @@
     public void setUp() throws Exception {
         mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
 
-        mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList()) {
+        mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList(),
+                new FakeAudioDeviceVolumeManagerWrapper()) {
             @Override
             AudioManager getAudioManager() {
                 return new AudioManager() {
@@ -93,6 +94,7 @@
         mHdmiControlService.setCecController(hdmiCecController);
         mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
         mHdmiControlService.initService();
+        mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
         mPowerManager = new FakePowerManagerWrapper(mContextSpy);
         mHdmiControlService.setPowerManager(mPowerManager);
         mPhysicalAddress = 0x2000;
@@ -153,7 +155,6 @@
                 mHdmiControlService);
         audioDevice.init();
         mLocalDevices.add(audioDevice);
-        mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
         mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
         mTestLooper.dispatchAll();
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
index e4c5ad67..e4eecc6 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
@@ -68,7 +68,8 @@
         mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
 
         HdmiControlService hdmiControlService =
-                new HdmiControlService(mContextSpy, Collections.emptyList()) {
+                new HdmiControlService(mContextSpy, Collections.emptyList(),
+                        new FakeAudioDeviceVolumeManagerWrapper()) {
                     @Override
                     boolean isPowerStandby() {
                         return false;
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
index d73cdb5..5b11466 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
@@ -68,7 +68,8 @@
         mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
 
         HdmiControlService hdmiControlService =
-                new HdmiControlService(mContextSpy, Collections.emptyList()) {
+                new HdmiControlService(mContextSpy, Collections.emptyList(),
+                        new FakeAudioDeviceVolumeManagerWrapper()) {
                     @Override
                     AudioManager getAudioManager() {
                         return mAudioManager;
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeControlTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeControlTest.java
new file mode 100644
index 0000000..e06877f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeControlTest.java
@@ -0,0 +1,570 @@
+/*
+ * 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.server.hdmi;
+
+import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM;
+
+import static com.android.server.hdmi.HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP;
+import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_BOOT_UP;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.TruthJUnit.assume;
+
+import static org.mockito.AdditionalMatchers.not;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.hardware.hdmi.DeviceFeatures;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.hardware.hdmi.HdmiPortInfo;
+import android.hardware.tv.cec.V1_0.SendMessageResult;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceVolumeManager;
+import android.media.AudioManager;
+import android.media.VolumeInfo;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.test.TestLooper;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.server.SystemService;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+
+/**
+ * Tests that Absolute Volume Control (AVC) is enabled and disabled correctly, and that
+ * the device responds correctly to incoming <Report Audio Status> messages and API calls
+ * from AudioService when AVC is active.
+ *
+ * This is an abstract base class. Concrete subclasses specify the type of the local device, and the
+ * type of the System Audio device. This allows each test to be run for multiple setups.
+ *
+ * We test the following pairs of (local device, System Audio device):
+ * (Playback, TV): {@link PlaybackDeviceToTvAvcTest}
+ * (Playback, Audio System): {@link PlaybackDeviceToAudioSystemAvcTest}
+ * (TV, Audio System): {@link TvToAudioSystemAvcTest}
+ */
+public abstract class BaseAbsoluteVolumeControlTest {
+    private HdmiControlService mHdmiControlService;
+    private HdmiCecController mHdmiCecController;
+    private HdmiCecLocalDevice mHdmiCecLocalDevice;
+    private FakeHdmiCecConfig mHdmiCecConfig;
+    private FakePowerManagerWrapper mPowerManager;
+    private Looper mLooper;
+    private Context mContextSpy;
+    private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
+
+    @Mock protected AudioManager mAudioManager;
+    protected FakeAudioDeviceVolumeManagerWrapper mAudioDeviceVolumeManager;
+
+    protected TestLooper mTestLooper = new TestLooper();
+    protected FakeNativeWrapper mNativeWrapper;
+
+    // Audio Status given by the System Audio device in its initial <Report Audio Status> that
+    // triggers AVC being enabled
+    private static final AudioStatus INITIAL_SYSTEM_AUDIO_DEVICE_STATUS =
+            new AudioStatus(50, false);
+
+    // VolumeInfo passed to AudioDeviceVolumeManager#setDeviceAbsoluteVolumeBehavior to enable AVC
+    private static final VolumeInfo ENABLE_AVC_VOLUME_INFO =
+            new VolumeInfo.Builder(AudioManager.STREAM_MUSIC)
+                    .setMuted(INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getMute())
+                    .setVolumeIndex(INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getVolume())
+                    .setMaxVolumeIndex(AudioStatus.MAX_VOLUME)
+                    .setMinVolumeIndex(AudioStatus.MIN_VOLUME)
+                    .build();
+
+    protected abstract HdmiCecLocalDevice createLocalDevice(HdmiControlService hdmiControlService);
+
+    protected abstract int getPhysicalAddress();
+    protected abstract int getDeviceType();
+    protected abstract AudioDeviceAttributes getAudioOutputDevice();
+
+    protected abstract int getSystemAudioDeviceLogicalAddress();
+    protected abstract int getSystemAudioDeviceType();
+
+    @Before
+    public void setUp() throws RemoteException {
+        MockitoAnnotations.initMocks(this);
+
+        mContextSpy = spy(new ContextWrapper(
+                InstrumentationRegistry.getInstrumentation().getTargetContext()));
+
+        mAudioDeviceVolumeManager = spy(new FakeAudioDeviceVolumeManagerWrapper());
+
+        mHdmiControlService =
+                new HdmiControlService(InstrumentationRegistry.getTargetContext(),
+                        Collections.singletonList(getDeviceType()),
+                        mAudioDeviceVolumeManager) {
+                    @Override
+                    AudioManager getAudioManager() {
+                        return mAudioManager;
+                    }
+
+                    @Override
+                    protected void writeStringSystemProperty(String key, String value) {
+                        // do nothing
+                    }
+                };
+
+        mLooper = mTestLooper.getLooper();
+        mHdmiControlService.setIoLooper(mLooper);
+
+        mHdmiCecConfig = new FakeHdmiCecConfig(mContextSpy);
+        mHdmiCecConfig.setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
+                HdmiControlManager.HDMI_CEC_VERSION_2_0);
+        mHdmiCecConfig.setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE,
+                HdmiControlManager.VOLUME_CONTROL_DISABLED);
+        mHdmiControlService.setHdmiCecConfig(mHdmiCecConfig);
+
+        mNativeWrapper = new FakeNativeWrapper();
+        mNativeWrapper.setPhysicalAddress(getPhysicalAddress());
+        mNativeWrapper.setPollAddressResponse(Constants.ADDR_TV, SendMessageResult.SUCCESS);
+
+        mHdmiCecController = HdmiCecController.createWithNativeWrapper(
+                mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
+        mHdmiControlService.setCecController(mHdmiCecController);
+        mHdmiControlService.setHdmiMhlController(
+                HdmiMhlControllerStub.create(mHdmiControlService));
+        mHdmiControlService.initService();
+        mPowerManager = new FakePowerManagerWrapper(mContextSpy);
+        mHdmiControlService.setPowerManager(mPowerManager);
+
+        mHdmiCecLocalDevice = createLocalDevice(mHdmiControlService);
+        mHdmiCecLocalDevice.init();
+        mLocalDevices.add(mHdmiCecLocalDevice);
+
+        HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1];
+        hdmiPortInfos[0] =
+                new HdmiPortInfo(1, HdmiPortInfo.PORT_OUTPUT, 0x0000, true, false, false);
+        mNativeWrapper.setPortInfo(hdmiPortInfos);
+        mNativeWrapper.setPortConnectionStatus(1, true);
+
+        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_BOOT_UP);
+        mTestLooper.dispatchAll();
+
+        // Simulate AudioManager's behavior and response when setDeviceVolumeBehavior is called
+        doAnswer(invocation -> {
+            setDeviceVolumeBehavior(invocation.getArgument(0), invocation.getArgument(1));
+            return null;
+        }).when(mAudioManager).setDeviceVolumeBehavior(any(), anyInt());
+
+        // Set starting volume behavior
+        doReturn(AudioManager.DEVICE_VOLUME_BEHAVIOR_VARIABLE)
+                .when(mAudioManager).getDeviceVolumeBehavior(eq(getAudioOutputDevice()));
+
+        // Audio service always plays STREAM_MUSIC on the device we need
+        doReturn(Collections.singletonList(getAudioOutputDevice())).when(mAudioManager)
+                .getDevicesForAttributes(HdmiControlService.STREAM_MUSIC_ATTRIBUTES);
+
+        // Max volume of STREAM_MUSIC
+        doReturn(25).when(mAudioManager).getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+
+        // Receive messages from devices to make sure they're registered in HdmiCecNetwork
+        mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+                Constants.ADDR_TV, getLogicalAddress()));
+        if (getSystemAudioDeviceType() == DEVICE_AUDIO_SYSTEM) {
+            mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+                    Constants.ADDR_AUDIO_SYSTEM, getLogicalAddress()));
+        }
+
+        mHdmiControlService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
+        mHdmiControlService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+        mTestLooper.dispatchAll();
+    }
+
+    protected int getLogicalAddress() {
+        synchronized (mHdmiCecLocalDevice.mLock) {
+            return mHdmiCecLocalDevice.getDeviceInfo().getLogicalAddress();
+        }
+    }
+
+    /**
+     * Simulates the volume behavior of {@code device} being set to {@code behavior}.
+     */
+    protected void setDeviceVolumeBehavior(AudioDeviceAttributes device,
+            @AudioManager.DeviceVolumeBehavior int behavior) {
+        doReturn(behavior).when(mAudioManager).getDeviceVolumeBehavior(eq(device));
+        mHdmiControlService.onDeviceVolumeBehaviorChanged(device, behavior);
+        mTestLooper.dispatchAll();
+    }
+
+    /**
+     * Changes the setting for CEC volume.
+     */
+    protected void setCecVolumeControlSetting(@HdmiControlManager.VolumeControl int setting) {
+        mHdmiControlService.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE, setting);
+        mTestLooper.dispatchAll();
+    }
+
+    /**
+     * Has the device receive a <Report Features> message from the System Audio device specifying
+     * whether <Set Audio Volume Level> is supported or not.
+     */
+    protected void receiveSetAudioVolumeLevelSupport(
+            @DeviceFeatures.FeatureSupportStatus int featureSupportStatus) {
+        // <Report Features> can't specify an unknown feature support status
+        if (featureSupportStatus != DeviceFeatures.FEATURE_SUPPORT_UNKNOWN) {
+            mNativeWrapper.onCecMessage(ReportFeaturesMessage.build(
+                    getSystemAudioDeviceLogicalAddress(), HdmiControlManager.HDMI_CEC_VERSION_2_0,
+                    Arrays.asList(getSystemAudioDeviceType()), Constants.RC_PROFILE_SOURCE,
+                    Collections.emptyList(),
+                    DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder()
+                            .setSetAudioVolumeLevelSupport(featureSupportStatus)
+                            .build()));
+            mTestLooper.dispatchAll();
+        }
+    }
+
+    /**
+     * Enables System Audio mode if the System Audio device is an Audio System.
+     */
+    protected void enableSystemAudioModeIfNeeded() {
+        if (getSystemAudioDeviceType() == DEVICE_AUDIO_SYSTEM) {
+            receiveSetSystemAudioMode(true);
+        }
+    }
+
+    /**
+     * Sets System Audio Mode by having the device receive <Set System Audio Mode>
+     * from the Audio System.
+     */
+    protected void receiveSetSystemAudioMode(boolean status) {
+        mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildSetSystemAudioMode(
+                Constants.ADDR_AUDIO_SYSTEM, Constants.ADDR_BROADCAST, status));
+        mTestLooper.dispatchAll();
+    }
+
+    /**
+     * Has the device receive a <Report Audio Status> reporting the status in
+     * {@link #INITIAL_SYSTEM_AUDIO_DEVICE_STATUS}
+     */
+    protected void receiveInitialReportAudioStatus() {
+        receiveReportAudioStatus(
+                INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getVolume(),
+                INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getMute());
+    }
+
+    /**
+     * Has the device receive a <Report Audio Status> message from the System Audio Device.
+     */
+    protected void receiveReportAudioStatus(int volume, boolean mute) {
+        mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildReportAudioStatus(
+                getSystemAudioDeviceLogicalAddress(),
+                getLogicalAddress(),
+                volume,
+                mute));
+        mTestLooper.dispatchAll();
+    }
+
+    /**
+     * Triggers all the conditions required to enable Absolute Volume Control.
+     */
+    protected void enableAbsoluteVolumeControl() {
+        setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+        setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
+        receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED);
+        enableSystemAudioModeIfNeeded();
+        receiveInitialReportAudioStatus();
+
+        verifyAbsoluteVolumeEnabled();
+    }
+
+    /**
+     * Verifies that AVC was enabled - that is the audio output device's volume behavior was last
+     * set to absolute volume behavior.
+     */
+    protected void verifyAbsoluteVolumeEnabled() {
+        InOrder inOrder = inOrder(mAudioManager, mAudioDeviceVolumeManager);
+        inOrder.verify(mAudioDeviceVolumeManager, atLeastOnce()).setDeviceAbsoluteVolumeBehavior(
+                eq(getAudioOutputDevice()), any(), any(), any(), anyBoolean());
+        inOrder.verify(mAudioManager, never()).setDeviceVolumeBehavior(
+                eq(getAudioOutputDevice()), not(eq(AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE)));
+    }
+
+    /**
+     * Verifies that AVC was disabled - that is, the audio output device's volume behavior was
+     * last set to something other than absolute volume behavior.
+     */
+    protected void verifyAbsoluteVolumeDisabled() {
+        InOrder inOrder = inOrder(mAudioManager, mAudioDeviceVolumeManager);
+        inOrder.verify(mAudioManager, atLeastOnce()).setDeviceVolumeBehavior(
+                eq(getAudioOutputDevice()), not(eq(AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE)));
+        inOrder.verify(mAudioDeviceVolumeManager, never()).setDeviceAbsoluteVolumeBehavior(
+                eq(getAudioOutputDevice()), any(), any(), any(), anyBoolean());
+    }
+
+    protected void verifyGiveAudioStatusNeverSent() {
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(
+                HdmiCecMessageBuilder.buildGiveAudioStatus(
+                        getLogicalAddress(), getSystemAudioDeviceLogicalAddress()));
+    }
+
+    protected void verifyGiveAudioStatusSent() {
+        assertThat(mNativeWrapper.getResultMessages()).contains(
+                HdmiCecMessageBuilder.buildGiveAudioStatus(
+                        getLogicalAddress(), getSystemAudioDeviceLogicalAddress()));
+    }
+
+    @Test
+    public void allConditionsExceptSavlSupportMet_sendsSetAudioVolumeLevelAndGiveFeatures() {
+        setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+        setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
+        enableSystemAudioModeIfNeeded();
+
+        assertThat(mNativeWrapper.getResultMessages()).contains(
+                SetAudioVolumeLevelMessage.build(
+                        getLogicalAddress(), getSystemAudioDeviceLogicalAddress(),
+                        Constants.AUDIO_VOLUME_STATUS_UNKNOWN));
+        assertThat(mNativeWrapper.getResultMessages()).contains(
+                HdmiCecMessageBuilder.buildGiveFeatures(
+                        getLogicalAddress(), getSystemAudioDeviceLogicalAddress()));
+    }
+
+    @Test
+    public void allConditionsMet_savlSupportLast_reportFeatures_giveAudioStatusSent() {
+        setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+        setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
+        enableSystemAudioModeIfNeeded();
+        verifyGiveAudioStatusNeverSent();
+
+        receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED);
+        verifyGiveAudioStatusSent();
+    }
+
+    @Test
+    public void allConditionsMet_savlSupportLast_noFeatureAbort_giveAudioStatusSent() {
+        setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+        setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
+        enableSystemAudioModeIfNeeded();
+        verifyGiveAudioStatusNeverSent();
+
+        mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+        verifyGiveAudioStatusSent();
+    }
+
+    @Test
+    public void allConditionsMet_cecVolumeEnabledLast_giveAudioStatusSent() {
+        setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+        enableSystemAudioModeIfNeeded();
+        receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED);
+        verifyGiveAudioStatusNeverSent();
+
+        setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
+        verifyGiveAudioStatusSent();
+    }
+
+    @Test
+    public void allConditionsMet_fullVolumeBehaviorLast_giveAudioStatusSent() {
+        setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
+        enableSystemAudioModeIfNeeded();
+        receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED);
+        verifyGiveAudioStatusNeverSent();
+
+        setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+        verifyGiveAudioStatusSent();
+    }
+
+    @Test
+    public void allConditionsMet_systemAudioModeEnabledLast_giveAudioStatusSent() {
+        // Only run when the System Audio device is an Audio System.
+        assume().that(getSystemAudioDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+
+        setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+        setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
+        receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED);
+        verifyGiveAudioStatusNeverSent();
+
+        receiveSetSystemAudioMode(true);
+        verifyGiveAudioStatusSent();
+    }
+
+    @Test
+    public void giveAudioStatusSent_systemAudioDeviceSendsReportAudioStatus_avcEnabled() {
+        setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
+        enableSystemAudioModeIfNeeded();
+        receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED);
+        setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+
+        // Verify that AVC was never enabled
+        verify(mAudioDeviceVolumeManager, never()).setDeviceAbsoluteVolumeBehavior(
+                eq(getAudioOutputDevice()), any(), any(), any(), anyBoolean());
+        receiveInitialReportAudioStatus();
+
+        verifyAbsoluteVolumeEnabled();
+    }
+
+    @Test
+    public void avcEnabled_cecVolumeDisabled_absoluteVolumeDisabled() {
+        enableAbsoluteVolumeControl();
+
+        setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_DISABLED);
+        verifyAbsoluteVolumeDisabled();
+    }
+
+    @Test
+    public void avcEnabled_setAudioVolumeLevelNotSupported_absoluteVolumeDisabled() {
+        enableAbsoluteVolumeControl();
+
+        receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_NOT_SUPPORTED);
+        verifyAbsoluteVolumeDisabled();
+    }
+
+    @Test
+    public void avcEnabled_setAudioVolumeLevelFeatureAborted_absoluteVolumeDisabled() {
+        enableAbsoluteVolumeControl();
+
+        mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildFeatureAbortCommand(
+                getSystemAudioDeviceLogicalAddress(), getLogicalAddress(),
+                Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL, Constants.ABORT_UNRECOGNIZED_OPCODE));
+        mTestLooper.dispatchAll();
+        verifyAbsoluteVolumeDisabled();
+    }
+
+    @Test
+    public void avcEnabled_systemAudioModeDisabled_absoluteVolumeDisabled() {
+        // Only run when the System Audio device is an Audio System.
+        assume().that(getSystemAudioDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+
+        enableAbsoluteVolumeControl();
+
+        receiveSetSystemAudioMode(false);
+        verifyAbsoluteVolumeDisabled();
+    }
+
+    @Test
+    public void avcEnabled_receiveReportAudioStatus_notifiesVolumeOrMuteChanges() {
+        // Initial <Report Audio Status> has volume=50 and mute=false
+        enableAbsoluteVolumeControl();
+
+        // New volume and mute status: sets both
+        receiveReportAudioStatus(20, true);
+        verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(5),
+                anyInt());
+        verify(mAudioManager).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
+                eq(AudioManager.ADJUST_MUTE), anyInt());
+        clearInvocations(mAudioManager);
+
+        // New volume only: sets volume only
+        receiveReportAudioStatus(32, true);
+        verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
+                anyInt());
+        verify(mAudioManager, never()).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
+                eq(AudioManager.ADJUST_MUTE), anyInt());
+        clearInvocations(mAudioManager);
+
+        // New mute status only: sets mute only
+        receiveReportAudioStatus(32, false);
+        verify(mAudioManager, never()).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
+                anyInt());
+        verify(mAudioManager).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
+                eq(AudioManager.ADJUST_UNMUTE), anyInt());
+        clearInvocations(mAudioManager);
+
+        // Repeat of earlier message: sets neither volume nor mute
+        receiveReportAudioStatus(32, false);
+        verify(mAudioManager, never()).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
+                anyInt());
+        verify(mAudioManager, never()).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
+                eq(AudioManager.ADJUST_UNMUTE), anyInt());
+        clearInvocations(mAudioManager);
+
+        // If AudioService causes us to send <Set Audio Volume Level>, the System Audio device's
+        // volume changes. Afterward, a duplicate of an earlier <Report Audio Status> should
+        // still cause us to call setStreamVolume()
+        mHdmiControlService.getAbsoluteVolumeChangedListener().onAudioDeviceVolumeChanged(
+                getAudioOutputDevice(),
+                new VolumeInfo.Builder(ENABLE_AVC_VOLUME_INFO)
+                        .setVolumeIndex(20)
+                        .build()
+        );
+        mTestLooper.dispatchAll();
+        receiveReportAudioStatus(32, false);
+        verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
+                anyInt());
+        verify(mAudioManager, never()).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
+                eq(AudioManager.ADJUST_UNMUTE), anyInt());
+    }
+
+    @Test
+    public void avcEnabled_audioDeviceVolumeAdjusted_sendsUserControlPressedAndGiveAudioStatus() {
+        enableAbsoluteVolumeControl();
+        mNativeWrapper.clearResultMessages();
+
+        mHdmiControlService.getAbsoluteVolumeChangedListener().onAudioDeviceVolumeAdjusted(
+                getAudioOutputDevice(),
+                ENABLE_AVC_VOLUME_INFO,
+                AudioManager.ADJUST_RAISE,
+                AudioDeviceVolumeManager.ADJUST_MODE_NORMAL
+        );
+        mTestLooper.dispatchAll();
+
+        assertThat(mNativeWrapper.getResultMessages()).contains(
+                HdmiCecMessageBuilder.buildUserControlPressed(getLogicalAddress(),
+                        getSystemAudioDeviceLogicalAddress(), CEC_KEYCODE_VOLUME_UP));
+        assertThat(mNativeWrapper.getResultMessages()).contains(
+                HdmiCecMessageBuilder.buildUserControlReleased(getLogicalAddress(),
+                        getSystemAudioDeviceLogicalAddress()));
+        assertThat(mNativeWrapper.getResultMessages()).contains(
+                HdmiCecMessageBuilder.buildGiveAudioStatus(getLogicalAddress(),
+                        getSystemAudioDeviceLogicalAddress()));
+    }
+
+    @Test
+    public void avcEnabled_audioDeviceVolumeChanged_sendsSetAudioVolumeLevel() {
+        enableAbsoluteVolumeControl();
+        mNativeWrapper.clearResultMessages();
+
+        mHdmiControlService.getAbsoluteVolumeChangedListener().onAudioDeviceVolumeChanged(
+                getAudioOutputDevice(),
+                new VolumeInfo.Builder(ENABLE_AVC_VOLUME_INFO)
+                        .setVolumeIndex(20)
+                        .build()
+        );
+        mTestLooper.dispatchAll();
+
+        assertThat(mNativeWrapper.getResultMessages()).contains(
+                SetAudioVolumeLevelMessage.build(getLogicalAddress(),
+                        getSystemAudioDeviceLogicalAddress(), 20));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java
index 5cec8ad..28ba4bb 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java
@@ -56,7 +56,7 @@
         mDeviceInfoForTests = HdmiDeviceInfo.hardwarePort(1001, 1234);
         HdmiControlService hdmiControlService =
                 new HdmiControlService(InstrumentationRegistry.getTargetContext(),
-                        Collections.emptyList()) {
+                        Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
 
                     @Override
                     void sendCecCommand(
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
index 52a0b6c..545f318 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.hdmi;
 
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
 import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
 import static com.android.server.hdmi.Constants.ADDR_TV;
 import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
@@ -78,7 +79,8 @@
 
         mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
 
-        mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList()) {
+        mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList(),
+                new FakeAudioDeviceVolumeManagerWrapper()) {
             @Override
             AudioManager getAudioManager() {
                 return new AudioManager() {
@@ -110,6 +112,7 @@
         mHdmiControlService.setCecController(hdmiCecController);
         mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
         mHdmiControlService.initService();
+        mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
         mPowerManager = new FakePowerManagerWrapper(mContextSpy);
         mHdmiControlService.setPowerManager(mPowerManager);
         mPhysicalAddress = 0x2000;
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
index 35432ed..d7fef90 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
@@ -20,6 +20,7 @@
 import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_STANDBY;
 import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
 
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
 import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
 import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2;
 import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_3;
@@ -100,7 +101,7 @@
 
         mHdmiControlService =
                 new HdmiControlService(InstrumentationRegistry.getTargetContext(),
-                        Collections.emptyList()) {
+                        Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
                     @Override
                     boolean isControlEnabled() {
                         return true;
@@ -136,6 +137,7 @@
 
         mLocalDevices.add(mHdmiCecLocalDevicePlayback);
         mHdmiControlService.initService();
+        mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
         mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
         mNativeWrapper.setPhysicalAddress(0x0000);
         mPowerManager = new FakePowerManagerWrapper(context);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
index e77cd91..72d36b0 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
@@ -20,6 +20,7 @@
 import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_STANDBY;
 import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
 
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
 import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
 import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2;
 import static com.android.server.hdmi.Constants.ADDR_TV;
@@ -109,7 +110,7 @@
 
         mHdmiControlService =
                 new HdmiControlService(InstrumentationRegistry.getTargetContext(),
-                        Collections.emptyList()) {
+                        Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
                     @Override
                     boolean isControlEnabled() {
                         return true;
@@ -145,6 +146,7 @@
                                  true, false, false);
         mNativeWrapper.setPortInfo(hdmiPortInfos);
         mHdmiControlService.initService();
+        mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
         mPowerManager = new FakePowerManagerWrapper(context);
         mHdmiControlService.setPowerManager(mPowerManager);
         mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeAudioDeviceVolumeManagerWrapper.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeAudioDeviceVolumeManagerWrapper.java
new file mode 100644
index 0000000..d33ef9b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeAudioDeviceVolumeManagerWrapper.java
@@ -0,0 +1,72 @@
+/*
+ * 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.hdmi;
+
+import static android.media.AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener;
+import static android.media.AudioDeviceVolumeManager.OnDeviceVolumeBehaviorChangedListener;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceVolumeManager;
+import android.media.AudioManager;
+import android.media.VolumeInfo;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.Executor;
+
+/**
+ * Wrapper for {@link AudioDeviceVolumeManager} that stubs its methods. Useful for testing.
+ */
+public class FakeAudioDeviceVolumeManagerWrapper implements
+        AudioDeviceVolumeManagerWrapperInterface {
+
+    private final Set<OnDeviceVolumeBehaviorChangedListener> mVolumeBehaviorListeners;
+
+    public FakeAudioDeviceVolumeManagerWrapper() {
+        mVolumeBehaviorListeners = new HashSet<>();
+    }
+
+    @Override
+    public void addOnDeviceVolumeBehaviorChangedListener(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OnDeviceVolumeBehaviorChangedListener listener)
+            throws SecurityException {
+        mVolumeBehaviorListeners.add(listener);
+    }
+
+    @Override
+    public void removeOnDeviceVolumeBehaviorChangedListener(
+            @NonNull OnDeviceVolumeBehaviorChangedListener listener) {
+        mVolumeBehaviorListeners.remove(listener);
+    }
+
+    @Override
+    public void setDeviceAbsoluteVolumeBehavior(
+            @NonNull AudioDeviceAttributes device,
+            @NonNull VolumeInfo volume,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OnAudioDeviceVolumeChangedListener vclistener,
+            boolean handlesVolumeAdjustment) {
+        // Notify all volume behavior listeners that the device adopted absolute volume behavior
+        for (OnDeviceVolumeBehaviorChangedListener listener : mVolumeBehaviorListeners) {
+            listener.onDeviceVolumeBehaviorChanged(device,
+                    AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
index 30bcc7e..9f744f9 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
@@ -89,7 +89,8 @@
         mContextSpy = spy(new ContextWrapper(
                 InstrumentationRegistry.getInstrumentation().getTargetContext()));
 
-        mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList()));
+        mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList(),
+                new FakeAudioDeviceVolumeManagerWrapper()));
         doNothing().when(mHdmiControlServiceSpy)
                 .writeStringSystemProperty(anyString(), anyString());
         doReturn(mHdmiCecAtomWriterSpy).when(mHdmiControlServiceSpy).getAtomWriter();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java
index 2dcc449..0cba106 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java
@@ -101,7 +101,8 @@
         mMyLooper = mTestLooper.getLooper();
 
         mHdmiControlServiceSpy = spy(new HdmiControlService(
-                InstrumentationRegistry.getTargetContext(), Collections.emptyList()));
+                InstrumentationRegistry.getTargetContext(), Collections.emptyList(),
+                new FakeAudioDeviceVolumeManagerWrapper()));
         doReturn(mMyLooper).when(mHdmiControlServiceSpy).getIoLooper();
         doReturn(mMyLooper).when(mHdmiControlServiceSpy).getServiceLooper();
         doAnswer(__ -> mCecVersion).when(mHdmiControlServiceSpy).getCecVersion();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
index 70bc460..91d265c 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -88,7 +88,7 @@
 
         mHdmiControlService =
             new HdmiControlService(InstrumentationRegistry.getTargetContext(),
-                    Collections.emptyList()) {
+                    Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
                 @Override
                 AudioManager getAudioManager() {
                     return new AudioManager() {
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index 86130da..484b5a8 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.hdmi;
 
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
 import static com.android.server.hdmi.Constants.ABORT_UNRECOGNIZED_OPCODE;
 import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
 import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
@@ -92,7 +93,7 @@
 
         mHdmiControlService =
                 new HdmiControlService(InstrumentationRegistry.getTargetContext(),
-                        Collections.emptyList()) {
+                        Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
                     @Override
                     void wakeUp() {
                         mWokenUp = true;
@@ -151,6 +152,7 @@
         mNativeWrapper.setPortInfo(hdmiPortInfos);
         mNativeWrapper.setPortConnectionStatus(1, true);
         mHdmiControlService.initService();
+        mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
         mPowerManager = new FakePowerManagerWrapper(context);
         mHdmiControlService.setPowerManager(mPowerManager);
         mHdmiControlService.setPowerManagerInternal(mPowerManagerInternal);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
index fb8baa3..48e70fe 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
@@ -17,6 +17,7 @@
 
 import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_TV;
 
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
 import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
 import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
 import static com.android.server.hdmi.Constants.ADDR_TV;
@@ -137,7 +138,8 @@
         Context context = InstrumentationRegistry.getTargetContext();
 
         mHdmiControlService =
-                new HdmiControlService(context, Collections.emptyList()) {
+                new HdmiControlService(context, Collections.emptyList(),
+                        new FakeAudioDeviceVolumeManagerWrapper()) {
                     @Override
                     boolean isControlEnabled() {
                         return isControlEnabled;
@@ -190,6 +192,7 @@
         mNativeWrapper.setPortInfo(hdmiPortInfos);
         mNativeWrapper.setPortConnectionStatus(1, true);
         mHdmiControlService.initService();
+        mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
         mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
         mNativeWrapper.setPhysicalAddress(0x2000);
         mTestLooper.dispatchAll();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index df4aa5d..f27b8c2 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.hdmi;
 
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
 import static com.android.server.hdmi.Constants.ABORT_UNRECOGNIZED_OPCODE;
 import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
 import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
@@ -29,8 +30,10 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.hardware.hdmi.HdmiControlManager;
@@ -125,7 +128,7 @@
 
         mHdmiControlService =
                 new HdmiControlService(InstrumentationRegistry.getTargetContext(),
-                        Collections.emptyList()) {
+                        Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
                     @Override
                     void wakeUp() {
                         mWokenUp = true;
@@ -179,6 +182,7 @@
                 new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, true);
         mNativeWrapper.setPortInfo(hdmiPortInfos);
         mHdmiControlService.initService();
+        mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
         mPowerManager = new FakePowerManagerWrapper(context);
         mHdmiControlService.setPowerManager(mPowerManager);
         mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
@@ -768,7 +772,7 @@
         // When the device reports its physical address, the listener eventually is invoked.
         HdmiCecMessage reportPhysicalAddress =
                 HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
-                ADDR_PLAYBACK_2, 0x1000, HdmiDeviceInfo.DEVICE_PLAYBACK);
+                        ADDR_PLAYBACK_2, 0x1000, HdmiDeviceInfo.DEVICE_PLAYBACK);
         mNativeWrapper.onCecMessage(reportPhysicalAddress);
         mTestLooper.dispatchAll();
 
@@ -777,6 +781,54 @@
         assertThat(mDeviceEventListeners.size()).isEqualTo(1);
         assertThat(mDeviceEventListeners.get(0).getStatus())
                 .isEqualTo(HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
+    }
 
+    @Test
+    public void receiveSetAudioVolumeLevel_samNotActivated_noFeatureAbort_volumeChanges() {
+        when(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)).thenReturn(25);
+
+        // Max volume of STREAM_MUSIC is retrieved on boot
+        mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
+        mTestLooper.dispatchAll();
+
+        mNativeWrapper.onCecMessage(SetAudioVolumeLevelMessage.build(
+                ADDR_PLAYBACK_1,
+                ADDR_TV,
+                20));
+        mTestLooper.dispatchAll();
+
+        // <Feature Abort>[Not in correct mode] not sent
+        HdmiCecMessage featureAbortMessage = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+                ADDR_TV,
+                ADDR_PLAYBACK_1,
+                Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL,
+                Constants.ABORT_NOT_IN_CORRECT_MODE);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(featureAbortMessage);
+
+        // <Set Audio Volume Level> uses volume range [0, 100]; STREAM_MUSIC uses range [0, 25]
+        verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(5), anyInt());
+    }
+
+    @Test
+    public void receiveSetAudioVolumeLevel_samActivated_respondsFeatureAbort_noVolumeChange() {
+        mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildSetSystemAudioMode(
+                ADDR_AUDIO_SYSTEM, ADDR_TV, true));
+        mTestLooper.dispatchAll();
+
+        mNativeWrapper.onCecMessage(SetAudioVolumeLevelMessage.build(
+                ADDR_PLAYBACK_1, ADDR_TV, 50));
+        mTestLooper.dispatchAll();
+
+        // <Feature Abort>[Not in correct mode] sent
+        HdmiCecMessage featureAbortMessage = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+                ADDR_TV,
+                ADDR_PLAYBACK_1,
+                Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL,
+                Constants.ABORT_NOT_IN_CORRECT_MODE);
+        assertThat(mNativeWrapper.getResultMessages()).contains(featureAbortMessage);
+
+        // AudioManager not notified of volume change
+        verify(mAudioManager, never()).setStreamVolume(eq(AudioManager.STREAM_MUSIC), anyInt(),
+                anyInt());
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
index 50c9f70..a446e10 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
@@ -51,7 +51,8 @@
     @Before
     public void setUp() throws Exception {
         HdmiControlService mHdmiControlService = new HdmiControlService(
-                InstrumentationRegistry.getTargetContext(), Collections.emptyList());
+                InstrumentationRegistry.getTargetContext(), Collections.emptyList(),
+                new FakeAudioDeviceVolumeManagerWrapper());
 
         mHdmiControlService.setIoLooper(mTestLooper.getLooper());
     }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java
index 03532ae..b8a1ba3 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java
@@ -67,7 +67,8 @@
     @Before
     public void setUp() throws Exception {
         mContext = InstrumentationRegistry.getTargetContext();
-        mHdmiControlService = new HdmiControlService(mContext, Collections.emptyList()) {
+        mHdmiControlService = new HdmiControlService(mContext, Collections.emptyList(),
+                new FakeAudioDeviceVolumeManagerWrapper()) {
             @Override
             void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) {
                 mDeviceEventListenerStatuses.add(status);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
index 7a68285..b94deed 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.hdmi;
 
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
 import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -65,7 +66,8 @@
         Context contextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
         Looper myLooper = mTestLooper.getLooper();
 
-        mHdmiControlService = new HdmiControlService(contextSpy, Collections.emptyList()) {
+        mHdmiControlService = new HdmiControlService(contextSpy, Collections.emptyList(),
+                new FakeAudioDeviceVolumeManagerWrapper()) {
             @Override
             boolean isControlEnabled() {
                 return true;
@@ -105,6 +107,7 @@
         mNativeWrapper.setPortInfo(hdmiPortInfos);
         mNativeWrapper.setPortConnectionStatus(1, true);
         mHdmiControlService.initService();
+        mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
         mPowerManager = new FakePowerManagerWrapper(contextSpy);
         mHdmiControlService.setPowerManager(mPowerManager);
         mHdmiControlService.getHdmiCecNetwork().initPortInfo();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index 3987c32..6266571 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -91,7 +91,8 @@
 
         HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContextSpy);
 
-        mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList()));
+        mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList(),
+                new FakeAudioDeviceVolumeManagerWrapper()));
         doNothing().when(mHdmiControlServiceSpy)
                 .writeStringSystemProperty(anyString(), anyString());
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
index 561e6a5..46a4e86 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.hdmi;
 
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
 import static com.android.server.hdmi.Constants.ADDR_TV;
 import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
 import static com.android.server.hdmi.OneTouchPlayAction.STATE_WAITING_FOR_REPORT_POWER_STATUS;
@@ -87,7 +88,8 @@
         mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
         mHdmiCecConfig = new FakeHdmiCecConfig(mContextSpy);
 
-        mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList()) {
+        mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList(),
+                new FakeAudioDeviceVolumeManagerWrapper()) {
             @Override
             AudioManager getAudioManager() {
                 return new AudioManager() {
@@ -120,6 +122,7 @@
         mHdmiControlService.setCecController(hdmiCecController);
         mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
         mHdmiControlService.initService();
+        mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
         mPowerManager = new FakePowerManagerWrapper(mContextSpy);
         mHdmiControlService.setPowerManager(mPowerManager);
         mPhysicalAddress = 0x2000;
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToAudioSystemAvcTest.java b/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToAudioSystemAvcTest.java
new file mode 100644
index 0000000..6418602
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToAudioSystemAvcTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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.hdmi;
+
+import android.hardware.hdmi.DeviceFeatures;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.media.AudioDeviceAttributes;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import com.google.android.collect.Lists;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Arrays;
+
+/**
+ * Tests for Absolute Volume Control where the local device is a Playback device and the
+ * System Audio device is an Audio System.
+ */
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+public class PlaybackDeviceToAudioSystemAvcTest extends BaseAbsoluteVolumeControlTest {
+
+    @Override
+    protected HdmiCecLocalDevice createLocalDevice(HdmiControlService hdmiControlService) {
+        return new HdmiCecLocalDevicePlayback(hdmiControlService);
+    }
+
+    @Override
+    protected int getPhysicalAddress() {
+        return 0x1100;
+    }
+
+    @Override
+    protected int getDeviceType() {
+        return HdmiDeviceInfo.DEVICE_PLAYBACK;
+    }
+
+    @Override
+    protected AudioDeviceAttributes getAudioOutputDevice() {
+        return HdmiControlService.AUDIO_OUTPUT_DEVICE_HDMI;
+    }
+
+    @Override
+    protected int getSystemAudioDeviceLogicalAddress() {
+        return Constants.ADDR_AUDIO_SYSTEM;
+    }
+
+    @Override
+    protected int getSystemAudioDeviceType() {
+        return HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM;
+    }
+
+    /**
+     * AVC is disabled if the Audio System disables System Audio mode, and the TV has unknown
+     * support for <Set Audio Volume Level>. It is enabled once the TV confirms support for
+     * <Set Audio Volume Level> and sends <Report Audio Status>.
+     */
+    @Test
+    public void switchToTv_absoluteVolumeControlDisabledUntilAllConditionsMet() {
+        enableAbsoluteVolumeControl();
+
+        // Audio System disables System Audio Mode. AVC should be disabled.
+        receiveSetSystemAudioMode(false);
+        verifyAbsoluteVolumeDisabled();
+
+        // TV reports support for <Set Audio Volume Level>
+        mNativeWrapper.onCecMessage(ReportFeaturesMessage.build(
+                Constants.ADDR_TV, HdmiControlManager.HDMI_CEC_VERSION_2_0,
+                Arrays.asList(HdmiDeviceInfo.DEVICE_TV), Constants.RC_PROFILE_TV,
+                Lists.newArrayList(Constants.RC_PROFILE_TV_NONE),
+                DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder()
+                        .setSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED)
+                        .build()));
+        mTestLooper.dispatchAll();
+
+        // TV reports its initial audio status
+        mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildReportAudioStatus(
+                Constants.ADDR_TV,
+                getLogicalAddress(),
+                30,
+                false));
+        mTestLooper.dispatchAll();
+
+        verifyAbsoluteVolumeEnabled();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToTvAvcTest.java b/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToTvAvcTest.java
new file mode 100644
index 0000000..504c3bc
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToTvAvcTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.hdmi;
+
+import static org.mockito.Mockito.clearInvocations;
+
+import android.hardware.hdmi.DeviceFeatures;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.media.AudioDeviceAttributes;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+/**
+ * Tests for Absolute Volume Control where the local device is a Playback device and the
+ * System Audio device is a TV.
+ */
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+public class PlaybackDeviceToTvAvcTest extends BaseAbsoluteVolumeControlTest {
+
+    @Override
+    protected HdmiCecLocalDevice createLocalDevice(HdmiControlService hdmiControlService) {
+        return new HdmiCecLocalDevicePlayback(hdmiControlService);
+    }
+
+    @Override
+    protected int getPhysicalAddress() {
+        return 0x1100;
+    }
+
+    @Override
+    protected int getDeviceType() {
+        return HdmiDeviceInfo.DEVICE_PLAYBACK;
+    }
+
+    @Override
+    protected AudioDeviceAttributes getAudioOutputDevice() {
+        return HdmiControlService.AUDIO_OUTPUT_DEVICE_HDMI;
+    }
+
+    @Override
+    protected int getSystemAudioDeviceLogicalAddress() {
+        return Constants.ADDR_TV;
+    }
+
+    @Override
+    protected int getSystemAudioDeviceType() {
+        return HdmiDeviceInfo.DEVICE_TV;
+    }
+
+    /**
+     * AVC is disabled when an Audio System with unknown support for <Set Audio Volume Level>
+     * becomes the System Audio device. It is enabled once the Audio System reports that it
+     * supports <Set Audio Volume Level> and sends <Report Audio Status>.
+     */
+    @Test
+    public void switchToAudioSystem_absoluteVolumeControlDisabledUntilAllConditionsMet() {
+        enableAbsoluteVolumeControl();
+
+        // Audio System enables System Audio Mode. AVC should be disabled.
+        receiveSetSystemAudioMode(true);
+        verifyAbsoluteVolumeDisabled();
+
+        clearInvocations(mAudioManager, mAudioDeviceVolumeManager);
+
+        // Audio System reports support for <Set Audio Volume Level>
+        mNativeWrapper.onCecMessage(ReportFeaturesMessage.build(
+                Constants.ADDR_AUDIO_SYSTEM, HdmiControlManager.HDMI_CEC_VERSION_2_0,
+                Arrays.asList(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM), Constants.RC_PROFILE_SOURCE,
+                Collections.emptyList(),
+                DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder()
+                        .setSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED)
+                        .build()));
+        mTestLooper.dispatchAll();
+
+        // Audio system reports its initial audio status
+        mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildReportAudioStatus(
+                Constants.ADDR_AUDIO_SYSTEM,
+                getLogicalAddress(),
+                30,
+                false));
+        mTestLooper.dispatchAll();
+
+        verifyAbsoluteVolumeEnabled();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
index c878f99..e5058be 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.hdmi;
 
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
 import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
 import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
 import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2;
@@ -68,7 +69,8 @@
         mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
 
         mHdmiControlService = new HdmiControlService(mContextSpy,
-                Collections.singletonList(HdmiDeviceInfo.DEVICE_TV)) {
+                Collections.singletonList(HdmiDeviceInfo.DEVICE_TV),
+                new FakeAudioDeviceVolumeManagerWrapper()) {
             @Override
             AudioManager getAudioManager() {
                 return new AudioManager() {
@@ -110,6 +112,7 @@
                 new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, false);
         mNativeWrapper.setPortInfo(hdmiPortInfo);
         mHdmiControlService.initService();
+        mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
         mPowerManager = new FakePowerManagerWrapper(mContextSpy);
         mHdmiControlService.setPowerManager(mPowerManager);
         mPhysicalAddress = 0x0000;
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
index 6184c21..f7983ca 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.hdmi;
 
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
 import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
 import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
 
@@ -96,8 +97,8 @@
         mMyLooper = mTestLooper.getLooper();
 
         mHdmiControlService =
-                new HdmiControlService(context,
-                        Collections.emptyList()) {
+                new HdmiControlService(context, Collections.emptyList(),
+                        new FakeAudioDeviceVolumeManagerWrapper()) {
                     @Override
                     boolean isControlEnabled() {
                         return true;
@@ -125,6 +126,7 @@
         mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
         mLocalDevices.add(mHdmiCecLocalDeviceTv);
         mHdmiControlService.initService();
+        mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
         mPowerManager = new FakePowerManagerWrapper(context);
         mHdmiControlService.setPowerManager(mPowerManager);
         mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
index 0587864..566a7e0 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.hdmi;
 
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
 import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
 import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
 import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
@@ -149,7 +150,7 @@
 
         mHdmiControlService =
                 new HdmiControlService(InstrumentationRegistry.getTargetContext(),
-                        Collections.emptyList()) {
+                        Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
                     @Override
                     boolean isControlEnabled() {
                         return true;
@@ -186,6 +187,7 @@
                         true, false, false);
         mNativeWrapper.setPortInfo(hdmiPortInfos);
         mHdmiControlService.initService();
+        mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
         mPowerManager = new FakePowerManagerWrapper(context);
         mHdmiControlService.setPowerManager(mPowerManager);
         mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java
index a34b55c..087e407 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java
@@ -20,6 +20,7 @@
 import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORTED;
 import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORT_UNKNOWN;
 
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
 import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -81,7 +82,8 @@
         mContextSpy = spy(new ContextWrapper(
                 InstrumentationRegistry.getInstrumentation().getTargetContext()));
 
-        mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList()));
+        mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList(),
+                new FakeAudioDeviceVolumeManagerWrapper()));
         doNothing().when(mHdmiControlServiceSpy)
                 .writeStringSystemProperty(anyString(), anyString());
 
@@ -98,6 +100,7 @@
         mHdmiControlServiceSpy.setHdmiMhlController(
                 HdmiMhlControllerStub.create(mHdmiControlServiceSpy));
         mHdmiControlServiceSpy.initService();
+        mHdmiControlServiceSpy.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
         mPowerManager = new FakePowerManagerWrapper(mContextSpy);
         mHdmiControlServiceSpy.setPowerManager(mPowerManager);
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java
index 9d14341..1644252 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java
@@ -17,6 +17,7 @@
 package com.android.server.hdmi;
 
 
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
 import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
 import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
 import static com.android.server.hdmi.SystemAudioAutoInitiationAction.RETRIES_ON_TIMEOUT;
@@ -69,7 +70,8 @@
 
         Looper myLooper = mTestLooper.getLooper();
 
-        mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList()) {
+        mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList(),
+                new FakeAudioDeviceVolumeManagerWrapper()) {
             @Override
             AudioManager getAudioManager() {
                 return new AudioManager() {
@@ -108,6 +110,7 @@
                 new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, true);
         mNativeWrapper.setPortInfo(hdmiPortInfos);
         mHdmiControlService.initService();
+        mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
         mPowerManager = new FakePowerManagerWrapper(mContextSpy);
         mHdmiControlService.setPowerManager(mPowerManager);
         mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java
index 095c69c..c2f706a 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java
@@ -69,7 +69,7 @@
         Context context = InstrumentationRegistry.getTargetContext();
 
         HdmiControlService hdmiControlService = new HdmiControlService(context,
-                Collections.emptyList()) {
+                Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
                     @Override
                     void sendCecCommand(
                             HdmiCecMessage command, @Nullable SendMessageCallback callback) {
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/TvToAudioSystemAvcTest.java b/services/tests/servicestests/src/com/android/server/hdmi/TvToAudioSystemAvcTest.java
new file mode 100644
index 0000000..41c0e0d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/TvToAudioSystemAvcTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.hdmi;
+
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.media.AudioDeviceAttributes;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for Absolute Volume Control where the local device is a TV and the System Audio device
+ * is an Audio System. Assumes that the TV uses ARC (rather than eARC).
+ */
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+public class TvToAudioSystemAvcTest extends BaseAbsoluteVolumeControlTest {
+
+    @Override
+    protected HdmiCecLocalDevice createLocalDevice(HdmiControlService hdmiControlService) {
+        return new HdmiCecLocalDeviceTv(hdmiControlService);
+    }
+
+    @Override
+    protected int getPhysicalAddress() {
+        return 0x0000;
+    }
+
+    @Override
+    protected int getDeviceType() {
+        return HdmiDeviceInfo.DEVICE_TV;
+    }
+
+    @Override
+    protected AudioDeviceAttributes getAudioOutputDevice() {
+        return HdmiControlService.AUDIO_OUTPUT_DEVICE_HDMI_ARC;
+    }
+
+    @Override
+    protected int getSystemAudioDeviceLogicalAddress() {
+        return Constants.ADDR_AUDIO_SYSTEM;
+    }
+
+    @Override
+    protected int getSystemAudioDeviceType() {
+        return HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
index bd35be4..c735d18 100644
--- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
@@ -22,7 +22,6 @@
 import static org.junit.Assert.assertNotNull;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doNothing;
@@ -39,7 +38,6 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
 import android.os.Binder;
 import android.os.HandlerThread;
 import android.os.LocaleList;
@@ -102,8 +100,6 @@
     @Mock
     private Context mMockContext;
     @Mock
-    private PackageManagerInternal mMockPackageManagerInternal;
-    @Mock
     private PackageManager mMockPackageManager;
     @Mock
     private LocaleManagerService mMockLocaleManagerService;
@@ -129,7 +125,6 @@
     @Before
     public void setUp() throws Exception {
         mMockContext = mock(Context.class);
-        mMockPackageManagerInternal = mock(PackageManagerInternal.class);
         mMockPackageManager = mock(PackageManager.class);
         mMockLocaleManagerService = mock(LocaleManagerService.class);
         SystemAppUpdateTracker systemAppUpdateTracker = mock(SystemAppUpdateTracker.class);
@@ -141,7 +136,7 @@
         broadcastHandlerThread.start();
 
         mBackupHelper = spy(new ShadowLocaleManagerBackupHelper(mMockContext,
-                mMockLocaleManagerService, mMockPackageManagerInternal, mClock, STAGE_DATA,
+                mMockLocaleManagerService, mMockPackageManager, mClock, STAGE_DATA,
                 broadcastHandlerThread));
         doNothing().when(mBackupHelper).notifyBackupManager();
 
@@ -158,8 +153,8 @@
 
     @Test
     public void testBackupPayload_noAppsInstalled_returnsNull() throws Exception {
-        doReturn(List.of()).when(mMockPackageManagerInternal)
-                .getInstalledApplications(anyLong(), anyInt(), anyInt());
+        doReturn(List.of()).when(mMockPackageManager)
+                .getInstalledApplicationsAsUser(any(), anyInt());
 
         assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
     }
@@ -198,8 +193,8 @@
         ApplicationInfo anotherAppInfo = new ApplicationInfo();
         defaultAppInfo.packageName = DEFAULT_PACKAGE_NAME;
         anotherAppInfo.packageName = "com.android.anotherapp";
-        doReturn(List.of(defaultAppInfo, anotherAppInfo)).when(mMockPackageManagerInternal)
-                .getInstalledApplications(anyLong(), anyInt(), anyInt());
+        doReturn(List.of(defaultAppInfo, anotherAppInfo)).when(mMockPackageManager)
+                .getInstalledApplicationsAsUser(any(), anyInt());
 
         setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, DEFAULT_LOCALES);
         // Exception when getting locales for anotherApp.
@@ -447,8 +442,8 @@
         // Retention period has not elapsed.
         setCurrentTimeMillis(
                 DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.minusHours(1).toMillis());
-        doReturn(List.of()).when(mMockPackageManagerInternal)
-                .getInstalledApplications(anyLong(), anyInt(), anyInt());
+        doReturn(List.of()).when(mMockPackageManager)
+                .getInstalledApplicationsAsUser(any(), anyInt());
         assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
 
         checkStageDataExists(DEFAULT_USER_ID);
@@ -456,8 +451,8 @@
         // Exactly RETENTION_PERIOD amount of time has passed so stage data should still not be
         // removed.
         setCurrentTimeMillis(DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.toMillis());
-        doReturn(List.of()).when(mMockPackageManagerInternal)
-                .getInstalledApplications(anyLong(), anyInt(), anyInt());
+        doReturn(List.of()).when(mMockPackageManager)
+                .getInstalledApplicationsAsUser(any(), anyInt());
         assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
 
         checkStageDataExists(DEFAULT_USER_ID);
@@ -465,8 +460,8 @@
         // Retention period has now expired, stage data should be deleted.
         setCurrentTimeMillis(
                 DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.plusSeconds(1).toMillis());
-        doReturn(List.of()).when(mMockPackageManagerInternal)
-                .getInstalledApplications(anyLong(), anyInt(), anyInt());
+        doReturn(List.of()).when(mMockPackageManager)
+                .getInstalledApplicationsAsUser(any(), anyInt());
         assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
 
         checkStageDataDoesNotExist(DEFAULT_USER_ID);
@@ -577,8 +572,8 @@
     private void setUpDummyAppForPackageManager(String packageName) {
         ApplicationInfo dummyApp = new ApplicationInfo();
         dummyApp.packageName = packageName;
-        doReturn(List.of(dummyApp)).when(mMockPackageManagerInternal)
-                .getInstalledApplications(anyLong(), anyInt(), anyInt());
+        doReturn(List.of(dummyApp)).when(mMockPackageManager)
+                .getInstalledApplicationsAsUser(any(), anyInt());
     }
 
     /**
diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
index 0b3ef45..1dcdbac 100644
--- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
@@ -23,7 +23,6 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doNothing;
@@ -40,7 +39,6 @@
 import android.content.pm.InstallSourceInfo;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
 import android.os.Binder;
 import android.os.LocaleList;
 
@@ -80,7 +78,7 @@
     @Mock
     private Context mMockContext;
     @Mock
-    private PackageManagerInternal mMockPackageManagerInternal;
+    private PackageManager mMockPackageManager;
     @Mock
     private FakePackageConfigurationUpdater mFakePackageConfigurationUpdater;
     @Mock
@@ -95,14 +93,13 @@
         mMockContext = mock(Context.class);
         mMockActivityTaskManager = mock(ActivityTaskManagerInternal.class);
         mMockActivityManager = mock(ActivityManagerInternal.class);
-        mMockPackageManagerInternal = mock(PackageManagerInternal.class);
+        mMockPackageManager = mock(PackageManager.class);
         mMockPackageMonitor = mock(PackageMonitor.class);
 
         // For unit tests, set the default installer info
-        PackageManager mockPackageManager = mock(PackageManager.class);
-        doReturn(DEFAULT_INSTALL_SOURCE_INFO).when(mockPackageManager)
+        doReturn(DEFAULT_INSTALL_SOURCE_INFO).when(mMockPackageManager)
                 .getInstallSourceInfo(anyString());
-        doReturn(mockPackageManager).when(mMockContext).getPackageManager();
+        doReturn(mMockPackageManager).when(mMockContext).getPackageManager();
 
         mFakePackageConfigurationUpdater = new FakePackageConfigurationUpdater();
         doReturn(mFakePackageConfigurationUpdater)
@@ -117,14 +114,14 @@
 
         mMockBackupHelper = mock(ShadowLocaleManagerBackupHelper.class);
         mLocaleManagerService = new LocaleManagerService(mMockContext, mMockActivityTaskManager,
-                mMockActivityManager, mMockPackageManagerInternal,
+                mMockActivityManager, mMockPackageManager,
                 mMockBackupHelper, mMockPackageMonitor);
     }
 
     @Test(expected = SecurityException.class)
     public void testSetApplicationLocales_arbitraryAppWithoutPermissions_fails() throws Exception {
         doReturn(DEFAULT_UID)
-                .when(mMockPackageManagerInternal).getPackageUid(anyString(), anyLong(), anyInt());
+                .when(mMockPackageManager).getPackageUidAsUser(anyString(), any(), anyInt());
         setUpFailingPermissionCheckFor(Manifest.permission.CHANGE_CONFIGURATION);
 
         try {
@@ -170,7 +167,7 @@
     @Test
     public void testSetApplicationLocales_arbitraryAppWithPermission_succeeds() throws Exception {
         doReturn(DEFAULT_UID)
-                .when(mMockPackageManagerInternal).getPackageUid(anyString(), anyLong(), anyInt());
+                .when(mMockPackageManager).getPackageUidAsUser(anyString(), any(), anyInt());
         // if package is not owned by the caller, the calling app should have the following
         //   permission. We will mock this to succeed to imitate that.
         setUpPassingPermissionCheckFor(Manifest.permission.CHANGE_CONFIGURATION);
@@ -186,7 +183,7 @@
     @Test
     public void testSetApplicationLocales_callerOwnsPackage_succeeds() throws Exception {
         doReturn(Binder.getCallingUid())
-                .when(mMockPackageManagerInternal).getPackageUid(anyString(), anyLong(), anyInt());
+                .when(mMockPackageManager).getPackageUidAsUser(anyString(), any(), anyInt());
 
         mLocaleManagerService.setApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID,
                 DEFAULT_LOCALES);
@@ -197,8 +194,8 @@
 
     @Test(expected = IllegalArgumentException.class)
     public void testSetApplicationLocales_invalidPackageOrUserId_fails() throws Exception {
-        doReturn(INVALID_UID)
-                .when(mMockPackageManagerInternal).getPackageUid(anyString(), anyLong(), anyInt());
+        doThrow(new PackageManager.NameNotFoundException("Mock"))
+                .when(mMockPackageManager).getPackageUidAsUser(anyString(), any(), anyInt());
         try {
             mLocaleManagerService.setApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID,
                     LocaleList.getEmptyLocaleList());
@@ -211,8 +208,8 @@
 
     @Test(expected = SecurityException.class)
     public void testGetApplicationLocales_arbitraryAppWithoutPermission_fails() throws Exception {
-        doReturn(DEFAULT_UID).when(mMockPackageManagerInternal)
-                .getPackageUid(anyString(), anyLong(), anyInt());
+        doReturn(DEFAULT_UID).when(mMockPackageManager)
+                .getPackageUidAsUser(anyString(), any(), anyInt());
         setUpFailingPermissionCheckFor(Manifest.permission.READ_APP_SPECIFIC_LOCALES);
 
         try {
@@ -229,8 +226,8 @@
     public void testGetApplicationLocales_appSpecificConfigAbsent_returnsEmptyList()
             throws Exception {
         // any valid app calling for its own package or having appropriate permission
-        doReturn(DEFAULT_UID).when(mMockPackageManagerInternal)
-                .getPackageUid(anyString(), anyLong(), anyInt());
+        doReturn(DEFAULT_UID).when(mMockPackageManager)
+                .getPackageUidAsUser(anyString(), any(), anyInt());
         setUpPassingPermissionCheckFor(Manifest.permission.READ_APP_SPECIFIC_LOCALES);
         doReturn(null)
                 .when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt());
@@ -244,8 +241,8 @@
     @Test
     public void testGetApplicationLocales_appSpecificLocalesAbsent_returnsEmptyList()
             throws Exception {
-        doReturn(DEFAULT_UID).when(mMockPackageManagerInternal)
-                .getPackageUid(anyString(), anyLong(), anyInt());
+        doReturn(DEFAULT_UID).when(mMockPackageManager)
+                .getPackageUidAsUser(anyString(), any(), anyInt());
         setUpPassingPermissionCheckFor(Manifest.permission.READ_APP_SPECIFIC_LOCALES);
         doReturn(new PackageConfig(/* nightMode = */ 0, /* locales = */ null))
                 .when(mMockActivityTaskManager).getApplicationConfig(any(), anyInt());
@@ -259,8 +256,8 @@
     @Test
     public void testGetApplicationLocales_callerOwnsAppAndConfigPresent_returnsLocales()
             throws Exception {
-        doReturn(Binder.getCallingUid()).when(mMockPackageManagerInternal)
-                .getPackageUid(anyString(), anyLong(), anyInt());
+        doReturn(Binder.getCallingUid()).when(mMockPackageManager)
+                .getPackageUidAsUser(anyString(), any(), anyInt());
         doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES))
                 .when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt());
 
@@ -273,8 +270,8 @@
     @Test
     public void testGetApplicationLocales_arbitraryCallerWithPermissions_returnsLocales()
             throws Exception {
-        doReturn(DEFAULT_UID).when(mMockPackageManagerInternal)
-                .getPackageUid(anyString(), anyLong(), anyInt());
+        doReturn(DEFAULT_UID).when(mMockPackageManager)
+                .getPackageUidAsUser(anyString(), any(), anyInt());
         setUpPassingPermissionCheckFor(Manifest.permission.READ_APP_SPECIFIC_LOCALES);
         doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES))
                 .when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt());
@@ -288,10 +285,10 @@
     @Test
     public void testGetApplicationLocales_callerIsInstaller_returnsLocales()
             throws Exception {
-        doReturn(DEFAULT_UID).when(mMockPackageManagerInternal)
-                .getPackageUid(eq(DEFAULT_PACKAGE_NAME), anyLong(), anyInt());
-        doReturn(Binder.getCallingUid()).when(mMockPackageManagerInternal)
-                .getPackageUid(eq(DEFAULT_INSTALLER_PACKAGE_NAME), anyLong(), anyInt());
+        doReturn(DEFAULT_UID).when(mMockPackageManager)
+                .getPackageUidAsUser(eq(DEFAULT_PACKAGE_NAME), any(), anyInt());
+        doReturn(Binder.getCallingUid()).when(mMockPackageManager)
+                .getPackageUidAsUser(eq(DEFAULT_INSTALLER_PACKAGE_NAME), any(), anyInt());
         doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES))
                 .when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt());
 
diff --git a/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java b/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java
index ad9be0d..e403c87 100644
--- a/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java
+++ b/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java
@@ -17,7 +17,7 @@
 package com.android.server.locales;
 
 import android.content.Context;
-import android.content.pm.PackageManagerInternal;
+import android.content.pm.PackageManager;
 import android.os.HandlerThread;
 import android.util.SparseArray;
 
@@ -31,9 +31,10 @@
 public class ShadowLocaleManagerBackupHelper extends LocaleManagerBackupHelper {
     ShadowLocaleManagerBackupHelper(Context context,
             LocaleManagerService localeManagerService,
-            PackageManagerInternal pmInternal, Clock clock,
+            PackageManager packageManager, Clock clock,
             SparseArray<LocaleManagerBackupHelper.StagedData> stagedData,
             HandlerThread broadcastHandlerThread) {
-        super(context, localeManagerService, pmInternal, clock, stagedData, broadcastHandlerThread);
+        super(context, localeManagerService, packageManager, clock, stagedData,
+                broadcastHandlerThread);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
index db602ca..808b74e 100644
--- a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
@@ -38,7 +38,6 @@
 import android.content.pm.InstallSourceInfo;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
 import android.os.Binder;
 import android.os.Environment;
 import android.os.LocaleList;
@@ -93,8 +92,6 @@
     @Mock
     PackageManager mMockPackageManager;
     @Mock
-    private PackageManagerInternal mMockPackageManagerInternal;
-    @Mock
     private ActivityTaskManagerInternal mMockActivityTaskManager;
     @Mock
     private ActivityManagerInternal mMockActivityManager;
@@ -110,21 +107,20 @@
         mMockContext = mock(Context.class);
         mMockActivityTaskManager = mock(ActivityTaskManagerInternal.class);
         mMockActivityManager = mock(ActivityManagerInternal.class);
-        mMockPackageManagerInternal = mock(PackageManagerInternal.class);
+        mMockPackageManager = mock(PackageManager.class);
         LocaleManagerBackupHelper mockLocaleManagerBackupHelper =
                 mock(ShadowLocaleManagerBackupHelper.class);
         // PackageMonitor is not needed in LocaleManagerService for these tests hence it is
         // passed as null.
         mLocaleManagerService = new LocaleManagerService(mMockContext,
                 mMockActivityTaskManager, mMockActivityManager,
-                mMockPackageManagerInternal, mockLocaleManagerBackupHelper,
+                mMockPackageManager, mockLocaleManagerBackupHelper,
                 /* mPackageMonitor= */ null);
 
         doReturn(DEFAULT_USER_ID).when(mMockActivityManager)
                 .handleIncomingUser(anyInt(), anyInt(), eq(DEFAULT_USER_ID), anyBoolean(), anyInt(),
                         anyString(), anyString());
 
-        mMockPackageManager = mock(PackageManager.class);
         doReturn(DEFAULT_INSTALL_SOURCE_INFO).when(mMockPackageManager)
                 .getInstallSourceInfo(anyString());
         doReturn(mMockPackageManager).when(mMockContext).getPackageManager();
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index 7634b09..6c7f872 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -441,14 +441,14 @@
         setNetpolicyXml(context);
 
         doAnswer(new Answer<Void>() {
-
             @Override
             public Void answer(InvocationOnMock invocation) throws Throwable {
                 mUidObserver = (IUidObserver) invocation.getArguments()[0];
                 Log.d(TAG, "set mUidObserver to " + mUidObserver);
                 return null;
             }
-        }).when(mActivityManager).registerUidObserver(any(), anyInt(), anyInt(), any(String.class));
+        }).when(mActivityManagerInternal).registerNetworkPolicyUidObserver(any(),
+                anyInt(), anyInt(), any(String.class));
 
         mFutureIntent = newRestrictBackgroundChangedFuture();
         mDeps = new TestDependencies(mServiceContext);
diff --git a/services/tests/servicestests/src/com/android/server/pm/AppsFilterImplTest.java b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
similarity index 87%
rename from services/tests/servicestests/src/com/android/server/pm/AppsFilterImplTest.java
rename to services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
index d8f4349..b72b8d2 100644
--- a/services/tests/servicestests/src/com/android/server/pm/AppsFilterImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
@@ -77,7 +77,7 @@
 
 @Presubmit
 @RunWith(JUnit4.class)
-public class AppsFilterImplTest {
+public class AppsFilterTest {
 
     private static final int DUMMY_CALLING_APPID = 10345;
     private static final int DUMMY_TARGET_APPID = 10556;
@@ -98,9 +98,9 @@
     }
 
     @Mock
-    AppsFilterImpl.FeatureConfig mFeatureConfigMock;
+    AppsFilter.FeatureConfig mFeatureConfigMock;
     @Mock
-    AppsFilterImpl.StateProvider mStateProvider;
+    AppsFilter.StateProvider mStateProvider;
     @Mock
     Executor mMockExecutor;
     @Mock
@@ -204,11 +204,11 @@
 
         MockitoAnnotations.initMocks(this);
         doAnswer(invocation -> {
-            ((AppsFilterImpl.StateProvider.CurrentStateCallback) invocation.getArgument(0))
+            ((AppsFilter.StateProvider.CurrentStateCallback) invocation.getArgument(0))
                     .currentState(mExisting, USER_INFO_LIST);
             return new Object();
         }).when(mStateProvider)
-                .runWithState(any(AppsFilterImpl.StateProvider.CurrentStateCallback.class));
+                .runWithState(any(AppsFilter.StateProvider.CurrentStateCallback.class));
 
         doAnswer(invocation -> {
             ((Runnable) invocation.getArgument(0)).run();
@@ -218,14 +218,14 @@
         when(mFeatureConfigMock.isGloballyEnabled()).thenReturn(true);
         when(mFeatureConfigMock.packageIsEnabled(any(AndroidPackage.class))).thenAnswer(
                 (Answer<Boolean>) invocation ->
-                        ((AndroidPackage) invocation.getArgument(SYSTEM_USER)).getTargetSdkVersion()
+                        ((AndroidPackage)invocation.getArgument(SYSTEM_USER)).getTargetSdkVersion()
                                 >= Build.VERSION_CODES.R);
     }
 
     @Test
     public void testSystemReadyPropogates() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+        final AppsFilter appsFilter =
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor, mMockPmInternal);
         final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
         watcher.register();
@@ -236,8 +236,8 @@
 
     @Test
     public void testQueriesAction_FilterMatches() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+        final AppsFilter appsFilter =
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor, mMockPmInternal);
         final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
         watcher.register();
@@ -259,8 +259,8 @@
     }
     @Test
     public void testQueriesProtectedAction_FilterDoesNotMatch() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+        final AppsFilter appsFilter =
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor, mMockPmInternal);
         final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
         watcher.register();
@@ -308,8 +308,8 @@
 
     @Test
     public void testQueriesProvider_FilterMatches() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+        final AppsFilter appsFilter =
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor, mMockPmInternal);
         final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
         watcher.register();
@@ -333,8 +333,8 @@
 
     @Test
     public void testOnUserUpdated_FilterMatches() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+        final AppsFilter appsFilter =
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor, mMockPmInternal);
         simulateAddBasicAndroid(appsFilter);
 
@@ -356,11 +356,11 @@
 
         // adds new user
         doAnswer(invocation -> {
-            ((AppsFilterImpl.StateProvider.CurrentStateCallback) invocation.getArgument(0))
+            ((AppsFilter.StateProvider.CurrentStateCallback) invocation.getArgument(0))
                     .currentState(mExisting, USER_INFO_LIST_WITH_ADDED);
             return new Object();
         }).when(mStateProvider)
-                .runWithState(any(AppsFilterImpl.StateProvider.CurrentStateCallback.class));
+                .runWithState(any(AppsFilter.StateProvider.CurrentStateCallback.class));
         appsFilter.onUserCreated(ADDED_USER);
 
         for (int subjectUserId : USER_ARRAY_WITH_ADDED) {
@@ -373,11 +373,11 @@
 
         // delete user
         doAnswer(invocation -> {
-            ((AppsFilterImpl.StateProvider.CurrentStateCallback) invocation.getArgument(0))
+            ((AppsFilter.StateProvider.CurrentStateCallback) invocation.getArgument(0))
                     .currentState(mExisting, USER_INFO_LIST);
             return new Object();
         }).when(mStateProvider)
-                .runWithState(any(AppsFilterImpl.StateProvider.CurrentStateCallback.class));
+                .runWithState(any(AppsFilter.StateProvider.CurrentStateCallback.class));
         appsFilter.onUserDeleted(ADDED_USER);
 
         for (int subjectUserId : USER_ARRAY) {
@@ -391,8 +391,8 @@
 
     @Test
     public void testQueriesDifferentProvider_Filters() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+        final AppsFilter appsFilter =
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor, mMockPmInternal);
         final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
         watcher.register();
@@ -416,8 +416,8 @@
 
     @Test
     public void testQueriesProviderWithSemiColon_FilterMatches() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+        final AppsFilter appsFilter =
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor, mMockPmInternal);
         simulateAddBasicAndroid(appsFilter);
         appsFilter.onSystemReady();
@@ -435,8 +435,8 @@
 
     @Test
     public void testQueriesAction_NoMatchingAction_Filters() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+        final AppsFilter appsFilter =
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor, mMockPmInternal);
         simulateAddBasicAndroid(appsFilter);
         appsFilter.onSystemReady();
@@ -452,8 +452,8 @@
 
     @Test
     public void testQueriesAction_NoMatchingActionFilterLowSdk_DoesntFilter() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+        final AppsFilter appsFilter =
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor, mMockPmInternal);
         simulateAddBasicAndroid(appsFilter);
         appsFilter.onSystemReady();
@@ -473,8 +473,8 @@
 
     @Test
     public void testNoQueries_Filters() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+        final AppsFilter appsFilter =
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor, mMockPmInternal);
         simulateAddBasicAndroid(appsFilter);
         appsFilter.onSystemReady();
@@ -490,7 +490,7 @@
 
     @Test
     public void testNoUsesLibrary_Filters() throws Exception {
-        final AppsFilterImpl appsFilter = new AppsFilterImpl(mStateProvider, mFeatureConfigMock,
+        final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock,
                 new String[]{}, /* systemAppsQueryable */ false, /* overlayProvider */ null,
                 mMockExecutor, mMockPmInternal);
 
@@ -516,7 +516,7 @@
 
     @Test
     public void testUsesLibrary_DoesntFilter() throws Exception {
-        final AppsFilterImpl appsFilter = new AppsFilterImpl(mStateProvider, mFeatureConfigMock,
+        final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock,
                 new String[]{}, /* systemAppsQueryable */ false, /* overlayProvider */ null,
                 mMockExecutor, mMockPmInternal);
 
@@ -543,7 +543,7 @@
 
     @Test
     public void testUsesOptionalLibrary_DoesntFilter() throws Exception {
-        final AppsFilterImpl appsFilter = new AppsFilterImpl(mStateProvider, mFeatureConfigMock,
+        final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock,
                 new String[]{}, /* systemAppsQueryable */ false, /* overlayProvider */ null,
                 mMockExecutor, mMockPmInternal);
 
@@ -570,7 +570,7 @@
 
     @Test
     public void testUsesLibrary_ShareUid_DoesntFilter() throws Exception {
-        final AppsFilterImpl appsFilter = new AppsFilterImpl(mStateProvider, mFeatureConfigMock,
+        final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock,
                 new String[]{}, /* systemAppsQueryable */ false, /* overlayProvider */ null,
                 mMockExecutor, mMockPmInternal);
 
@@ -602,8 +602,8 @@
 
     @Test
     public void testForceQueryable_SystemDoesntFilter() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+        final AppsFilter appsFilter =
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor, mMockPmInternal);
         simulateAddBasicAndroid(appsFilter);
         appsFilter.onSystemReady();
@@ -621,8 +621,8 @@
 
     @Test
     public void testForceQueryable_NonSystemFilters() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+        final AppsFilter appsFilter =
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor, mMockPmInternal);
         simulateAddBasicAndroid(appsFilter);
         appsFilter.onSystemReady();
@@ -638,10 +638,9 @@
 
     @Test
     public void testForceQueryableByDevice_SystemCaller_DoesntFilter() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock,
-                        new String[]{"com.some.package"}, false, null,
-                        mMockExecutor, mMockPmInternal);
+        final AppsFilter appsFilter =
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{"com.some.package"},
+                        false, null, mMockExecutor, mMockPmInternal);
         simulateAddBasicAndroid(appsFilter);
         appsFilter.onSystemReady();
 
@@ -658,8 +657,8 @@
 
     @Test
     public void testSystemSignedTarget_DoesntFilter() throws CertificateException {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+        final AppsFilter appsFilter =
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor, mMockPmInternal);
         appsFilter.onSystemReady();
 
@@ -687,10 +686,9 @@
 
     @Test
     public void testForceQueryableByDevice_NonSystemCaller_Filters() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock,
-                        new String[]{"com.some.package"}, false, null,
-                        mMockExecutor, mMockPmInternal);
+        final AppsFilter appsFilter =
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{"com.some.package"},
+                        false, null, mMockExecutor, mMockPmInternal);
         simulateAddBasicAndroid(appsFilter);
         appsFilter.onSystemReady();
 
@@ -706,8 +704,8 @@
 
     @Test
     public void testSystemQueryable_DoesntFilter() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{},
+        final AppsFilter appsFilter =
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{},
                         true /* system force queryable */, null, mMockExecutor,
                         mMockPmInternal);
         simulateAddBasicAndroid(appsFilter);
@@ -725,8 +723,8 @@
 
     @Test
     public void testQueriesPackage_DoesntFilter() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+        final AppsFilter appsFilter =
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor, mMockPmInternal);
         simulateAddBasicAndroid(appsFilter);
         appsFilter.onSystemReady();
@@ -744,8 +742,8 @@
     public void testNoQueries_FeatureOff_DoesntFilter() throws Exception {
         when(mFeatureConfigMock.packageIsEnabled(any(AndroidPackage.class)))
                 .thenReturn(false);
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+        final AppsFilter appsFilter =
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor, mMockPmInternal);
         simulateAddBasicAndroid(appsFilter);
         appsFilter.onSystemReady();
@@ -761,8 +759,8 @@
 
     @Test
     public void testSystemUid_DoesntFilter() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+        final AppsFilter appsFilter =
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor, mMockPmInternal);
         simulateAddBasicAndroid(appsFilter);
         appsFilter.onSystemReady();
@@ -777,8 +775,8 @@
 
     @Test
     public void testSystemUidSecondaryUser_DoesntFilter() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+        final AppsFilter appsFilter =
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor, mMockPmInternal);
         simulateAddBasicAndroid(appsFilter);
         appsFilter.onSystemReady();
@@ -794,8 +792,8 @@
 
     @Test
     public void testNonSystemUid_NoCallingSetting_Filters() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+        final AppsFilter appsFilter =
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor, mMockPmInternal);
         simulateAddBasicAndroid(appsFilter);
         appsFilter.onSystemReady();
@@ -809,8 +807,8 @@
 
     @Test
     public void testNoTargetPackage_filters() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+        final AppsFilter appsFilter =
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor, mMockPmInternal);
         simulateAddBasicAndroid(appsFilter);
         appsFilter.onSystemReady();
@@ -840,7 +838,7 @@
                 .setOverlayTargetOverlayableName("overlayableName");
         ParsingPackage actor = pkg("com.some.package.actor");
 
-        final AppsFilterImpl appsFilter = new AppsFilterImpl(
+        final AppsFilter appsFilter = new AppsFilter(
                 mStateProvider,
                 mFeatureConfigMock,
                 new String[]{},
@@ -935,7 +933,7 @@
         when(mMockPmInternal.getSharedUserPackages(any(Integer.class))).thenReturn(
                 actorSharedSettingPackages
         );
-        final AppsFilterImpl appsFilter = new AppsFilterImpl(
+        final AppsFilter appsFilter = new AppsFilter(
                 mStateProvider,
                 mFeatureConfigMock,
                 new String[]{},
@@ -987,8 +985,8 @@
 
     @Test
     public void testInitiatingApp_DoesntFilter() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+        final AppsFilter appsFilter =
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor, mMockPmInternal);
         simulateAddBasicAndroid(appsFilter);
         appsFilter.onSystemReady();
@@ -1005,8 +1003,8 @@
 
     @Test
     public void testUninstalledInitiatingApp_Filters() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+        final AppsFilter appsFilter =
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor, mMockPmInternal);
         simulateAddBasicAndroid(appsFilter);
         appsFilter.onSystemReady();
@@ -1023,8 +1021,8 @@
 
     @Test
     public void testOriginatingApp_Filters() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+        final AppsFilter appsFilter =
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor, mMockPmInternal);
         final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
         watcher.register();
@@ -1048,8 +1046,8 @@
 
     @Test
     public void testInstallingApp_DoesntFilter() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+        final AppsFilter appsFilter =
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor, mMockPmInternal);
         final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
         watcher.register();
@@ -1073,8 +1071,8 @@
 
     @Test
     public void testInstrumentation_DoesntFilter() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+        final AppsFilter appsFilter =
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor, mMockPmInternal);
         final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
         watcher.register();
@@ -1102,8 +1100,8 @@
 
     @Test
     public void testWhoCanSee() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+        final AppsFilter appsFilter =
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor, mMockPmInternal);
         final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
         watcher.register();
@@ -1175,8 +1173,8 @@
 
     @Test
     public void testOnChangeReport() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+        final AppsFilter appsFilter =
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor, mMockPmInternal);
         final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
         watcher.register();
@@ -1248,8 +1246,8 @@
 
     @Test
     public void testOnChangeReportedFilter() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+        final AppsFilter appsFilter =
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor, mMockPmInternal);
         simulateAddBasicAndroid(appsFilter);
         appsFilter.onSystemReady();
@@ -1272,53 +1270,6 @@
         watcher.verifyNoChangeReported("shouldFilterApplication");
     }
 
-    @Test
-    public void testAppsFilterRead() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
-                        mMockExecutor, mMockPmInternal);
-        simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady();
-
-        PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"),
-                DUMMY_TARGET_APPID);
-        PackageSetting instrumentation = simulateAddPackage(appsFilter,
-                pkgWithInstrumentation("com.some.other.package", "com.some.package"),
-                DUMMY_CALLING_APPID);
-
-        final int hasProviderAppId = Process.FIRST_APPLICATION_UID + 1;
-        final int queriesProviderAppId = Process.FIRST_APPLICATION_UID + 2;
-        PackageSetting queriesProvider = simulateAddPackage(appsFilter,
-                pkgQueriesProvider("com.yet.some.other.package", "com.some.authority"),
-                queriesProviderAppId);
-        appsFilter.grantImplicitAccess(
-                hasProviderAppId, queriesProviderAppId, false /* retainOnUpdate */);
-
-        AppsFilterSnapshot snapshot = appsFilter.snapshot();
-        assertFalse(
-                snapshot.shouldFilterApplication(DUMMY_CALLING_APPID, instrumentation, target,
-                        SYSTEM_USER));
-        assertFalse(
-                snapshot.shouldFilterApplication(DUMMY_TARGET_APPID, target, instrumentation,
-                        SYSTEM_USER));
-
-        SparseArray<int[]> queriesProviderFilter =
-                snapshot.getVisibilityAllowList(queriesProvider, USER_ARRAY, mExisting);
-        assertThat(toList(queriesProviderFilter.get(SYSTEM_USER)), contains(queriesProviderAppId));
-        assertTrue(snapshot.canQueryPackage(instrumentation.getPkg(),
-                target.getPackageName()));
-
-        // New changes don't affect the snapshot
-        appsFilter.removePackage(target, false);
-        assertTrue(
-                appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, instrumentation, target,
-                        SYSTEM_USER));
-        assertFalse(
-                snapshot.shouldFilterApplication(DUMMY_CALLING_APPID, instrumentation, target,
-                        SYSTEM_USER));
-
-    }
-
     private List<Integer> toList(int[] array) {
         ArrayList<Integer> ret = new ArrayList<>(array.length);
         for (int i = 0; i < array.length; i++) {
@@ -1331,7 +1282,7 @@
         PackageSettingBuilder withBuilder(PackageSettingBuilder builder);
     }
 
-    private void simulateAddBasicAndroid(AppsFilterImpl appsFilter) throws Exception {
+    private void simulateAddBasicAndroid(AppsFilter appsFilter) throws Exception {
         final Signature frameworkSignature = Mockito.mock(Signature.class);
         final SigningDetails frameworkSigningDetails =
                 new SigningDetails(new Signature[]{frameworkSignature}, 1);
@@ -1340,17 +1291,17 @@
                 b -> b.setSigningDetails(frameworkSigningDetails));
     }
 
-    private PackageSetting simulateAddPackage(AppsFilterImpl filter,
+    private PackageSetting simulateAddPackage(AppsFilter filter,
             ParsingPackage newPkgBuilder, int appId) {
         return simulateAddPackage(filter, newPkgBuilder, appId, null /*settingBuilder*/);
     }
 
-    private PackageSetting simulateAddPackage(AppsFilterImpl filter,
+    private PackageSetting simulateAddPackage(AppsFilter filter,
             ParsingPackage newPkgBuilder, int appId, @Nullable WithSettingBuilder action) {
         return simulateAddPackage(filter, newPkgBuilder, appId, action, null /*sharedUserSetting*/);
     }
 
-    private PackageSetting simulateAddPackage(AppsFilterImpl filter,
+    private PackageSetting simulateAddPackage(AppsFilter filter,
                 ParsingPackage newPkgBuilder, int appId, @Nullable WithSettingBuilder action,
             @Nullable SharedUserSetting sharedUserSetting) {
         final PackageSetting setting =
@@ -1373,7 +1324,7 @@
         return setting;
     }
 
-    private void simulateAddPackage(PackageSetting setting, AppsFilterImpl filter,
+    private void simulateAddPackage(PackageSetting setting, AppsFilter filter,
             @Nullable SharedUserSetting sharedUserSetting) {
         mExisting.put(setting.getPackageName(), setting);
         if (sharedUserSetting != null) {
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
index 004d7bc..07cca0c 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
@@ -84,15 +84,15 @@
 @RunWith(AndroidJUnit4.class)
 public class PackageParserLegacyCoreTest {
     private static final String RELEASED = null;
-    private static final String OLDER_PRE_RELEASE = "A";
-    private static final String PRE_RELEASE = "B";
-    private static final String NEWER_PRE_RELEASE = "C";
+    private static final String OLDER_PRE_RELEASE = "Q";
+    private static final String PRE_RELEASE = "R";
+    private static final String NEWER_PRE_RELEASE = "Z";
 
     // Codenames with a fingerprint attached to them. These may only be present in the apps
     // declared min SDK and not as platform codenames.
-    private static final String OLDER_PRE_RELEASE_WITH_FINGERPRINT = "A.fingerprint";
-    private static final String PRE_RELEASE_WITH_FINGERPRINT = "B.fingerprint";
-    private static final String NEWER_PRE_RELEASE_WITH_FINGERPRINT = "C.fingerprint";
+    private static final String OLDER_PRE_RELEASE_WITH_FINGERPRINT = "Q.fingerprint";
+    private static final String PRE_RELEASE_WITH_FINGERPRINT = "R.fingerprint";
+    private static final String NEWER_PRE_RELEASE_WITH_FINGERPRINT = "Z.fingerprint";
 
     private static final String[] CODENAMES_RELEASED = { /* empty */};
     private static final String[] CODENAMES_PRE_RELEASE = {PRE_RELEASE};
@@ -199,13 +199,14 @@
     }
 
     private void verifyComputeTargetSdkVersion(int targetSdkVersion, String targetSdkCodename,
-            boolean isPlatformReleased, int expectedTargetSdk) {
+            boolean isPlatformReleased, boolean allowUnknownCodenames, int expectedTargetSdk) {
         final ParseTypeImpl input = ParseTypeImpl.forParsingWithoutPlatformCompat();
         final ParseResult<Integer> result = FrameworkParsingPackageUtils.computeTargetSdkVersion(
                 targetSdkVersion,
                 targetSdkCodename,
                 isPlatformReleased ? CODENAMES_RELEASED : CODENAMES_PRE_RELEASE,
-                input);
+                input,
+                allowUnknownCodenames);
 
         if (expectedTargetSdk == -1) {
             assertTrue(result.isError());
@@ -220,40 +221,61 @@
         // Do allow older release targetSdkVersion on pre-release platform.
         // APP: Released API 10
         // DEV: Pre-release API 20
-        verifyComputeTargetSdkVersion(OLDER_VERSION, RELEASED, false, OLDER_VERSION);
+        verifyComputeTargetSdkVersion(OLDER_VERSION, RELEASED, false, false, OLDER_VERSION);
 
         // Do allow same release targetSdkVersion on pre-release platform.
         // APP: Released API 20
         // DEV: Pre-release API 20
-        verifyComputeTargetSdkVersion(PLATFORM_VERSION, RELEASED, false, PLATFORM_VERSION);
+        verifyComputeTargetSdkVersion(PLATFORM_VERSION, RELEASED, false, false, PLATFORM_VERSION);
 
         // Do allow newer release targetSdkVersion on pre-release platform.
         // APP: Released API 30
         // DEV: Pre-release API 20
-        verifyComputeTargetSdkVersion(NEWER_VERSION, RELEASED, false, NEWER_VERSION);
+        verifyComputeTargetSdkVersion(NEWER_VERSION, RELEASED, false, false, NEWER_VERSION);
 
         // Don't allow older pre-release targetSdkVersion on pre-release platform.
         // APP: Pre-release API 10
         // DEV: Pre-release API 20
-        verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, false, -1);
-        verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE_WITH_FINGERPRINT, false, -1);
+        verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, false, false, -1);
+        verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE_WITH_FINGERPRINT, false,
+                false, -1
+        );
 
+        // Don't allow older pre-release targetSdkVersion on pre-release platform when
+        // allowUnknownCodenames is true.
+        // APP: Pre-release API 10
+        // DEV: Pre-release API 20
+        verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, false,
+                true, -1);
+        verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE_WITH_FINGERPRINT, false,
+                true, -1);
 
         // Do allow same pre-release targetSdkVersion on pre-release platform,
         // but overwrite the specified version with CUR_DEVELOPMENT.
         // APP: Pre-release API 20
         // DEV: Pre-release API 20
         verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE, false,
-                Build.VERSION_CODES.CUR_DEVELOPMENT);
+                false, Build.VERSION_CODES.CUR_DEVELOPMENT);
         verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE_WITH_FINGERPRINT, false,
-                Build.VERSION_CODES.CUR_DEVELOPMENT);
-
+                false, Build.VERSION_CODES.CUR_DEVELOPMENT);
 
         // Don't allow newer pre-release targetSdkVersion on pre-release platform.
         // APP: Pre-release API 30
         // DEV: Pre-release API 20
-        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, false, -1);
-        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, false, -1);
+        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, false, false, -1);
+        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, false,
+                false, -1
+        );
+
+        // Do allow newer pre-release targetSdkVersion on pre-release platform when
+        // allowUnknownCodenames is true.
+        // APP: Pre-release API 30
+        // DEV: Pre-release API 20
+        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, false,
+                true, Build.VERSION_CODES.CUR_DEVELOPMENT);
+        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, false,
+                true, Build.VERSION_CODES.CUR_DEVELOPMENT);
+
     }
 
     @Test
@@ -261,36 +283,58 @@
         // Do allow older release targetSdkVersion on released platform.
         // APP: Released API 10
         // DEV: Released API 20
-        verifyComputeTargetSdkVersion(OLDER_VERSION, RELEASED, true, OLDER_VERSION);
+        verifyComputeTargetSdkVersion(OLDER_VERSION, RELEASED, true, false, OLDER_VERSION);
 
         // Do allow same release targetSdkVersion on released platform.
         // APP: Released API 20
         // DEV: Released API 20
-        verifyComputeTargetSdkVersion(PLATFORM_VERSION, RELEASED, true, PLATFORM_VERSION);
+        verifyComputeTargetSdkVersion(PLATFORM_VERSION, RELEASED, true, false, PLATFORM_VERSION);
 
         // Do allow newer release targetSdkVersion on released platform.
         // APP: Released API 30
         // DEV: Released API 20
-        verifyComputeTargetSdkVersion(NEWER_VERSION, RELEASED, true, NEWER_VERSION);
+        verifyComputeTargetSdkVersion(NEWER_VERSION, RELEASED, true, false, NEWER_VERSION);
 
         // Don't allow older pre-release targetSdkVersion on released platform.
         // APP: Pre-release API 10
         // DEV: Released API 20
-        verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, true, -1);
-        verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE_WITH_FINGERPRINT, true, -1);
+        verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, true, false, -1);
+        verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE_WITH_FINGERPRINT, true,
+                false, -1
+        );
 
         // Don't allow same pre-release targetSdkVersion on released platform.
         // APP: Pre-release API 20
         // DEV: Released API 20
-        verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE, true, -1);
-        verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE_WITH_FINGERPRINT, true, -1);
+        verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE, true, false, -1);
+        verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE_WITH_FINGERPRINT, true, false,
+                -1
+        );
 
+        // Don't allow same pre-release targetSdkVersion on released platform when
+        // allowUnknownCodenames is true.
+        // APP: Pre-release API 20
+        // DEV: Released API 20
+        verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE, true, true,
+                -1);
+        verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE_WITH_FINGERPRINT, true, true,
+                -1);
 
         // Don't allow newer pre-release targetSdkVersion on released platform.
         // APP: Pre-release API 30
         // DEV: Released API 20
-        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, true, -1);
-        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, true, -1);
+        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, true, false, -1);
+        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, true,
+                false, -1
+        );
+        // Do allow newer pre-release targetSdkVersion on released platform when
+        // allowUnknownCodenames is true.
+        // APP: Pre-release API 30
+        // DEV: Released API 20
+        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, true, true,
+                Build.VERSION_CODES.CUR_DEVELOPMENT);
+        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, true,
+                true, Build.VERSION_CODES.CUR_DEVELOPMENT);
     }
 
     /**
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/library/ApexSharedLibraryUpdaterTest.java b/services/tests/servicestests/src/com/android/server/pm/parsing/library/ApexSharedLibraryUpdaterTest.java
index 1d9ea4b..0b144dc 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/library/ApexSharedLibraryUpdaterTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/library/ApexSharedLibraryUpdaterTest.java
@@ -41,6 +41,8 @@
 @RunWith(JUnit4.class)
 public class ApexSharedLibraryUpdaterTest extends PackageSharedLibraryUpdaterTest {
 
+    private static final String SDK_INT_PLUS_ONE = "" + (Build.VERSION.SDK_INT + 1);
+    private static final String SDK_INT_PLUS_TWO = "" + (Build.VERSION.SDK_INT + 2);
     private final ArrayMap<String, SystemConfig.SharedLibraryEntry> mSharedLibraries =
             new ArrayMap<>(8);
 
@@ -51,14 +53,19 @@
 
     private void installSharedLibraries() throws Exception {
         mSharedLibraries.clear();
-        insertLibrary("foo", 0, 0);
-        insertLibrary("fooBcpSince30", 30, 0);
-        insertLibrary("fooBcpBefore30", 0, 30);
-        insertLibrary("fooFromFuture", Build.VERSION.SDK_INT + 2, 0);
+        insertLibrary("foo", null, null);
+        insertLibrary("fooBcpSince30", "30", null);
+        insertLibrary("fooBcpBefore30", null, "30");
+        // simulate libraries being added to the BCP in a future release
+        insertLibrary("fooSinceFuture", SDK_INT_PLUS_ONE, null);
+        insertLibrary("fooSinceFutureCodename", "Z", null);
+        // simulate libraries being removed from the BCP in a future release
+        insertLibrary("fooBcpBeforeFuture", null, SDK_INT_PLUS_ONE);
+        insertLibrary("fooBcpBeforeFutureCodename", null, "Z");
     }
 
-    private void insertLibrary(String libraryName, int onBootclasspathSince,
-            int onBootclasspathBefore) {
+    private void insertLibrary(String libraryName, String onBootclasspathSince,
+            String onBootclasspathBefore) {
         mSharedLibraries.put(libraryName, new SystemConfig.SharedLibraryEntry(
                 libraryName,
                 "foo.jar",
@@ -112,7 +119,7 @@
     }
 
     @Test
-    public void testBcpSince11kNotAppliedWithoutLibrary() {
+    public void testBcpSinceFutureNotAppliedWithoutLibrary() {
         ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
                 .setTargetSdkVersion(Build.VERSION_CODES.R)
                 .hideAsParsed());
@@ -128,15 +135,17 @@
     }
 
     @Test
-    public void testBcpSince11kNotAppliedWithLibrary() {
+    public void testBcpSinceFutureNotAppliedWithLibrary() {
         ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
                 .setTargetSdkVersion(Build.VERSION_CODES.R)
-                .addUsesLibrary("fooFromFuture")
+                .addUsesLibrary("fooSinceFuture")
+                .addUsesLibrary("fooSinceFutureCodename")
                 .hideAsParsed());
 
         AndroidPackage after = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
                 .setTargetSdkVersion(Build.VERSION_CODES.R)
-                .addUsesLibrary("fooFromFuture")
+                .addUsesLibrary("fooSinceFuture")
+                .addUsesLibrary("fooSinceFutureCodename")
                 .hideAsParsed())
                 .hideAsFinal();
 
@@ -183,7 +192,7 @@
      */
     @Test
     public void testBcpRemovedThenAddedPast() {
-        insertLibrary("fooBcpRemovedThenAdded", 30, 28);
+        insertLibrary("fooBcpRemovedThenAdded", "30", "28");
 
         ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
                 .setTargetSdkVersion(Build.VERSION_CODES.N)
@@ -207,7 +216,8 @@
      */
     @Test
     public void testBcpRemovedThenAddedMiddle_targetQ() {
-        insertLibrary("fooBcpRemovedThenAdded", Build.VERSION.SDK_INT + 1, 30);
+        insertLibrary("fooBcpRemovedThenAdded", SDK_INT_PLUS_ONE, "30");
+        insertLibrary("fooBcpRemovedThenAddedCodename", "Z", "30");
 
         ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
                 .setTargetSdkVersion(Build.VERSION_CODES.Q)
@@ -217,6 +227,7 @@
                 .setTargetSdkVersion(Build.VERSION_CODES.Q)
                 .addUsesLibrary("fooBcpRemovedThenAdded")
                 .addUsesLibrary("fooBcpBefore30")
+                .addUsesLibrary("fooBcpRemovedThenAddedCodename")
                 .hideAsParsed())
                 .hideAsFinal();
 
@@ -232,7 +243,8 @@
      */
     @Test
     public void testBcpRemovedThenAddedMiddle_targetR() {
-        insertLibrary("fooBcpRemovedThenAdded", Build.VERSION.SDK_INT + 1, 30);
+        insertLibrary("fooBcpRemovedThenAdded", SDK_INT_PLUS_ONE, "30");
+        insertLibrary("fooBcpRemovedThenAddedCodename", "Z", "30");
 
         ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
                 .setTargetSdkVersion(Build.VERSION_CODES.R)
@@ -256,7 +268,8 @@
      */
     @Test
     public void testBcpRemovedThenAddedMiddle_targetR_usingLib() {
-        insertLibrary("fooBcpRemovedThenAdded", Build.VERSION.SDK_INT + 1, 30);
+        insertLibrary("fooBcpRemovedThenAdded", SDK_INT_PLUS_ONE, "30");
+        insertLibrary("fooBcpRemovedThenAddedCodename", "Z", "30");
 
         ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
                 .setTargetSdkVersion(Build.VERSION_CODES.R)
@@ -274,6 +287,82 @@
         checkBackwardsCompatibility(before, after);
     }
 
+    /**
+     * Test a library that was first removed from the BCP [to a mainline module] and later was
+     * moved back to the BCP via a mainline module update. Both things happening in future SDKs.
+     */
+    @Test
+    public void testBcpRemovedThenAddedFuture() {
+        insertLibrary("fooBcpRemovedThenAdded", SDK_INT_PLUS_TWO, SDK_INT_PLUS_ONE);
+        ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
+                .setTargetSdkVersion(Build.VERSION_CODES.R)
+                .hideAsParsed());
+
+        AndroidPackage after = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
+                .setTargetSdkVersion(Build.VERSION_CODES.R)
+                .hideAsParsed())
+                .hideAsFinal();
+
+        // in this example, we are at the point where the library is still in the BCP
+        checkBackwardsCompatibility(before, after);
+    }
+
+    /**
+     * Test a library that was first removed from the BCP [to a mainline module] and later was
+     * moved back to the BCP via a mainline module update. Both things happening in future SDKs.
+     */
+    @Test
+    public void testBcpRemovedThenAddedFuture_usingLib() {
+        insertLibrary("fooBcpRemovedThenAdded", SDK_INT_PLUS_TWO, SDK_INT_PLUS_ONE);
+
+        ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
+                .setTargetSdkVersion(Integer.parseInt(SDK_INT_PLUS_ONE))
+                .addUsesLibrary("fooBcpRemovedThenAdded")
+                .hideAsParsed());
+
+        AndroidPackage after = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
+                .setTargetSdkVersion(Integer.parseInt(SDK_INT_PLUS_ONE))
+                .hideAsParsed())
+                .hideAsFinal();
+
+        // in this example, we are at the point where the library was removed from the BCP
+        checkBackwardsCompatibility(before, after);
+    }
+
+    @Test
+    public void testBcpBeforeFuture() {
+        ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
+                .setTargetSdkVersion(Build.VERSION_CODES.R)
+                .addUsesLibrary("fooBcpBeforeFuture")
+                .addUsesLibrary("fooBcpBeforeFutureCodename")
+                .hideAsParsed());
+
+        AndroidPackage after = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
+                .setTargetSdkVersion(Build.VERSION_CODES.R)
+                .hideAsParsed())
+                .hideAsFinal();
+
+        // in this example, we are at the point where the library was removed from the BCP
+        checkBackwardsCompatibility(before, after);
+    }
+
+    @Test
+    public void testBcpBeforeFuture_futureTargetSdk() {
+        ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
+                .setTargetSdkVersion(Integer.parseInt(SDK_INT_PLUS_ONE))
+                .addUsesLibrary("fooBcpBeforeFuture")
+                .addUsesLibrary("fooBcpBeforeFutureCodename")
+                .hideAsParsed());
+
+        AndroidPackage after = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
+                .setTargetSdkVersion(Integer.parseInt(SDK_INT_PLUS_ONE))
+                .hideAsParsed())
+                .hideAsFinal();
+
+        // in this example, we are at the point where the library was removed from the BCP
+        checkBackwardsCompatibility(before, after);
+    }
+
     private void checkBackwardsCompatibility(ParsedPackage before, AndroidPackage after) {
         checkBackwardsCompatibility(before, after,
                 () -> new ApexSharedLibraryUpdater(mSharedLibraries));
diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundHw2CompatTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundHw2CompatTest.java
index 6bdd88c..2d0755d 100644
--- a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundHw2CompatTest.java
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundHw2CompatTest.java
@@ -20,18 +20,15 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.atMost;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
@@ -56,32 +53,20 @@
 import org.junit.runners.Parameterized;
 import org.mockito.ArgumentCaptor;
 
-import java.util.LinkedList;
-import java.util.List;
 import java.util.concurrent.atomic.AtomicReference;
 
 @RunWith(Parameterized.class)
 public class SoundHw2CompatTest {
-    @Parameterized.Parameter(0) public String mVersion;
-    @Parameterized.Parameter(1) public boolean mSupportConcurrentCapture;
+    @Parameterized.Parameter public String mVersion;
 
     private final Runnable mRebootRunnable = mock(Runnable.class);
     private ISoundTriggerHal mCanonical;
-    private CaptureStateNotifier mCaptureStateNotifier;
     private android.hardware.soundtrigger.V2_0.ISoundTriggerHw mHalDriver;
 
     // We run the test once for every version of the underlying driver.
-    @Parameterized.Parameters(name = "{0}, concurrent={1}")
-    public static Iterable<Object[]> data() {
-        List<Object[]> result = new LinkedList<>();
-
-        for (String version : new String[]{"V2_0", "V2_1", "V2_2", "V2_3",}) {
-            for (boolean concurrentCapture : new boolean[]{false, true}) {
-                result.add(new Object[]{version, concurrentCapture});
-            }
-        }
-
-        return result;
+    @Parameterized.Parameters
+    public static Object[] data() {
+        return new String[]{"V2_0", "V2_1", "V2_2", "V2_3"};
     }
 
     @Before
@@ -139,7 +124,7 @@
         when(mHalDriver.asBinder()).thenReturn(binder);
 
         android.hardware.soundtrigger.V2_3.Properties halProperties =
-                TestUtil.createDefaultProperties_2_3(mSupportConcurrentCapture);
+                TestUtil.createDefaultProperties_2_3();
         doAnswer(invocation -> {
             ((android.hardware.soundtrigger.V2_0.ISoundTriggerHw.getPropertiesCallback) invocation.getArgument(
                     0)).onValues(0, halProperties.base);
@@ -156,10 +141,7 @@
             }).when(driver).getProperties_2_3(any());
         }
 
-        mCaptureStateNotifier = spy(new CaptureStateNotifier());
-
-        mCanonical = SoundTriggerHw2Compat.create(mHalDriver, mRebootRunnable,
-                mCaptureStateNotifier);
+        mCanonical = SoundTriggerHw2Compat.create(mHalDriver, mRebootRunnable, null);
 
         // During initialization any method can be called, but after we're starting to enforce that
         // no additional methods are called.
@@ -171,7 +153,6 @@
         mCanonical.detach();
         verifyNoMoreInteractions(mHalDriver);
         verifyNoMoreInteractions(mRebootRunnable);
-        mCaptureStateNotifier.verifyNoMoreListeners();
     }
 
     @Test
@@ -194,12 +175,12 @@
             // It is OK for the SUT to cache the properties, so the underlying method doesn't
             // need to be called every single time.
             verify(driver, atMost(1)).getProperties_2_3(any());
-            TestUtil.validateDefaultProperties(properties, mSupportConcurrentCapture);
+            TestUtil.validateDefaultProperties(properties);
         } else {
             // It is OK for the SUT to cache the properties, so the underlying method doesn't
             // need to be called every single time.
             verify(mHalDriver, atMost(1)).getProperties(any());
-            TestUtil.validateDefaultProperties(properties, mSupportConcurrentCapture, 0, "");
+            TestUtil.validateDefaultProperties(properties, 0, "");
         }
     }
 
@@ -291,7 +272,7 @@
 
         ISoundTriggerHal.ModelCallback canonicalCallback = mock(
                 ISoundTriggerHal.ModelCallback.class);
-        final int maxModels = TestUtil.createDefaultProperties_2_0(false).maxSoundModels;
+        final int maxModels = TestUtil.createDefaultProperties_2_0().maxSoundModels;
         int[] modelHandles = new int[maxModels];
 
         // Load as many models as we're allowed.
@@ -318,7 +299,7 @@
         verify(globalCallback).onResourcesAvailable();
     }
 
-    private int loadPhraseModel_2_0(ISoundTriggerHal.ModelCallback canonicalCallback)
+    private void loadPhraseModel_2_0(ISoundTriggerHal.ModelCallback canonicalCallback)
             throws Exception {
         final int handle = 29;
         ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel>
@@ -345,10 +326,9 @@
 
         TestUtil.validatePhraseSoundModel_2_0(modelCaptor.getValue());
         validateCallback_2_0(callbackCaptor.getValue(), canonicalCallback);
-        return handle;
     }
 
-    private int loadPhraseModel_2_1(ISoundTriggerHal.ModelCallback canonicalCallback)
+    private void loadPhraseModel_2_1(ISoundTriggerHal.ModelCallback canonicalCallback)
             throws Exception {
         final android.hardware.soundtrigger.V2_1.ISoundTriggerHw driver_2_1 =
                 (android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver;
@@ -380,14 +360,13 @@
 
         TestUtil.validatePhraseSoundModel_2_1(model.get());
         validateCallback_2_1(callbackCaptor.getValue(), canonicalCallback);
-        return handle;
     }
 
-    public int loadPhraseModel(ISoundTriggerHal.ModelCallback canonicalCallback) throws Exception {
+    public void loadPhraseModel(ISoundTriggerHal.ModelCallback canonicalCallback) throws Exception {
         if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
-            return loadPhraseModel_2_1(canonicalCallback);
+            loadPhraseModel_2_1(canonicalCallback);
         } else {
-            return loadPhraseModel_2_0(canonicalCallback);
+            loadPhraseModel_2_0(canonicalCallback);
         }
     }
 
@@ -484,80 +463,6 @@
     }
 
     @Test
-    public void testConcurrentCaptureAbort() throws Exception {
-        assumeFalse(mSupportConcurrentCapture);
-        verify(mCaptureStateNotifier, atLeast(1)).registerListener(any());
-
-        // Register global callback.
-        ISoundTriggerHal.GlobalCallback globalCallback = mock(
-                ISoundTriggerHal.GlobalCallback.class);
-        mCanonical.registerCallback(globalCallback);
-
-        // Load.
-        ISoundTriggerHal.ModelCallback canonicalCallback = mock(
-                ISoundTriggerHal.ModelCallback.class);
-        final int handle = loadGenericModel(canonicalCallback);
-
-        // Then start.
-        startRecognition(handle, canonicalCallback);
-
-        // Now activate external capture.
-        mCaptureStateNotifier.setState(true);
-
-        // Expect hardware to have been stopped.
-        verify(mHalDriver).stopRecognition(handle);
-
-        // Expect an abort event (async).
-        ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
-                RecognitionEvent.class);
-        mCanonical.flushCallbacks();
-        verify(canonicalCallback).recognitionCallback(eq(handle), eventCaptor.capture());
-        assertEquals(RecognitionStatus.ABORTED, eventCaptor.getValue().status);
-
-        // Deactivate external capture.
-        mCaptureStateNotifier.setState(false);
-
-        // Expect a onResourcesAvailable().
-        mCanonical.flushCallbacks();
-        verify(globalCallback).onResourcesAvailable();
-    }
-
-    @Test
-    public void testConcurrentCaptureReject() throws Exception {
-        assumeFalse(mSupportConcurrentCapture);
-        verify(mCaptureStateNotifier, atLeast(1)).registerListener(any());
-
-        // Register global callback.
-        ISoundTriggerHal.GlobalCallback globalCallback = mock(
-                ISoundTriggerHal.GlobalCallback.class);
-        mCanonical.registerCallback(globalCallback);
-
-        // Load (this registers the callback).
-        ISoundTriggerHal.ModelCallback canonicalCallback = mock(
-                ISoundTriggerHal.ModelCallback.class);
-        final int handle = loadGenericModel(canonicalCallback);
-
-        // Report external capture active.
-        mCaptureStateNotifier.setState(true);
-
-        // Then start.
-        RecognitionConfig config = TestUtil.createRecognitionConfig();
-        try {
-            mCanonical.startRecognition(handle, 203, 204, config);
-            fail("Expected an exception");
-        } catch (RecoverableException e) {
-            assertEquals(Status.RESOURCE_CONTENTION, e.errorCode);
-        }
-
-        // Deactivate external capture.
-        mCaptureStateNotifier.setState(false);
-
-        // Expect a onResourcesAvailable().
-        mCanonical.flushCallbacks();
-        verify(globalCallback).onResourcesAvailable();
-    }
-
-    @Test
     public void testStopRecognition() throws Exception {
         mCanonical.stopRecognition(17);
         verify(mHalDriver).stopRecognition(17);
@@ -675,7 +580,7 @@
     }
 
     @Test
-    public void testGlobalCallback() throws Exception {
+    public void testGlobalCallback() {
         testGlobalCallback_2_0();
     }
 
@@ -803,29 +708,4 @@
         verifyNoMoreInteractions(canonicalCallback);
         clearInvocations(canonicalCallback);
     }
-
-    public static class CaptureStateNotifier implements ICaptureStateNotifier {
-        private final List<Listener> mListeners = new LinkedList<>();
-
-        @Override
-        public boolean registerListener(Listener listener) {
-            mListeners.add(listener);
-            return false;
-        }
-
-        @Override
-        public void unregisterListener(Listener listener) {
-            mListeners.remove(listener);
-        }
-
-        public void setState(boolean state) {
-            for (Listener listener : mListeners) {
-                listener.onCaptureStateChange(state);
-            }
-        }
-
-        public void verifyNoMoreListeners() {
-            assertEquals(0, mListeners.size());
-        }
-    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandlerTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandlerTest.java
new file mode 100644
index 0000000..6198925
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandlerTest.java
@@ -0,0 +1,313 @@
+/*
+ * 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.soundtrigger_middleware;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atMost;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import android.media.soundtrigger.RecognitionEvent;
+import android.media.soundtrigger.RecognitionStatus;
+
+import androidx.annotation.NonNull;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+
+@RunWith(JUnit4.class)
+public class SoundTriggerHalConcurrentCaptureHandlerTest {
+    private ISoundTriggerHal mUnderlying;
+    private CaptureStateNotifier mNotifier;
+    private ISoundTriggerHal.GlobalCallback mGlobalCallback;
+    private SoundTriggerHalConcurrentCaptureHandler mHandler;
+
+    @Before
+    public void setUp() {
+        mNotifier = new CaptureStateNotifier();
+        mUnderlying = mock(ISoundTriggerHal.class);
+        mGlobalCallback = mock(ISoundTriggerHal.GlobalCallback.class);
+        mHandler = new SoundTriggerHalConcurrentCaptureHandler(mUnderlying, mNotifier);
+        mHandler.registerCallback(mGlobalCallback);
+    }
+
+    @Test
+    public void testBasic() throws Exception {
+        ISoundTriggerHal.ModelCallback callback = mock(ISoundTriggerHal.ModelCallback.class);
+        int handle = mHandler.loadSoundModel(TestUtil.createGenericSoundModel(), callback);
+        verify(mUnderlying).loadSoundModel(any(), any());
+
+        mHandler.startRecognition(handle, 101, 102, TestUtil.createRecognitionConfig());
+        verify(mUnderlying).startRecognition(eq(handle), eq(101), eq(102), any());
+
+        mNotifier.setActive(true);
+        verify(mUnderlying).stopRecognition(handle);
+        ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+                RecognitionEvent.class);
+        Thread.sleep(50);
+        verify(callback).recognitionCallback(eq(handle), eventCaptor.capture());
+        RecognitionEvent event = eventCaptor.getValue();
+        assertEquals(event.status, RecognitionStatus.ABORTED);
+        assertFalse(event.recognitionStillActive);
+        verifyZeroInteractions(mGlobalCallback);
+        clearInvocations(callback, mUnderlying);
+
+        mNotifier.setActive(false);
+        Thread.sleep(50);
+        verify(mGlobalCallback).onResourcesAvailable();
+        verifyNoMoreInteractions(callback, mUnderlying);
+
+        mNotifier.setActive(true);
+        verifyNoMoreInteractions(callback, mUnderlying);
+    }
+
+    @Test
+    public void testStopBeforeActive() throws Exception {
+        ISoundTriggerHal.ModelCallback callback = mock(ISoundTriggerHal.ModelCallback.class);
+        int handle = mHandler.loadSoundModel(TestUtil.createGenericSoundModel(), callback);
+        verify(mUnderlying).loadSoundModel(any(), any());
+
+        mHandler.startRecognition(handle, 101, 102, TestUtil.createRecognitionConfig());
+        verify(mUnderlying).startRecognition(eq(handle), eq(101), eq(102), any());
+        mHandler.stopRecognition(handle);
+        verify(mUnderlying).stopRecognition(handle);
+        clearInvocations(mUnderlying);
+
+        mNotifier.setActive(true);
+        Thread.sleep(50);
+        verifyNoMoreInteractions(mUnderlying);
+        verifyNoMoreInteractions(callback);
+    }
+
+    @Test
+    public void testStopAfterActive() {
+        ISoundTriggerHal.ModelCallback callback = mock(ISoundTriggerHal.ModelCallback.class);
+        int handle = mHandler.loadSoundModel(TestUtil.createGenericSoundModel(), callback);
+        verify(mUnderlying).loadSoundModel(any(), any());
+
+        mHandler.startRecognition(handle, 101, 102, TestUtil.createRecognitionConfig());
+        verify(mUnderlying).startRecognition(eq(handle), eq(101), eq(102), any());
+
+        mNotifier.setActive(true);
+        verify(mUnderlying, times(1)).stopRecognition(handle);
+        mHandler.stopRecognition(handle);
+        verify(callback, times(1)).recognitionCallback(eq(handle), any());
+    }
+
+    @Test(timeout = 200)
+    public void testAbortWhileStop() {
+        ISoundTriggerHal.ModelCallback callback = mock(ISoundTriggerHal.ModelCallback.class);
+        int handle = mHandler.loadSoundModel(TestUtil.createGenericSoundModel(), callback);
+        ArgumentCaptor<ISoundTriggerHal.ModelCallback> modelCallbackCaptor =
+                ArgumentCaptor.forClass(ISoundTriggerHal.ModelCallback.class);
+        verify(mUnderlying).loadSoundModel(any(), modelCallbackCaptor.capture());
+        ISoundTriggerHal.ModelCallback modelCallback = modelCallbackCaptor.getValue();
+
+        mHandler.startRecognition(handle, 101, 102, TestUtil.createRecognitionConfig());
+        verify(mUnderlying).startRecognition(eq(handle), eq(101), eq(102), any());
+
+        doAnswer(invocation -> {
+            RecognitionEvent event = TestUtil.createRecognitionEvent(RecognitionStatus.ABORTED,
+                    false);
+            // Call the callback from a different thread to detect deadlocks by preventing recursive
+            // locking from working.
+            runOnSeparateThread(() -> modelCallback.recognitionCallback(handle, event));
+            return null;
+        }).when(mUnderlying).stopRecognition(handle);
+        mHandler.stopRecognition(handle);
+        verify(mUnderlying, times(1)).stopRecognition(handle);
+
+        ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+                RecognitionEvent.class);
+        verify(callback, atMost(1)).recognitionCallback(eq(handle), eventCaptor.capture());
+    }
+
+    @Test(timeout = 200)
+    public void testActiveWhileStop() {
+        ISoundTriggerHal.ModelCallback callback = mock(ISoundTriggerHal.ModelCallback.class);
+        int handle = mHandler.loadSoundModel(TestUtil.createGenericSoundModel(), callback);
+        ArgumentCaptor<ISoundTriggerHal.ModelCallback> modelCallbackCaptor =
+                ArgumentCaptor.forClass(ISoundTriggerHal.ModelCallback.class);
+        verify(mUnderlying).loadSoundModel(any(), modelCallbackCaptor.capture());
+        ISoundTriggerHal.ModelCallback modelCallback = modelCallbackCaptor.getValue();
+
+        mHandler.startRecognition(handle, 101, 102, TestUtil.createRecognitionConfig());
+        verify(mUnderlying).startRecognition(eq(handle), eq(101), eq(102), any());
+
+        doAnswer(invocation -> {
+            // The stop request causes a callback to be flushed.
+            RecognitionEvent event = TestUtil.createRecognitionEvent(RecognitionStatus.FORCED,
+                    true);
+            // Call the callback from a different thread to detect deadlocks by preventing recursive
+            // locking from working.
+            runOnSeparateThread(() -> modelCallback.recognitionCallback(handle, event));
+            // While the HAL is processing the stop request, capture state becomes active.
+            new Thread(() -> mNotifier.setActive(true)).start();
+            Thread.sleep(50);
+            return null;
+        }).when(mUnderlying).stopRecognition(handle);
+        mHandler.stopRecognition(handle);
+        // We only expect one underlying invocation of stop().
+        verify(mUnderlying, times(1)).stopRecognition(handle);
+
+        // The callback shouldn't be invoked in this case.
+        verify(callback, never()).recognitionCallback(eq(handle), any());
+    }
+
+    @Test(timeout = 200)
+    public void testStopWhileActive() {
+        ISoundTriggerHal.ModelCallback callback = mock(ISoundTriggerHal.ModelCallback.class);
+        int handle = mHandler.loadSoundModel(TestUtil.createGenericSoundModel(), callback);
+        ArgumentCaptor<ISoundTriggerHal.ModelCallback> modelCallbackCaptor =
+                ArgumentCaptor.forClass(ISoundTriggerHal.ModelCallback.class);
+        verify(mUnderlying).loadSoundModel(any(), modelCallbackCaptor.capture());
+        ISoundTriggerHal.ModelCallback modelCallback = modelCallbackCaptor.getValue();
+
+        mHandler.startRecognition(handle, 101, 102, TestUtil.createRecognitionConfig());
+        verify(mUnderlying).startRecognition(eq(handle), eq(101), eq(102), any());
+
+        doAnswer(invocation -> {
+            // The stop request causes a callback to be flushed.
+            RecognitionEvent event = TestUtil.createRecognitionEvent(RecognitionStatus.FORCED,
+                    true);
+            // Call the callback from a different thread to detect deadlocks by preventing recursive
+            // locking from working.
+            runOnSeparateThread(() -> modelCallback.recognitionCallback(handle, event));
+            // While the HAL is processing the stop request, client requests stop.
+            new Thread(() -> mHandler.stopRecognition(handle)).start();
+            Thread.sleep(50);
+            return null;
+        }).when(mUnderlying).stopRecognition(handle);
+        mNotifier.setActive(true);
+        // We only expect one underlying invocation of stop().
+        verify(mUnderlying, times(1)).stopRecognition(handle);
+        verify(callback, atMost(1)).recognitionCallback(eq(handle), any());
+    }
+
+    @Test(timeout = 200)
+    public void testEventWhileActive() throws Exception {
+        ISoundTriggerHal.ModelCallback callback = mock(ISoundTriggerHal.ModelCallback.class);
+        int handle = mHandler.loadSoundModel(TestUtil.createGenericSoundModel(), callback);
+        ArgumentCaptor<ISoundTriggerHal.ModelCallback> modelCallbackCaptor =
+                ArgumentCaptor.forClass(ISoundTriggerHal.ModelCallback.class);
+        verify(mUnderlying).loadSoundModel(any(), modelCallbackCaptor.capture());
+        ISoundTriggerHal.ModelCallback modelCallback = modelCallbackCaptor.getValue();
+
+        mHandler.startRecognition(handle, 101, 102, TestUtil.createRecognitionConfig());
+        verify(mUnderlying).startRecognition(eq(handle), eq(101), eq(102), any());
+
+        doAnswer(invocation -> {
+            RecognitionEvent event = TestUtil.createRecognitionEvent(RecognitionStatus.SUCCESS,
+                    false);
+            // Call the callback from a different thread to detect deadlocks by preventing recursive
+            // locking from working.
+            runOnSeparateThread(() -> modelCallback.recognitionCallback(handle, event));
+            return null;
+        }).when(mUnderlying).stopRecognition(handle);
+        mNotifier.setActive(true);
+        verify(mUnderlying, times(1)).stopRecognition(handle);
+        Thread.sleep(50);
+
+        ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+                RecognitionEvent.class);
+        verify(callback, atMost(2)).recognitionCallback(eq(handle), eventCaptor.capture());
+        RecognitionEvent lastEvent = eventCaptor.getValue();
+        assertEquals(lastEvent.status, RecognitionStatus.ABORTED);
+        assertFalse(lastEvent.recognitionStillActive);
+    }
+
+
+    @Test(timeout = 200)
+    public void testNonFinalEventWhileActive() throws Exception {
+        ISoundTriggerHal.ModelCallback callback = mock(ISoundTriggerHal.ModelCallback.class);
+        int handle = mHandler.loadSoundModel(TestUtil.createGenericSoundModel(), callback);
+        ArgumentCaptor<ISoundTriggerHal.ModelCallback> modelCallbackCaptor =
+                ArgumentCaptor.forClass(ISoundTriggerHal.ModelCallback.class);
+        verify(mUnderlying).loadSoundModel(any(), modelCallbackCaptor.capture());
+        ISoundTriggerHal.ModelCallback modelCallback = modelCallbackCaptor.getValue();
+
+        mHandler.startRecognition(handle, 101, 102, TestUtil.createRecognitionConfig());
+        verify(mUnderlying).startRecognition(eq(handle), eq(101), eq(102), any());
+
+        doAnswer(invocation -> {
+            RecognitionEvent event = TestUtil.createRecognitionEvent(RecognitionStatus.FORCED,
+                    true);
+            // Call the callback from a different thread to detect deadlocks by preventing recursive
+            // locking from working.
+            runOnSeparateThread(() -> modelCallback.recognitionCallback(handle, event));
+
+            return null;
+        }).when(mUnderlying).stopRecognition(handle);
+        mNotifier.setActive(true);
+        verify(mUnderlying, times(1)).stopRecognition(handle);
+
+        Thread.sleep(50);
+        ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+                RecognitionEvent.class);
+        verify(callback, atMost(2)).recognitionCallback(eq(handle), eventCaptor.capture());
+        RecognitionEvent lastEvent = eventCaptor.getValue();
+        assertEquals(lastEvent.status, RecognitionStatus.ABORTED);
+        assertFalse(lastEvent.recognitionStillActive);
+    }
+
+    private static void runOnSeparateThread(Runnable runnable) {
+        Thread thread = new Thread(runnable);
+        thread.start();
+        try {
+            thread.join();
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static class CaptureStateNotifier implements ICaptureStateNotifier {
+        boolean mActive = false;
+        Listener mListener;
+
+        @Override
+        public boolean registerListener(@NonNull Listener listener) {
+            mListener = listener;
+            return mActive;
+        }
+
+        @Override
+        public void unregisterListener(@NonNull Listener listener) {
+            mListener = null;
+        }
+
+        public void setActive(boolean active) {
+            mActive = active;
+            if (mListener != null) {
+                // Call the callback from a different thread to detect deadlocks by preventing
+                // recursive locking from working.
+                runOnSeparateThread(() -> mListener.onCaptureStateChange(mActive));
+            }
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
index 0187e34..3bebc94 100644
--- a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
@@ -134,7 +134,7 @@
     public void setUp() throws Exception {
         clearInvocations(mHalDriver);
         clearInvocations(mAudioSessionProvider);
-        when(mHalDriver.getProperties()).thenReturn(TestUtil.createDefaultProperties(false));
+        when(mHalDriver.getProperties()).thenReturn(TestUtil.createDefaultProperties());
         mService = new SoundTriggerMiddlewareImpl(() -> mHalDriver, mAudioSessionProvider);
     }
 
@@ -156,7 +156,7 @@
         assertEquals(1, allDescriptors.length);
 
         Properties properties = allDescriptors[0].properties;
-        assertEquals(TestUtil.createDefaultProperties(false), properties);
+        assertEquals(TestUtil.createDefaultProperties(), properties);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/TestUtil.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/TestUtil.java
index 30b4a59..39561f7 100644
--- a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/TestUtil.java
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/TestUtil.java
@@ -162,8 +162,8 @@
                 phrases.get(0).recognitionModes);
     }
 
-    static android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties createDefaultProperties_2_0(
-            boolean supportConcurrentCapture) {
+    static android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties
+            createDefaultProperties_2_0() {
         android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties properties =
                 new android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties();
         properties.implementor = "implementor";
@@ -185,17 +185,16 @@
                         | android.hardware.soundtrigger.V2_0.RecognitionMode.GENERIC_TRIGGER;
         properties.captureTransition = true;
         properties.maxBufferMs = 321;
-        properties.concurrentCapture = supportConcurrentCapture;
+        properties.concurrentCapture = true;
         properties.triggerInEvent = true;
         properties.powerConsumptionMw = 432;
         return properties;
     }
 
-    static android.hardware.soundtrigger.V2_3.Properties createDefaultProperties_2_3(
-            boolean supportConcurrentCapture) {
+    static android.hardware.soundtrigger.V2_3.Properties createDefaultProperties_2_3() {
         android.hardware.soundtrigger.V2_3.Properties properties =
                 new android.hardware.soundtrigger.V2_3.Properties();
-        properties.base = createDefaultProperties_2_0(supportConcurrentCapture);
+        properties.base = createDefaultProperties_2_0();
         properties.supportedModelArch = "supportedModelArch";
         properties.audioCapabilities =
                 android.hardware.soundtrigger.V2_3.AudioCapabilities.ECHO_CANCELLATION
@@ -203,7 +202,7 @@
         return properties;
     }
 
-    static Properties createDefaultProperties(boolean supportConcurrentCapture) {
+    static Properties createDefaultProperties() {
         Properties properties = new Properties();
         properties.implementor = "implementor";
         properties.description = "description";
@@ -217,7 +216,7 @@
                         | RecognitionMode.USER_AUTHENTICATION | RecognitionMode.GENERIC_TRIGGER;
         properties.captureTransition = true;
         properties.maxBufferMs = 321;
-        properties.concurrentCapture = supportConcurrentCapture;
+        properties.concurrentCapture = true;
         properties.triggerInEvent = true;
         properties.powerConsumptionMw = 432;
         properties.supportedModelArch = "supportedModelArch";
@@ -226,13 +225,13 @@
         return properties;
     }
 
-    static void validateDefaultProperties(Properties properties, boolean supportConcurrentCapture) {
-        validateDefaultProperties(properties, supportConcurrentCapture,
+    static void validateDefaultProperties(Properties properties) {
+        validateDefaultProperties(properties,
                 AudioCapabilities.ECHO_CANCELLATION | AudioCapabilities.NOISE_SUPPRESSION,
                 "supportedModelArch");
     }
 
-    static void validateDefaultProperties(Properties properties, boolean supportConcurrentCapture,
+    static void validateDefaultProperties(Properties properties,
             @AudioCapabilities int audioCapabilities, @NonNull String supportedModelArch) {
         assertEquals("implementor", properties.implementor);
         assertEquals("description", properties.description);
@@ -246,7 +245,7 @@
                 properties.recognitionModes);
         assertTrue(properties.captureTransition);
         assertEquals(321, properties.maxBufferMs);
-        assertEquals(supportConcurrentCapture, properties.concurrentCapture);
+        assertEquals(true, properties.concurrentCapture);
         assertTrue(properties.triggerInEvent);
         assertEquals(432, properties.powerConsumptionMw);
         assertEquals(supportedModelArch, properties.supportedModelArch);
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 bfdffc0..20486b3 100644
--- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
@@ -429,18 +429,40 @@
     public void readPermissions_allowLibs_parsesSimpleLibrary() throws IOException {
         String contents =
                 "<permissions>\n"
-                + "    <library \n"
-                + "        name=\"foo\"\n"
-                + "        file=\"" + mFooJar + "\"\n"
-                + "        on-bootclasspath-before=\"10\"\n"
-                + "        on-bootclasspath-since=\"20\"\n"
-                + "     />\n\n"
-                + " </permissions>";
+                        + "    <library \n"
+                        + "        name=\"foo\"\n"
+                        + "        file=\"" + mFooJar + "\"\n"
+                        + "        on-bootclasspath-before=\"10\"\n"
+                        + "        on-bootclasspath-since=\"20\"\n"
+                        + "     />\n\n"
+                        + " </permissions>";
         parseSharedLibraries(contents);
         assertFooIsOnlySharedLibrary();
         SystemConfig.SharedLibraryEntry entry = mSysConfig.getSharedLibraries().get("foo");
-        assertThat(entry.onBootclasspathBefore).isEqualTo(10);
-        assertThat(entry.onBootclasspathSince).isEqualTo(20);
+        assertThat(entry.onBootclasspathBefore).isEqualTo("10");
+        assertThat(entry.onBootclasspathSince).isEqualTo("20");
+    }
+
+    /**
+     * Tests that readPermissions works correctly for a library with on-bootclasspath-before
+     * and on-bootclasspath-since that uses codenames.
+     */
+    @Test
+    public void readPermissions_allowLibs_parsesSimpleLibraryWithCodenames() throws IOException {
+        String contents =
+                "<permissions>\n"
+                        + "    <library \n"
+                        + "        name=\"foo\"\n"
+                        + "        file=\"" + mFooJar + "\"\n"
+                        + "        on-bootclasspath-before=\"Q\"\n"
+                        + "        on-bootclasspath-since=\"W\"\n"
+                        + "     />\n\n"
+                        + " </permissions>";
+        parseSharedLibraries(contents);
+        assertFooIsOnlySharedLibrary();
+        SystemConfig.SharedLibraryEntry entry = mSysConfig.getSharedLibraries().get("foo");
+        assertThat(entry.onBootclasspathBefore).isEqualTo("Q");
+        assertThat(entry.onBootclasspathSince).isEqualTo("W");
     }
 
     /**
@@ -461,8 +483,8 @@
         parseSharedLibraries(contents);
         assertFooIsOnlySharedLibrary();
         SystemConfig.SharedLibraryEntry entry = mSysConfig.getSharedLibraries().get("foo");
-        assertThat(entry.onBootclasspathBefore).isEqualTo(10);
-        assertThat(entry.onBootclasspathSince).isEqualTo(20);
+        assertThat(entry.onBootclasspathBefore).isEqualTo("10");
+        assertThat(entry.onBootclasspathSince).isEqualTo("20");
     }
 
     /**
@@ -543,12 +565,20 @@
      */
     @Test
     public void readPermissions_allowLibs_allowsCurrentMaxSdk() throws IOException {
+        // depending on whether this test is running before or after finalization, we need to
+        // pass a different parameter
+        String parameter;
+        if ("REL".equals(Build.VERSION.CODENAME)) {
+            parameter = "" + Build.VERSION.SDK_INT;
+        } else {
+            parameter = "ZZZ";
+        }
         String contents =
                 "<permissions>\n"
                 + "    <library \n"
                 + "        name=\"foo\"\n"
                 + "        file=\"" + mFooJar + "\"\n"
-                + "        max-device-sdk=\"" + Build.VERSION.SDK_INT + "\"\n"
+                + "        max-device-sdk=\"" + parameter + "\"\n"
                 + "     />\n\n"
                 + " </permissions>";
         parseSharedLibraries(contents);
diff --git a/services/tests/servicestests/src/com/android/server/utils/WatcherTest.java b/services/tests/servicestests/src/com/android/server/utils/WatcherTest.java
index 37c95f7..4ed4c23 100644
--- a/services/tests/servicestests/src/com/android/server/utils/WatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/utils/WatcherTest.java
@@ -17,7 +17,6 @@
 package com.android.server.utils;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -861,54 +860,6 @@
         }
     }
 
-    @Test
-    public void testWatchedSparseSetArray() {
-        final String name = "WatchedSparseSetArray";
-        WatchableTester tester;
-
-        // Test WatchedSparseSetArray
-        WatchedSparseSetArray array = new WatchedSparseSetArray();
-        tester = new WatchableTester(array, name);
-        tester.verify(0, "Initial array - no registration");
-        array.add(INDEX_A, 1);
-        tester.verify(0, "Updates with no registration");
-        tester.register();
-        tester.verify(0, "Updates with no registration");
-        array.add(INDEX_B, 2);
-        tester.verify(1, "Updates with registration");
-        array.add(INDEX_B, 4);
-        array.add(INDEX_C, 5);
-        tester.verify(3, "Updates with registration");
-        // Special methods
-        assertTrue(array.remove(INDEX_C, 5));
-        tester.verify(4, "Removed 5 from key 3");
-        array.remove(INDEX_B);
-        tester.verify(5, "Removed everything for key 2");
-
-        // Snapshot
-        {
-            WatchedSparseSetArray arraySnap = (WatchedSparseSetArray) array.snapshot();
-            tester.verify(5, "Generate snapshot");
-            // Verify that the snapshot is a proper copy of the source.
-            assertEquals("WatchedSparseSetArray snap same size",
-                    array.size(), arraySnap.size());
-            for (int i = 0; i < array.size(); i++) {
-                ArraySet set = array.get(array.keyAt(i));
-                ArraySet setSnap = arraySnap.get(arraySnap.keyAt(i));
-                assertNotNull(set);
-                assertTrue(set.equals(setSnap));
-            }
-            array.add(INDEX_D, 9);
-            tester.verify(6, "Tick after snapshot");
-            // Verify that the array is sealed
-            verifySealed(name, ()->arraySnap.add(INDEX_D, 10));
-            assertTrue(!array.isSealed());
-            assertTrue(arraySnap.isSealed());
-        }
-        array.clear();
-        tester.verify(7, "Cleared all entries");
-    }
-
     private static class IndexGenerator {
         private final int mSeed;
         private final Random mRandom;
@@ -1133,18 +1084,6 @@
         assertEquals(a.equals(s), true);
         a.put(rowIndex, colIndex, !a.get(rowIndex, colIndex));
         assertEquals(a.equals(s), false);
-
-        // Verify copy-in/out
-        {
-            final String msg = name + " copy";
-            WatchedSparseBooleanMatrix copy = new WatchedSparseBooleanMatrix();
-            copy.copyFrom(matrix);
-            final int end = copy.size();
-            assertTrue(msg + " size mismatch " + end + " " + matrix.size(), end == matrix.size());
-            for (int i = 0; i < end; i++) {
-                assertEquals(copy.keyAt(i), keys[i]);
-            }
-        }
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
index 9f13591..de5f6ed 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -276,7 +276,7 @@
     }
 
     @Test
-    public void vibrate_singleVibratorRepeatingShortAlwaysOnWaveform_turnsVibratorOnForASecond()
+    public void vibrate_singleVibratorRepeatingShortAlwaysOnWaveform_turnsVibratorOnForLonger()
             throws Exception {
         FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
         fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
@@ -293,11 +293,71 @@
 
         verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
         assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
-        assertEquals(Arrays.asList(expectedOneShot(1000)),
+        assertEquals(Arrays.asList(expectedOneShot(5000)),
                 fakeVibrator.getEffectSegments(vibrationId));
     }
 
     @Test
+    public void vibrate_singleVibratorRepeatingPwle_generatesLargestPwles() throws Exception {
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
+        fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
+        fakeVibrator.setMinFrequency(100);
+        fakeVibrator.setResonantFrequency(150);
+        fakeVibrator.setFrequencyResolution(50);
+        fakeVibrator.setMaxAmplitudes(1, 1, 1);
+        fakeVibrator.setPwleSizeMax(10);
+
+        long vibrationId = 1;
+        VibrationEffect effect = VibrationEffect.startWaveform(targetAmplitude(1))
+                // Very long segment so thread will be cancelled after first PWLE is triggered.
+                .addTransition(Duration.ofMillis(100), targetFrequency(100))
+                .build();
+        VibrationEffect repeatingEffect = VibrationEffect.startComposition()
+                .repeatEffectIndefinitely(effect)
+                .compose();
+        VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, repeatingEffect);
+
+        assertTrue(waitUntil(() -> !fakeVibrator.getEffectSegments(vibrationId).isEmpty(),
+                TEST_TIMEOUT_MILLIS));
+        conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false);
+        waitForCompletion();
+
+        // PWLE size max was used to generate a single vibrate call with 10 segments.
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
+        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+        assertEquals(10, fakeVibrator.getEffectSegments(vibrationId).size());
+    }
+
+    @Test
+    public void vibrate_singleVibratorRepeatingPrimitives_generatesLargestComposition()
+            throws Exception {
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
+        fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        fakeVibrator.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK);
+        fakeVibrator.setCompositionSizeMax(10);
+
+        long vibrationId = 1;
+        VibrationEffect effect = VibrationEffect.startComposition()
+                // Very long delay so thread will be cancelled after first PWLE is triggered.
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
+                .compose();
+        VibrationEffect repeatingEffect = VibrationEffect.startComposition()
+                .repeatEffectIndefinitely(effect)
+                .compose();
+        VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, repeatingEffect);
+
+        assertTrue(waitUntil(() -> !fakeVibrator.getEffectSegments(vibrationId).isEmpty(),
+                TEST_TIMEOUT_MILLIS));
+        conductor.notifyCancelled(Vibration.Status.CANCELLED_SUPERSEDED, /* immediate= */ false);
+        waitForCompletion();
+
+        // Composition size max was used to generate a single vibrate call with 10 primitives.
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_SUPERSEDED);
+        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+        assertEquals(10, fakeVibrator.getEffectSegments(vibrationId).size());
+    }
+
+    @Test
     public void vibrate_singleVibratorRepeatingLongAlwaysOnWaveform_turnsVibratorOnForACycle()
             throws Exception {
         FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
@@ -319,7 +379,7 @@
                 fakeVibrator.getEffectSegments(vibrationId));
     }
 
-
+    @LargeTest
     @Test
     public void vibrate_singleVibratorRepeatingAlwaysOnWaveform_turnsVibratorBackOn()
             throws Exception {
@@ -329,22 +389,21 @@
         long vibrationId = 1;
         int[] amplitudes = new int[]{1, 2};
         VibrationEffect effect = VibrationEffect.createWaveform(
-                new long[]{900, 50}, amplitudes, 0);
+                new long[]{4900, 50}, amplitudes, 0);
         VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);
 
-        assertTrue(waitUntil(() -> fakeVibrator.getAmplitudes().size() > 2 * amplitudes.length,
-                1000 + TEST_TIMEOUT_MILLIS));
+        assertTrue(waitUntil(() -> fakeVibrator.getEffectSegments(vibrationId).size() > 1,
+                5000 + TEST_TIMEOUT_MILLIS));
         conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false);
         waitForCompletion();
 
         verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
         assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
-        assertEquals(2, fakeVibrator.getEffectSegments(vibrationId).size());
-        // First time turn vibrator ON for minimum of 1s.
-        assertEquals(1000L, fakeVibrator.getEffectSegments(vibrationId).get(0).getDuration());
+        // First time turn vibrator ON for minimum of 5s.
+        assertEquals(5000L, fakeVibrator.getEffectSegments(vibrationId).get(0).getDuration());
         // Vibrator turns off in the middle of the second execution of first step, turn it back ON
-        // for another 1s + remaining of 850ms.
-        assertEquals(1850,
+        // for another 5s + remaining of 850ms.
+        assertEquals(4900 + 50 + 4900,
                 fakeVibrator.getEffectSegments(vibrationId).get(1).getDuration(), /* delta= */ 20);
         // Set amplitudes for a cycle {1, 2}, start second loop then turn it back on to same value.
         assertEquals(expectedAmplitudes(1, 2, 1, 1),
@@ -530,12 +589,18 @@
 
     @Test
     public void vibrate_singleVibratorComposedEffects_runsDifferentVibrations() throws Exception {
-        mVibratorProviders.get(VIBRATOR_ID).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
-        mVibratorProviders.get(VIBRATOR_ID).setSupportedPrimitives(
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
+        fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        fakeVibrator.setSupportedPrimitives(
                 VibrationEffect.Composition.PRIMITIVE_CLICK,
                 VibrationEffect.Composition.PRIMITIVE_TICK);
-        mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS,
-                IVibrator.CAP_AMPLITUDE_CONTROL);
+        fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS,
+                IVibrator.CAP_COMPOSE_PWLE_EFFECTS, IVibrator.CAP_AMPLITUDE_CONTROL);
+        fakeVibrator.setMinFrequency(100);
+        fakeVibrator.setResonantFrequency(150);
+        fakeVibrator.setFrequencyResolution(50);
+        fakeVibrator.setMaxAmplitudes(
+                0.5f /* 100Hz*/, 1 /* 150Hz */, 0.6f /* 200Hz */);
 
         long vibrationId = 1;
         VibrationEffect effect = VibrationEffect.startComposition()
@@ -543,7 +608,11 @@
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
                 .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
-                .addOffDuration(Duration.ofMillis(100))
+                .addEffect(VibrationEffect.startWaveform()
+                        .addTransition(Duration.ofMillis(10),
+                                targetAmplitude(1), targetFrequency(100))
+                        .addTransition(Duration.ofMillis(20), targetFrequency(120))
+                        .build())
                 .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
                 .compose();
         startThreadAndDispatcher(vibrationId, effect);
@@ -552,7 +621,7 @@
         // Use first duration the vibrator is turned on since we cannot estimate the clicks.
         verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
         verify(mManagerHooks).noteVibratorOff(eq(UID));
-        verify(mControllerCallbacks, times(4)).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
+        verify(mControllerCallbacks, times(5)).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
         verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
         assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
         assertEquals(Arrays.asList(
@@ -560,6 +629,10 @@
                 expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0),
                 expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f, 0),
                 expectedPrebaked(VibrationEffect.EFFECT_CLICK),
+                expectedRamp(/* startAmplitude= */ 0, /* endAmplitude= */ 0.5f,
+                        /* startFrequencyHz= */ 150, /* endFrequencyHz= */ 100, /* duration= */ 10),
+                expectedRamp(/* startAmplitude= */ 0.5f, /* endAmplitude= */ 0.7f,
+                        /* startFrequencyHz= */ 100, /* endFrequencyHz= */ 120, /* duration= */ 20),
                 expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
                 mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
         assertEquals(expectedAmplitudes(100), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes());
@@ -605,30 +678,36 @@
     }
 
     @Test
-    public void vibrate_singleVibratorLargePwle_splitsVibratorComposeCalls() {
+    public void vibrate_singleVibratorLargePwle_splitsComposeCallWhenAmplitudeIsLowest() {
         FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
         fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
         fakeVibrator.setMinFrequency(100);
         fakeVibrator.setResonantFrequency(150);
         fakeVibrator.setFrequencyResolution(50);
         fakeVibrator.setMaxAmplitudes(1, 1, 1);
-        fakeVibrator.setPwleSizeMax(2);
+        fakeVibrator.setPwleSizeMax(3);
 
         long vibrationId = 1;
         VibrationEffect effect = VibrationEffect.startWaveform(targetAmplitude(1))
                 .addSustain(Duration.ofMillis(10))
                 .addTransition(Duration.ofMillis(20), targetAmplitude(0))
+                // Waveform will be split here, after vibration goes to zero amplitude
                 .addTransition(Duration.ZERO, targetAmplitude(0.8f), targetFrequency(100))
                 .addSustain(Duration.ofMillis(30))
                 .addTransition(Duration.ofMillis(40), targetAmplitude(0.6f), targetFrequency(200))
+                // Waveform will be split here at lowest amplitude.
+                .addTransition(Duration.ofMillis(40), targetAmplitude(0.7f), targetFrequency(200))
+                .addTransition(Duration.ofMillis(40), targetAmplitude(0.6f), targetFrequency(200))
                 .build();
         startThreadAndDispatcher(vibrationId, effect);
         waitForCompletion();
 
         verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
-        // Vibrator compose called twice.
-        verify(mControllerCallbacks, times(2)).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
-        assertEquals(4, fakeVibrator.getEffectSegments(vibrationId).size());
+
+        // Vibrator compose called 3 times with 2 segments instead of 2 times with 3 segments.
+        // Using best split points instead of max-packing PWLEs.
+        verify(mControllerCallbacks, times(3)).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
+        assertEquals(6, fakeVibrator.getEffectSegments(vibrationId).size());
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index 9c72ce2..4fbf006 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -602,10 +602,15 @@
                 VibrationEffect.EFFECT_HEAVY_CLICK, VibrationEffect.EFFECT_DOUBLE_CLICK);
         VibratorManagerService service = createSystemReadyService();
         mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
+
+        // The haptic feedback should be ignored in low power, but not the ringtone. The end
+        // of the test asserts which actual effects ended up playing.
         vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), HAPTIC_FEEDBACK_ATTRS);
         vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), RINGTONE_ATTRS);
         assertTrue(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() == 1,
                 service, TEST_TIMEOUT_MILLIS));
+        // Allow the ringtone to complete, as the other vibrations won't cancel it.
+        assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
 
         mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE);
         vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK),
@@ -815,6 +820,29 @@
     }
 
     @Test
+    public void vibrate_withOngoingRingtoneVibration_ignoresEffect() throws Exception {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+        VibratorManagerService service = createSystemReadyService();
+
+        VibrationEffect alarmEffect = VibrationEffect.createWaveform(
+                new long[]{10_000, 10_000}, new int[]{128, 255}, -1);
+        vibrate(service, alarmEffect, new VibrationAttributes.Builder().setUsage(
+                VibrationAttributes.USAGE_RINGTONE).build());
+
+        // VibrationThread will start this vibration async, so wait before checking it started.
+        assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
+                service, TEST_TIMEOUT_MILLIS));
+
+        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
+                HAPTIC_FEEDBACK_ATTRS);
+
+        // Wait before checking it never played a second effect.
+        assertFalse(waitUntil(s -> mVibratorProviders.get(1).getAllEffectSegments().size() > 1,
+                service, /* timeout= */ 50));
+    }
+
+    @Test
     public void vibrate_withInputDevices_vibratesInputDevices() throws Exception {
         mockVibrators(1);
         mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
@@ -1315,6 +1343,24 @@
         assertNotEquals(IExternalVibratorService.SCALE_MUTE, scale);
     }
 
+    @Test
+    public void onExternalVibration_withUnknownUsage_appliesMediaSettings() {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+        setUserSetting(Settings.System.MEDIA_VIBRATION_INTENSITY,
+                Vibrator.VIBRATION_INTENSITY_OFF);
+        AudioAttributes flaggedAudioAttrs = new AudioAttributes.Builder()
+                .setUsage(AudioAttributes.USAGE_UNKNOWN)
+                .setFlags(AudioAttributes.FLAG_BYPASS_MUTE)
+                .build();
+        createSystemReadyService();
+
+        int scale = mExternalVibratorService.onExternalVibrationStart(
+                new ExternalVibration(/* uid= */ 123, PACKAGE_NAME, flaggedAudioAttrs,
+                        mock(IExternalVibrationController.class)));
+        assertEquals(IExternalVibratorService.SCALE_MUTE, scale);
+    }
+
     private VibrationEffectSegment expectedPrebaked(int effectId) {
         return expectedPrebaked(effectId, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
     }
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 9902e83..908de34 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -1139,18 +1139,8 @@
                 true /* createdByOrganizer */);
         sourceRecord.getTask().addChild(taskFragment, POSITION_TOP);
 
-        starter.startActivityInner(
-                /* r */targetRecord,
-                /* sourceRecord */ sourceRecord,
-                /* voiceSession */null,
-                /* voiceInteractor */ null,
-                /* startFlags */ 0,
-                /* doResume */true,
-                /* options */null,
-                /* inTask */null,
-                /* inTaskFragment */ taskFragment,
-                /* restrictedBgActivity */false,
-                /* intentGrants */null);
+        startActivityInner(starter, targetRecord, sourceRecord, null /* options */,
+                null /* inTask */, taskFragment);
 
         assertFalse(taskFragment.hasChild());
     }
@@ -1167,18 +1157,8 @@
         taskFragment.setTaskFragmentOrganizer(mock(TaskFragmentOrganizerToken.class), SYSTEM_UID,
                 "system_uid");
 
-        starter.startActivityInner(
-                /* r */targetRecord,
-                /* sourceRecord */ sourceRecord,
-                /* voiceSession */null,
-                /* voiceInteractor */ null,
-                /* startFlags */ 0,
-                /* doResume */true,
-                /* options */null,
-                /* inTask */null,
-                /* inTaskFragment */ taskFragment,
-                /* restrictedBgActivity */false,
-                /* intentGrants */null);
+        startActivityInner(starter, targetRecord, sourceRecord, null /* options */,
+                null /* inTask */, taskFragment);
 
         assertTrue(taskFragment.hasChild());
     }
@@ -1195,18 +1175,8 @@
         taskFragment.setTaskFragmentOrganizer(mock(TaskFragmentOrganizerToken.class),
                 targetRecord.getUid(), "test_process_name");
 
-        starter.startActivityInner(
-                /* r */targetRecord,
-                /* sourceRecord */ sourceRecord,
-                /* voiceSession */null,
-                /* voiceInteractor */ null,
-                /* startFlags */ 0,
-                /* doResume */true,
-                /* options */null,
-                /* inTask */null,
-                /* inTaskFragment */ taskFragment,
-                /* restrictedBgActivity */false,
-                /* intentGrants */null);
+        startActivityInner(starter, targetRecord, sourceRecord, null /* options */,
+                null /* inTask */, taskFragment);
 
         assertTrue(taskFragment.hasChild());
     }
@@ -1231,18 +1201,8 @@
         doReturn(true).when(signingDetails).hasAncestorOrSelfWithDigest(any());
         doReturn(signingDetails).when(androidPackage).getSigningDetails();
 
-        starter.startActivityInner(
-                /* r */targetRecord,
-                /* sourceRecord */ sourceRecord,
-                /* voiceSession */null,
-                /* voiceInteractor */ null,
-                /* startFlags */ 0,
-                /* doResume */true,
-                /* options */null,
-                /* inTask */null,
-                /* inTaskFragment */ taskFragment,
-                /* restrictedBgActivity */false,
-                /* intentGrants */null);
+        startActivityInner(starter, targetRecord, sourceRecord, null /* options */,
+                null /* inTask */, taskFragment);
 
         assertTrue(taskFragment.hasChild());
     }
@@ -1258,23 +1218,30 @@
 
         targetRecord.info.flags |= ActivityInfo.FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING;
 
-        starter.startActivityInner(
-                /* r */targetRecord,
-                /* sourceRecord */ sourceRecord,
-                /* voiceSession */null,
-                /* voiceInteractor */ null,
-                /* startFlags */ 0,
-                /* doResume */true,
-                /* options */null,
-                /* inTask */null,
-                /* inTaskFragment */ taskFragment,
-                /* restrictedBgActivity */false,
-                /* intentGrants */null);
+        startActivityInner(starter, targetRecord, sourceRecord, null /* options */,
+                null /* inTask */, taskFragment);
 
         assertTrue(taskFragment.hasChild());
     }
 
     @Test
+    public void testStartActivityInner_inTask() {
+        final ActivityStarter starter = prepareStarter(0, false);
+        // Simulate an app uses AppTask to create a non-attached task, and then it requests to
+        // start activity in the task.
+        final Task inTask = new TaskBuilder(mSupervisor).setTaskDisplayArea(null).setTaskId(123)
+                .build();
+        inTask.inRecents = true;
+        assertFalse(inTask.isAttached());
+        final ActivityRecord target = new ActivityBuilder(mAtm).build();
+        startActivityInner(starter, target, null /* source */, null /* options */, inTask,
+                null /* inTaskFragment */);
+
+        assertTrue(inTask.isAttached());
+        assertEquals(inTask, target.getTask());
+    }
+
+    @Test
     public void testLaunchCookie_newAndExistingTask() {
         final ActivityStarter starter = prepareStarter(0, false);
 
@@ -1322,21 +1289,20 @@
 
         // Start the target launch-into-pip activity from a source
         final ActivityRecord sourceRecord = new ActivityBuilder(mAtm).setCreateTask(true).build();
-        starter.startActivityInner(
-                /* r */ targetRecord,
-                /* sourceRecord */ sourceRecord,
-                /* voiceSession */ null,
-                /* voiceInteractor */ null,
-                /* startFlags */ 0,
-                /* doResume */ true,
-                /* options */ opts,
-                /* inTask */ null,
-                /* inTaskFragment */ null,
-                /* restrictedBgActivity */ false,
-                /* intentGrants */ null);
+        startActivityInner(starter, targetRecord, sourceRecord, opts,
+                null /* inTask */, null /* inTaskFragment */);
 
         // Verify the ActivityRecord#getLaunchIntoPipHostActivity points to sourceRecord.
         assertThat(targetRecord.getLaunchIntoPipHostActivity()).isNotNull();
         assertEquals(targetRecord.getLaunchIntoPipHostActivity(), sourceRecord);
     }
+
+    private static void startActivityInner(ActivityStarter starter, ActivityRecord target,
+            ActivityRecord source, ActivityOptions options, Task inTask,
+            TaskFragment inTaskFragment) {
+        starter.startActivityInner(target, source, null /* voiceSession */,
+                null /* voiceInteractor */, 0 /* startFlags */, true /* doResume */,
+                options, inTask, inTaskFragment, false /* restrictedBgActivity */,
+                null /* intentGrants */);
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index 97d477f..33b7024 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -861,13 +861,13 @@
 
     @Test
     public void testOverrideTaskFragmentAdapter_overrideWithEmbeddedActivity() {
+        final Task task = createTask(mDisplayContent);
         final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
         final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
-        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
+        setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
 
         // Create a TaskFragment with embedded activity.
-        final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(
-                createTask(mDisplayContent), organizer);
+        final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
         final ActivityRecord activity = taskFragment.getTopMostActivity();
         prepareActivityForAppTransition(activity);
         spyOn(mDisplayContent.mAppTransition);
@@ -882,11 +882,11 @@
 
     @Test
     public void testOverrideTaskFragmentAdapter_overrideWithNonEmbeddedActivity() {
+        final Task task = createTask(mDisplayContent);
         final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
         final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
-        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
+        setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
 
-        final Task task = createTask(mDisplayContent);
         // Closing non-embedded activity.
         final ActivityRecord closingActivity = createActivityRecord(task);
         prepareActivityForAppTransition(closingActivity);
@@ -907,11 +907,11 @@
 
     @Test
     public void testOverrideTaskFragmentAdapter_overrideEmbeddedActivityWithDiffUid() {
+        final Task task = createTask(mDisplayContent);
         final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
         final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
-        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
+        setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
 
-        final Task task = createTask(mDisplayContent);
         // Closing TaskFragment with embedded activity.
         final TaskFragment taskFragment1 = createTaskFragmentWithEmbeddedActivity(task, organizer);
         final ActivityRecord closingActivity = taskFragment1.getTopMostActivity();
@@ -934,16 +934,16 @@
 
     @Test
     public void testOverrideTaskFragmentAdapter_noOverrideWithTwoApps() {
+        final Task task = createTask(mDisplayContent);
         final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
         final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
-        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
+        setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
 
         // Closing activity in Task1.
         final ActivityRecord closingActivity = createActivityRecord(mDisplayContent);
         prepareActivityForAppTransition(closingActivity);
         // Opening TaskFragment with embedded activity in Task2.
-        final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(
-                createTask(mDisplayContent), organizer);
+        final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
         final ActivityRecord openingActivity = taskFragment.getTopMostActivity();
         prepareActivityForAppTransition(openingActivity);
         spyOn(mDisplayContent.mAppTransition);
@@ -958,11 +958,11 @@
 
     @Test
     public void testOverrideTaskFragmentAdapter_noOverrideNonEmbeddedActivityWithDiffUid() {
+        final Task task = createTask(mDisplayContent);
         final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
         final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
-        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
+        setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
 
-        final Task task = createTask(mDisplayContent);
         // Closing TaskFragment with embedded activity.
         final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
         final ActivityRecord closingActivity = taskFragment.getTopMostActivity();
@@ -986,13 +986,13 @@
 
     @Test
     public void testOverrideTaskFragmentAdapter_noOverrideWithWallpaper() {
+        final Task task = createTask(mDisplayContent);
         final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
         final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
-        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
+        setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
 
         // Create a TaskFragment with embedded activity.
-        final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(
-                createTask(mDisplayContent), organizer);
+        final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
         final ActivityRecord activity = taskFragment.getTopMostActivity();
         prepareActivityForAppTransition(activity);
         // Set wallpaper as visible.
@@ -1012,13 +1012,13 @@
 
     @Test
     public void testOverrideTaskFragmentAdapter_inputProtectedForUntrustedAnimation() {
+        final Task task = createTask(mDisplayContent);
         final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
         final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
-        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
+        setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
 
         // Create a TaskFragment with embedded activities, one is trusted embedded, and the other
         // one is untrusted embedded.
-        final Task task = createTask(mDisplayContent);
         final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
                 .setParentTask(task)
                 .createActivityCount(2)
@@ -1071,12 +1071,12 @@
      */
     @Test
     public void testOverrideTaskFragmentAdapter_inputProtectedForTrustedAnimation() {
+        final Task task = createTask(mDisplayContent);
         final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
         final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
-        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
+        setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
 
         // Create a TaskFragment with only trusted embedded activity
-        final Task task = createTask(mDisplayContent);
         final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
                 .setParentTask(task)
                 .createActivityCount(1)
@@ -1170,7 +1170,7 @@
     }
 
     /** Registers remote animation for the organizer. */
-    private void setupTaskFragmentRemoteAnimation(TaskFragmentOrganizer organizer,
+    private void setupTaskFragmentRemoteAnimation(TaskFragmentOrganizer organizer, int taskId,
             TestRemoteAnimationRunner remoteAnimationRunner) {
         final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
                 remoteAnimationRunner, 10, 1);
@@ -1181,7 +1181,8 @@
         definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_OPEN, adapter);
         definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CLOSE, adapter);
         mAtm.mTaskFragmentOrganizerController.registerOrganizer(iOrganizer);
-        mAtm.mTaskFragmentOrganizerController.registerRemoteAnimations(iOrganizer, definition);
+        mAtm.mTaskFragmentOrganizerController.registerRemoteAnimations(iOrganizer, taskId,
+                definition);
     }
 
     private void prepareAndTriggerAppTransition(@Nullable ActivityRecord openingActivity,
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index f44de1e..a4a62a4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -23,8 +23,13 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+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.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -32,13 +37,16 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.hardware.HardwareBuffer;
+import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
 import android.view.WindowManager;
 import android.window.BackEvent;
 import android.window.BackNavigationInfo;
 import android.window.IOnBackInvokedCallback;
+import android.window.OnBackInvokedCallback;
 import android.window.OnBackInvokedDispatcher;
 import android.window.TaskSnapshot;
+import android.window.WindowOnBackInvokedDispatcher;
 
 import com.android.server.LocalServices;
 
@@ -46,6 +54,9 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
 @Presubmit
 @RunWith(WindowTestRunner.class)
 public class BackNavigationControllerTests extends WindowTestsBase {
@@ -148,6 +159,61 @@
         assertThat(backNavigationInfo.getOnBackInvokedCallback()).isEqualTo(appCallback);
     }
 
+    @Test
+    public void testUnregisterCallbacksWithSystemCallback()
+            throws InterruptedException, RemoteException {
+        CountDownLatch systemLatch = new CountDownLatch(1);
+        CountDownLatch appLatch = new CountDownLatch(1);
+
+        Task task = createTopTaskWithActivity();
+        WindowState appWindow = task.getTopVisibleAppMainWindow();
+        WindowOnBackInvokedDispatcher dispatcher = new WindowOnBackInvokedDispatcher();
+        doAnswer(invocation -> {
+            appWindow.setOnBackInvokedCallback(invocation.getArgument(1),
+                    invocation.getArgument(2));
+            return null;
+        }).when(appWindow.mSession).setOnBackInvokedCallback(eq(appWindow.mClient), any(),
+                anyInt());
+
+        addToWindowMap(appWindow, true);
+        dispatcher.attachToWindow(appWindow.mSession, appWindow.mClient);
+
+
+        OnBackInvokedCallback appCallback = createBackCallback(appLatch);
+        OnBackInvokedCallback systemCallback = createBackCallback(systemLatch);
+
+        // Register both a system callback and an application callback
+        dispatcher.registerSystemOnBackInvokedCallback(systemCallback);
+        dispatcher.registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_DEFAULT,
+                appCallback);
+
+        // Check that the top callback is the app callback
+        assertEquals(appCallback, dispatcher.getTopCallback());
+
+        // Now unregister the app callback and check that the top callback is the system callback
+        dispatcher.unregisterOnBackInvokedCallback(appCallback);
+        assertEquals(systemCallback, dispatcher.getTopCallback());
+
+        // Verify that this has correctly been propagated to the server and that the
+        // BackNavigationInfo object will contain the system callback
+        BackNavigationInfo backNavigationInfo = startBackNavigation();
+        assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull();
+        IOnBackInvokedCallback callback = backNavigationInfo.getOnBackInvokedCallback();
+        assertThat(callback).isNotNull();
+
+        try {
+            callback.onBackInvoked();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+
+        // Check that the system callback has been call
+        assertTrue("System callback has not been called",
+                systemLatch.await(500, TimeUnit.MILLISECONDS));
+        assertEquals("App callback should not have been called",
+                1, appLatch.getCount());
+    }
+
     private IOnBackInvokedCallback withSystemCallback(Task task) {
         IOnBackInvokedCallback callback = createOnBackInvokedCallback();
         task.getTopMostActivity().getTopChild().setOnBackInvokedCallback(callback,
@@ -188,6 +254,17 @@
         };
     }
 
+    private OnBackInvokedCallback createBackCallback(CountDownLatch latch) {
+        return new OnBackInvokedCallback() {
+            @Override
+            public void onBackInvoked() {
+                if (latch != null) {
+                    latch.countDown();
+                }
+            }
+        };
+    }
+
     @NonNull
     private TaskSnapshotController createMockTaskSnapshotController() {
         TaskSnapshotController taskSnapshotController = mock(TaskSnapshotController.class);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerHelperTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
similarity index 74%
rename from services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerHelperTests.java
rename to services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
index f968999..a8282600 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerHelperTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.app.ActivityManager.START_ABORTED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 
@@ -26,6 +27,7 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
 
 import android.app.WindowConfiguration;
 import android.content.ComponentName;
@@ -45,13 +47,13 @@
 import java.util.Set;
 
 /**
- * Tests for the {@link DisplayWindowPolicyControllerHelper} class.
+ * Tests for the {@link DisplayWindowPolicyController} class.
  *
  * Build/Install/Run:
- *  atest WmTests:DisplayWindowPolicyControllerHelperTests
+ *  atest WmTests:DisplayWindowPolicyControllerTests
  */
 @RunWith(WindowTestRunner.class)
-public class DisplayWindowPolicyControllerHelperTests extends WindowTestsBase {
+public class DisplayWindowPolicyControllerTests extends WindowTestsBase {
     private static final int TEST_USER_0_ID = 0;
     private static final int TEST_USER_1_ID = 10;
 
@@ -152,8 +154,51 @@
         assertTrue(mSecondaryDisplay.mDwpcHelper.isWindowingModeSupported(WINDOWING_MODE_PINNED));
     }
 
+    @Test
+    public void testInterestedWindowFlags() {
+        final int fakeFlag1 = 0x00000010;
+        final int fakeFlag2 = 0x00000100;
+        final int fakeSystemFlag1 = 0x00000010;
+        final int fakeSystemFlag2 = 0x00000100;
+
+        mDwpc.setInterestedWindowFlags(fakeFlag1, fakeSystemFlag1);
+
+        assertTrue(mDwpc.isInterestedWindowFlags(fakeFlag1, fakeSystemFlag1));
+        assertTrue(mDwpc.isInterestedWindowFlags(fakeFlag1, fakeSystemFlag2));
+        assertTrue(mDwpc.isInterestedWindowFlags(fakeFlag2, fakeSystemFlag1));
+        assertFalse(mDwpc.isInterestedWindowFlags(fakeFlag2, fakeSystemFlag2));
+    }
+
+    @Test
+    public void testCanContainActivities() {
+        ActivityStarter starter = new ActivityStarter(mock(ActivityStartController.class), mAtm,
+                mSupervisor, mock(ActivityStartInterceptor.class));
+        final Task task = new TaskBuilder(mSupervisor).setDisplay(mSecondaryDisplay).build();
+        final ActivityRecord sourceRecord = new ActivityBuilder(mAtm).setTask(task).build();
+        final ActivityRecord disallowedRecord =
+                new ActivityBuilder(mAtm).setComponent(mDwpc.DISALLOWED_ACTIVITY).build();
+
+        int result = starter.startActivityInner(
+                disallowedRecord,
+                sourceRecord,
+                /* voiceSession */null,
+                /* voiceInteractor */ null,
+                /* startFlags */ 0,
+                /* doResume */true,
+                /* options */null,
+                /* inTask */null,
+                /* inTaskFragment */ null,
+                /* restrictedBgActivity */false,
+                /* intentGrants */null);
+
+        assertEquals(result, START_ABORTED);
+    }
+
     private class TestDisplayWindowPolicyController extends DisplayWindowPolicyController {
 
+        public ComponentName DISALLOWED_ACTIVITY =
+                new ComponentName("fake.package", "DisallowedActivity");
+
         ComponentName mTopActivity = null;
         int mTopActivityUid = UserHandle.USER_NULL;
         ArraySet<Integer> mRunningUids = new ArraySet<>();
@@ -161,7 +206,14 @@
         @Override
         public boolean canContainActivities(@NonNull List<ActivityInfo> activities,
                 @WindowConfiguration.WindowingMode int windowingMode) {
-            return false;
+            final int activityCount = activities.size();
+            for (int i = 0; i < activityCount; i++) {
+                final ActivityInfo aInfo = activities.get(i);
+                if (aInfo.getComponentName().equals(DISALLOWED_ACTIVITY)) {
+                    return false;
+                }
+            }
+            return true;
         }
 
         @Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 7b38a95..76fb7ff 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -1529,6 +1529,27 @@
     }
 
     @Test
+    public void testDisplayIgnoreOrientationRequest_orientationChangedToUnspecified() {
+        // Set up a display in landscape and ignoring orientation request.
+        setUpDisplaySizeWithApp(2800, 1400);
+        final DisplayContent display = mActivity.mDisplayContent;
+        display.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+
+        // Portrait fixed app without max aspect.
+        prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT);
+
+        assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertFalse(mActivity.inSizeCompatMode());
+
+        mActivity.setRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED);
+
+        assertTrue(mActivity.inSizeCompatMode());
+        // We should remember the original orientation.
+        assertEquals(mActivity.getResolvedOverrideConfiguration().orientation,
+                Configuration.ORIENTATION_PORTRAIT);
+    }
+
+    @Test
     public void testDisplayIgnoreOrientationRequest_newLaunchedMaxAspectApp() {
         // Set up a display in landscape and ignoring orientation request.
         setUpDisplaySizeWithApp(2800, 1400);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index b1c9d3d..37719fe 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -67,6 +67,7 @@
 @Presubmit
 @RunWith(WindowTestRunner.class)
 public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
+    private static final int TASK_ID = 10;
 
     private TaskFragmentOrganizerController mController;
     private TaskFragmentOrganizer mOrganizer;
@@ -219,13 +220,13 @@
     @Test
     public void testRegisterRemoteAnimations() {
         mController.registerOrganizer(mIOrganizer);
-        mController.registerRemoteAnimations(mIOrganizer, mDefinition);
+        mController.registerRemoteAnimations(mIOrganizer, TASK_ID, mDefinition);
 
-        assertEquals(mDefinition, mController.getRemoteAnimationDefinition(mIOrganizer));
+        assertEquals(mDefinition, mController.getRemoteAnimationDefinition(mIOrganizer, TASK_ID));
 
-        mController.unregisterRemoteAnimations(mIOrganizer);
+        mController.unregisterRemoteAnimations(mIOrganizer, TASK_ID);
 
-        assertNull(mController.getRemoteAnimationDefinition(mIOrganizer));
+        assertNull(mController.getRemoteAnimationDefinition(mIOrganizer, TASK_ID));
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index bcab4a5..c672b91 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -87,6 +87,7 @@
 import android.view.IWindow;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
+import android.view.InsetsVisibilities;
 import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
@@ -793,7 +794,8 @@
             }
 
             @Override
-            public void topFocusedWindowChanged(String packageName) {
+            public void topFocusedWindowChanged(String packageName,
+                    InsetsVisibilities requestedVisibilities) {
             }
         };
     }
diff --git a/services/usb/java/com/android/server/usb/UsbPortManager.java b/services/usb/java/com/android/server/usb/UsbPortManager.java
index f07a406..5e633ab 100644
--- a/services/usb/java/com/android/server/usb/UsbPortManager.java
+++ b/services/usb/java/com/android/server/usb/UsbPortManager.java
@@ -508,7 +508,7 @@
      *
      * @param portId port identifier.
      */
-    public boolean resetUsbPort(@NonNull String portId, int transactionId,
+    public void resetUsbPort(@NonNull String portId, int transactionId,
             @NonNull IUsbOperationInternal callback, IndentingPrintWriter pw) {
         synchronized (mLock) {
             Objects.requireNonNull(callback);
@@ -525,12 +525,11 @@
                             "resetUsbPort: Failed to call OperationComplete. opId:"
                             + transactionId, e);
                 }
-                return false;
             }
 
             try {
                 try {
-                    return mUsbPortHal.resetUsbPort(portId, transactionId, callback);
+                    mUsbPortHal.resetUsbPort(portId, transactionId, callback);
                 } catch (Exception e) {
                     logAndPrintException(pw,
                         "reseetUsbPort: Failed to resetUsbPort. opId:"
@@ -542,7 +541,6 @@
                         "resetUsbPort: Failed to call onOperationComplete. opId:"
                         + transactionId, e);
             }
-            return false;
         }
     }
 
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index e06ab02..86f877f 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -685,7 +685,7 @@
     }
 
     @Override
-    public boolean resetUsbPort(String portId, int operationId,
+    public void resetUsbPort(String portId, int operationId,
             IUsbOperationInternal callback) {
         Objects.requireNonNull(portId, "resetUsbPort: portId must not be null. opId:"
                 + operationId);
@@ -694,13 +694,11 @@
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
 
         final long ident = Binder.clearCallingIdentity();
-        boolean wait;
 
         try {
             if (mPortManager != null) {
-                wait = mPortManager.resetUsbPort(portId, operationId, callback, null);
+                mPortManager.resetUsbPort(portId, operationId, callback, null);
             } else {
-                wait = false;
                 try {
                     callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
                 } catch (RemoteException e) {
@@ -710,7 +708,6 @@
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
-        return wait;
     }
 
     @Override
diff --git a/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java b/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java
index 8c9e80f..600fc27 100644
--- a/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java
+++ b/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java
@@ -274,7 +274,7 @@
     }
 
     @Override
-    public boolean resetUsbPort(String portName, long operationID,
+    public void resetUsbPort(String portName, long operationID,
             IUsbOperationInternal callback) {
         Objects.requireNonNull(portName);
         Objects.requireNonNull(callback);
@@ -286,7 +286,6 @@
                             "resetUsbPort: Proxy is null. Retry !opID:"
                             + operationID);
                     callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
-                    return false;
                 }
                 while (sCallbacks.get(key) != null) {
                     key = ThreadLocalRandom.current().nextInt();
@@ -304,16 +303,13 @@
                             + portName + "opId:" + operationID, e);
                     callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
                     sCallbacks.remove(key);
-                    return false;
                 }
             } catch (RemoteException e) {
                 logAndPrintException(mPw,
                         "resetUsbPort: Failed to call onOperationComplete portID="
                         + portName + "opID:" + operationID, e);
                 sCallbacks.remove(key);
-                return false;
             }
-            return true;
         }
     }
 
diff --git a/services/usb/java/com/android/server/usb/hal/port/UsbPortHal.java b/services/usb/java/com/android/server/usb/hal/port/UsbPortHal.java
index 4fa296d..f98c598 100644
--- a/services/usb/java/com/android/server/usb/hal/port/UsbPortHal.java
+++ b/services/usb/java/com/android/server/usb/hal/port/UsbPortHal.java
@@ -202,9 +202,7 @@
      *                      implementation as needed.
      * @param callback callback object to be invoked to invoke the status of the operation upon
      *                 completion.
-     * @param callback callback object to be invoked to invoke the status of the operation upon
-     *                 completion.
      */
-    public boolean resetUsbPort(String portName, long transactionId,
+    public void resetUsbPort(String portName, long transactionId,
             IUsbOperationInternal callback);
 }
diff --git a/services/usb/java/com/android/server/usb/hal/port/UsbPortHidl.java b/services/usb/java/com/android/server/usb/hal/port/UsbPortHidl.java
index c7f0775..23d913c 100644
--- a/services/usb/java/com/android/server/usb/hal/port/UsbPortHidl.java
+++ b/services/usb/java/com/android/server/usb/hal/port/UsbPortHidl.java
@@ -318,7 +318,7 @@
     }
 
     @Override
-    public boolean resetUsbPort(String portName, long transactionId,
+    public void resetUsbPort(String portName, long transactionId,
             IUsbOperationInternal callback) {
         try {
             callback.onOperationComplete(USB_OPERATION_ERROR_NOT_SUPPORTED);
@@ -327,7 +327,6 @@
                     + transactionId
                     + " portId:" + portName, e);
         }
-        return false;
     }
 
     @Override
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index c86f38d..8d4a017 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -119,13 +119,6 @@
     private static final Duration MAX_UPDATE_TIMEOUT_DURATION =
             Duration.ofMillis(MAX_UPDATE_TIMEOUT_MILLIS);
     private static final long RESET_DEBUG_HOTWORD_LOGGING_TIMEOUT_MILLIS = 60 * 60 * 1000; // 1 hour
-    /**
-     * Time after which each HotwordDetectionService process is stopped and replaced by a new one.
-     * 0 indicates no restarts.
-     */
-    private static final int RESTART_PERIOD_SECONDS =
-            DeviceConfig.getInt(DeviceConfig.NAMESPACE_VOICE_INTERACTION,
-                    KEY_RESTART_PERIOD_IN_SECONDS, 0);
     private static final int MAX_ISOLATED_PROCESS_NUMBER = 10;
 
     // Hotword metrics
@@ -150,6 +143,11 @@
     private final @NonNull ServiceConnectionFactory mServiceConnectionFactory;
     private final IHotwordRecognitionStatusCallback mCallback;
     private final int mDetectorType;
+    /**
+     * Time after which each HotwordDetectionService process is stopped and replaced by a new one.
+     * 0 indicates no restarts.
+     */
+    private final int mReStartPeriodSeconds;
 
     final Object mLock;
     final int mVoiceInteractionServiceUid;
@@ -195,6 +193,8 @@
         mUser = userId;
         mCallback = callback;
         mDetectorType = detectorType;
+        mReStartPeriodSeconds = DeviceConfig.getInt(DeviceConfig.NAMESPACE_VOICE_INTERACTION,
+                KEY_RESTART_PERIOD_IN_SECONDS, 0);
         final Intent intent = new Intent(HotwordDetectionService.SERVICE_INTERFACE);
         intent.setComponent(mDetectionComponentName);
         initAudioFlingerLocked();
@@ -206,11 +206,11 @@
         mLastRestartInstant = Instant.now();
         updateStateAfterProcessStart(options, sharedMemory);
 
-        if (RESTART_PERIOD_SECONDS <= 0) {
+        if (mReStartPeriodSeconds <= 0) {
             mCancellationTaskFuture = null;
         } else {
-            // TODO(volnov): we need to be smarter here, e.g. schedule it a bit more often, but wait
-            // until the current session is closed.
+            // TODO: we need to be smarter here, e.g. schedule it a bit more often,
+            //  but wait until the current session is closed.
             mCancellationTaskFuture = mScheduledExecutorService.scheduleAtFixedRate(() -> {
                 Slog.v(TAG, "Time to restart the process, TTL has passed");
                 synchronized (mLock) {
@@ -218,7 +218,7 @@
                     HotwordMetricsLogger.writeServiceRestartEvent(mDetectorType,
                             HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__SCHEDULE);
                 }
-            }, RESTART_PERIOD_SECONDS, RESTART_PERIOD_SECONDS, TimeUnit.SECONDS);
+            }, mReStartPeriodSeconds, mReStartPeriodSeconds, TimeUnit.SECONDS);
         }
     }
 
@@ -785,7 +785,7 @@
     }
 
     public void dump(String prefix, PrintWriter pw) {
-        pw.print(prefix); pw.print("RESTART_PERIOD_SECONDS="); pw.println(RESTART_PERIOD_SECONDS);
+        pw.print(prefix); pw.print("mReStartPeriodSeconds="); pw.println(mReStartPeriodSeconds);
         pw.print(prefix);
         pw.print("mBound=" + mRemoteHotwordDetectionService.isBound());
         pw.print(", mValidatingDspTrigger=" + mValidatingDspTrigger);
diff --git a/telecomm/OWNERS b/telecomm/OWNERS
index 9969ee9..eb0c432 100644
--- a/telecomm/OWNERS
+++ b/telecomm/OWNERS
@@ -1,8 +1,6 @@
 set noparent
 
 breadley@google.com
-hallliu@google.com
 tgunn@google.com
 xiaotonj@google.com
-shuoq@google.com
 rgreenwalt@google.com
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index df114db..e0f5b20 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -2601,6 +2601,33 @@
     }
 
     /**
+     * Determines whether there are any ongoing {@link PhoneAccount#CAPABILITY_SELF_MANAGED}
+     * calls for a given {@code packageName} and {@code userHandle}.
+     *
+     * @param packageName the package name of the app to check calls for.
+     * @param userHandle the user handle on which to check for calls.
+     * @return {@code true} if there are ongoing calls, {@code false} otherwise.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public boolean isInSelfManagedCall(@NonNull String packageName,
+            @NonNull UserHandle userHandle) {
+        ITelecomService service = getTelecomService();
+        if (service != null) {
+            try {
+                return service.isInSelfManagedCall(packageName, userHandle,
+                        mContext.getOpPackageName());
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException isInSelfManagedCall: " + e);
+                e.rethrowFromSystemServer();
+                return false;
+            }
+        } else {
+            throw new IllegalStateException("Telecom service is not present");
+        }
+    }
+
+    /**
      * Handles {@link Intent#ACTION_CALL} intents trampolined from UserCallActivity.
      * @param intent The {@link Intent#ACTION_CALL} intent to handle.
      * @param callingPackageProxy The original package that called this before it was trampolined.
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index 9999c89..07e18d5 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -22,6 +22,7 @@
 import android.telecom.PhoneAccountHandle;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.UserHandle;
 import android.telecom.PhoneAccount;
 
 /**
@@ -374,4 +375,10 @@
      * @see TelecomServiceImpl#setTestCallDiagnosticService
      */
     void setTestCallDiagnosticService(in String packageName);
+
+    /**
+     * @see TelecomServiceImpl#isInSelfManagedCall
+     */
+    boolean isInSelfManagedCall(String packageName, in UserHandle userHandle,
+        String callingPackage);
 }
diff --git a/telephony/OWNERS b/telephony/OWNERS
index f248fd5..9681ee8 100644
--- a/telephony/OWNERS
+++ b/telephony/OWNERS
@@ -1,15 +1,10 @@
 set noparent
 
-amitmahajan@google.com
 breadley@google.com
 fionaxu@google.com
 jackyu@google.com
 rgreenwalt@google.com
 tgunn@google.com
-jminjie@google.com
-shuoq@google.com
-sarahchin@google.com
-xiaotonj@google.com
 huiwang@google.com
 jayachandranc@google.com
 chinmayd@google.com
diff --git a/telephony/java/android/service/euicc/OWNERS b/telephony/java/android/service/euicc/OWNERS
index 6aa399d..fbeb6da 100644
--- a/telephony/java/android/service/euicc/OWNERS
+++ b/telephony/java/android/service/euicc/OWNERS
@@ -1,5 +1,5 @@
-# Bug component: 20868
+set noparent
 
+fionaxu@google.com
 rgreenwalt@google.com
-tgunn@google.com
-amitmahajan@google.com
+amruthr@google.com
diff --git a/telephony/java/android/service/sms/OWNERS b/telephony/java/android/service/sms/OWNERS
deleted file mode 100644
index 6aa399d..0000000
--- a/telephony/java/android/service/sms/OWNERS
+++ /dev/null
@@ -1,5 +0,0 @@
-# Bug component: 20868
-
-rgreenwalt@google.com
-tgunn@google.com
-amitmahajan@google.com
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 8d7fab4..a2266fb 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1959,6 +1959,13 @@
             "nr_advanced_threshold_bandwidth_khz_int";
 
     /**
+     * Boolean indicating if operator name should be shown in the status bar
+     * @hide
+     */
+    public static final String KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL =
+            "show_operator_name_in_statusbar_bool";
+
+    /**
      * The string is used to filter redundant string from PLMN Network Name that's supplied by
      * specific carrier.
      *
@@ -8913,6 +8920,7 @@
         sDefaults.putStringArray(KEY_SUPPORT_TDSCDMA_ROAMING_NETWORKS_STRING_ARRAY, null);
         sDefaults.putBoolean(KEY_WORLD_MODE_ENABLED_BOOL, false);
         sDefaults.putString(KEY_CARRIER_SETTINGS_ACTIVITY_COMPONENT_NAME_STRING, "");
+        sDefaults.putBoolean(KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL, false);
         sDefaults.putBoolean(KEY_CARRIER_CONFIG_APPLIED_BOOL, false);
         sDefaults.putBoolean(KEY_CHECK_PRICING_WITH_CARRIER_FOR_DATA_ROAMING_BOOL, false);
         sDefaults.putBoolean(KEY_SHOW_DATA_CONNECTED_ROAMING_NOTIFICATION_BOOL, false);
diff --git a/telephony/java/android/telephony/OWNERS b/telephony/java/android/telephony/OWNERS
deleted file mode 100644
index 6aa399d..0000000
--- a/telephony/java/android/telephony/OWNERS
+++ /dev/null
@@ -1,5 +0,0 @@
-# Bug component: 20868
-
-rgreenwalt@google.com
-tgunn@google.com
-amitmahajan@google.com
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 92cabab..8a75ae4 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -10556,7 +10556,7 @@
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null)
-                return telephony.enableDataConnectivity();
+                return telephony.enableDataConnectivity(getOpPackageName());
         } catch (RemoteException e) {
             Log.e(TAG, "Error calling ITelephony#enableDataConnectivity", e);
         }
@@ -10571,7 +10571,7 @@
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null)
-                return telephony.disableDataConnectivity();
+                return telephony.disableDataConnectivity(getOpPackageName());
         } catch (RemoteException e) {
             Log.e(TAG, "Error calling ITelephony#disableDataConnectivity", e);
         }
@@ -11768,7 +11768,25 @@
         if (SubscriptionManager.isValidPhoneId(phoneId)) {
             List<String> newList = updateTelephonyProperty(
                     TelephonyProperties.operator_alpha(), phoneId, name);
-            TelephonyProperties.operator_alpha(newList);
+            try {
+                TelephonyProperties.operator_alpha(newList);
+            } catch (IllegalArgumentException e) { //property value is longer than the byte limit
+                Log.e(TAG, "setNetworkOperatorNameForPhone: ", e);
+
+                int numberOfEntries = newList.size();
+                int maxOperatorLength = //save 1 byte for joiner " , "
+                        (SystemProperties.PROP_VALUE_MAX - numberOfEntries) / numberOfEntries;
+
+                //examine and truncate every operator and retry
+                for (int i = 0; i < newList.size(); i++) {
+                    if (newList.get(i) != null) {
+                        newList.set(i, TextUtils
+                                .truncateStringForUtf8Storage(newList.get(i), maxOperatorLength));
+                    }
+                }
+                TelephonyProperties.operator_alpha(newList);
+                Log.e(TAG, "successfully truncated operator_alpha: " + newList);
+            }
         }
     }
 
@@ -11931,7 +11949,7 @@
             Log.d(TAG, "factoryReset: subId=" + subId);
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                telephony.factoryReset(subId);
+                telephony.factoryReset(subId, getOpPackageName());
             }
         } catch (RemoteException e) {
         }
@@ -11951,7 +11969,7 @@
             Log.d(TAG, "resetSettings: subId=" + getSubId());
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                telephony.factoryReset(getSubId());
+                telephony.factoryReset(getSubId(), getOpPackageName());
             }
         } catch (RemoteException e) {
         }
@@ -12172,15 +12190,7 @@
     })
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
     public @Nullable ServiceState getServiceState() {
-        if (getRenouncedPermissions().contains(Manifest.permission.ACCESS_FINE_LOCATION))  {
-            if (getRenouncedPermissions().contains(Manifest.permission.ACCESS_COARSE_LOCATION)) {
-                return getServiceState(INCLUDE_LOCATION_DATA_NONE);
-            } else {
-                return getServiceState(INCLUDE_LOCATION_DATA_COARSE);
-            }
-        }
-
-        return getServiceState(INCLUDE_LOCATION_DATA_FINE);
+        return getServiceState(getLocationData());
     }
 
     /**
@@ -13219,7 +13229,7 @@
         try {
             ITelephony service = getITelephony();
             if (service != null) {
-                service.setDataEnabledForReason(subId, reason, enabled);
+                service.setDataEnabledForReason(subId, reason, enabled, getOpPackageName());
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
@@ -16171,17 +16181,21 @@
      */
     public void registerTelephonyCallback(@NonNull @CallbackExecutor Executor executor,
             @NonNull TelephonyCallback callback) {
-        if (getRenouncedPermissions().contains(Manifest.permission.ACCESS_FINE_LOCATION))  {
-            if (getRenouncedPermissions().contains(Manifest.permission.ACCESS_COARSE_LOCATION)) {
-                registerTelephonyCallback(INCLUDE_LOCATION_DATA_NONE, executor, callback);
-                return;
-            } else {
-                registerTelephonyCallback(INCLUDE_LOCATION_DATA_COARSE, executor, callback);
-                return;
-            }
-        }
+        registerTelephonyCallback(getLocationData(), executor, callback);
+    }
 
-        registerTelephonyCallback(INCLUDE_LOCATION_DATA_FINE, executor, callback);
+    private int getLocationData() {
+        boolean renounceCoarseLocation =
+                getRenouncedPermissions().contains(Manifest.permission.ACCESS_COARSE_LOCATION);
+        boolean renounceFineLocation =
+                getRenouncedPermissions().contains(Manifest.permission.ACCESS_FINE_LOCATION);
+        if (renounceCoarseLocation) {
+            return INCLUDE_LOCATION_DATA_NONE;
+        } else if (renounceFineLocation) {
+            return INCLUDE_LOCATION_DATA_COARSE;
+        } else {
+            return INCLUDE_LOCATION_DATA_FINE;
+        }
     }
 
     /** @hide */
diff --git a/telephony/java/android/telephony/UiccCardInfo.java b/telephony/java/android/telephony/UiccCardInfo.java
index 3843a62..249f740 100644
--- a/telephony/java/android/telephony/UiccCardInfo.java
+++ b/telephony/java/android/telephony/UiccCardInfo.java
@@ -21,6 +21,9 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.internal.telephony.util.TelephonyUtils;
+import com.android.telephony.Rlog;
+
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -227,7 +230,6 @@
         this.mIccIdAccessRestricted = iccIdAccessRestricted;
     }
 
-
     @Override
     public boolean equals(Object obj) {
         if (this == obj) {
@@ -261,7 +263,7 @@
                 + ", mCardId="
                 + mCardId
                 + ", mEid="
-                + mEid
+                + Rlog.pii(TelephonyUtils.IS_DEBUGGABLE, mEid)
                 + ", mPhysicalSlotIndex="
                 + mPhysicalSlotIndex
                 + ", mIsRemovable="
diff --git a/telephony/java/android/telephony/UiccSlotInfo.java b/telephony/java/android/telephony/UiccSlotInfo.java
index 17ce450..dd3639a 100644
--- a/telephony/java/android/telephony/UiccSlotInfo.java
+++ b/telephony/java/android/telephony/UiccSlotInfo.java
@@ -279,7 +279,7 @@
                 + ", mIsEuicc="
                 + mIsEuicc
                 + ", mCardId="
-                + mCardId
+                + SubscriptionInfo.givePrintableIccid(mCardId)
                 + ", cardState="
                 + mCardStateInfo
                 + ", mIsExtendedApduSupported="
diff --git a/telephony/java/android/telephony/cdma/OWNERS b/telephony/java/android/telephony/cdma/OWNERS
deleted file mode 100644
index 6aa399d..0000000
--- a/telephony/java/android/telephony/cdma/OWNERS
+++ /dev/null
@@ -1,5 +0,0 @@
-# Bug component: 20868
-
-rgreenwalt@google.com
-tgunn@google.com
-amitmahajan@google.com
diff --git a/telephony/java/android/telephony/data/OWNERS b/telephony/java/android/telephony/data/OWNERS
index 932b35c..9dce366 100644
--- a/telephony/java/android/telephony/data/OWNERS
+++ b/telephony/java/android/telephony/data/OWNERS
@@ -1,5 +1,6 @@
-# Bug component: 20868
+set noparent
 
-rgreenwalt@google.com
-tgunn@google.com
 jackyu@google.com
+amruthr@google.com
+rgreenwalt@google.com
+
diff --git a/telephony/java/android/telephony/emergency/OWNERS b/telephony/java/android/telephony/emergency/OWNERS
index fa07dce..1d8de5d 100644
--- a/telephony/java/android/telephony/emergency/OWNERS
+++ b/telephony/java/android/telephony/emergency/OWNERS
@@ -1,5 +1,3 @@
-# Bug component: 20868
+set noparent
 
-rgreenwalt@google.com
-tgunn@google.com
-shuoq@google.com
+file:platform/frameworks/base:/telecomm/OWNERS
diff --git a/telephony/java/android/telephony/euicc/OWNERS b/telephony/java/android/telephony/euicc/OWNERS
index 9e51a4b..781550c 100644
--- a/telephony/java/android/telephony/euicc/OWNERS
+++ b/telephony/java/android/telephony/euicc/OWNERS
@@ -1,6 +1,4 @@
-# Bug component: 20868
+set noparent
 
-rgreenwalt@google.com
-tgunn@google.com
-refuhoo@google.com
-amitmahajan@google.com
+file:platform/frameworks/base:/telephony/java/android/service/euicc/OWNERS
+
diff --git a/telephony/java/android/telephony/gsm/OWNERS b/telephony/java/android/telephony/gsm/OWNERS
deleted file mode 100644
index 6aa399d..0000000
--- a/telephony/java/android/telephony/gsm/OWNERS
+++ /dev/null
@@ -1,5 +0,0 @@
-# Bug component: 20868
-
-rgreenwalt@google.com
-tgunn@google.com
-amitmahajan@google.com
diff --git a/telephony/java/android/telephony/ims/OWNERS b/telephony/java/android/telephony/ims/OWNERS
index 0854c5d..3c72680 100644
--- a/telephony/java/android/telephony/ims/OWNERS
+++ b/telephony/java/android/telephony/ims/OWNERS
@@ -1,5 +1,6 @@
-# Bug component: 20868
+set noparent
 
 rgreenwalt@google.com
 tgunn@google.com
 breadley@google.com
+amruthr@google.com
diff --git a/telephony/java/android/telephony/ims/aidl/OWNERS b/telephony/java/android/telephony/ims/aidl/OWNERS
deleted file mode 100644
index 0854c5d..0000000
--- a/telephony/java/android/telephony/ims/aidl/OWNERS
+++ /dev/null
@@ -1,5 +0,0 @@
-# Bug component: 20868
-
-rgreenwalt@google.com
-tgunn@google.com
-breadley@google.com
diff --git a/telephony/java/android/telephony/ims/compat/OWNERS b/telephony/java/android/telephony/ims/compat/OWNERS
deleted file mode 100644
index 0854c5d..0000000
--- a/telephony/java/android/telephony/ims/compat/OWNERS
+++ /dev/null
@@ -1,5 +0,0 @@
-# Bug component: 20868
-
-rgreenwalt@google.com
-tgunn@google.com
-breadley@google.com
diff --git a/telephony/java/android/telephony/ims/compat/feature/OWNERS b/telephony/java/android/telephony/ims/compat/feature/OWNERS
deleted file mode 100644
index 0854c5d..0000000
--- a/telephony/java/android/telephony/ims/compat/feature/OWNERS
+++ /dev/null
@@ -1,5 +0,0 @@
-# Bug component: 20868
-
-rgreenwalt@google.com
-tgunn@google.com
-breadley@google.com
diff --git a/telephony/java/android/telephony/ims/compat/stub/OWNERS b/telephony/java/android/telephony/ims/compat/stub/OWNERS
deleted file mode 100644
index 0854c5d..0000000
--- a/telephony/java/android/telephony/ims/compat/stub/OWNERS
+++ /dev/null
@@ -1,5 +0,0 @@
-# Bug component: 20868
-
-rgreenwalt@google.com
-tgunn@google.com
-breadley@google.com
diff --git a/telephony/java/android/telephony/ims/feature/OWNERS b/telephony/java/android/telephony/ims/feature/OWNERS
deleted file mode 100644
index 0854c5d..0000000
--- a/telephony/java/android/telephony/ims/feature/OWNERS
+++ /dev/null
@@ -1,5 +0,0 @@
-# Bug component: 20868
-
-rgreenwalt@google.com
-tgunn@google.com
-breadley@google.com
diff --git a/telephony/java/android/telephony/ims/stub/OWNERS b/telephony/java/android/telephony/ims/stub/OWNERS
deleted file mode 100644
index 0854c5d..0000000
--- a/telephony/java/android/telephony/ims/stub/OWNERS
+++ /dev/null
@@ -1,5 +0,0 @@
-# Bug component: 20868
-
-rgreenwalt@google.com
-tgunn@google.com
-breadley@google.com
diff --git a/telephony/java/android/telephony/mbms/OWNERS b/telephony/java/android/telephony/mbms/OWNERS
index 718e0a2..1d8de5d 100644
--- a/telephony/java/android/telephony/mbms/OWNERS
+++ b/telephony/java/android/telephony/mbms/OWNERS
@@ -1,5 +1,3 @@
-# Bug component: 20868
+set noparent
 
-rgreenwalt@google.com
-tgunn@google.com
-hallliu@google.com
+file:platform/frameworks/base:/telecomm/OWNERS
diff --git a/telephony/java/android/telephony/mbms/vendor/OWNERS b/telephony/java/android/telephony/mbms/vendor/OWNERS
deleted file mode 100644
index 718e0a2..0000000
--- a/telephony/java/android/telephony/mbms/vendor/OWNERS
+++ /dev/null
@@ -1,5 +0,0 @@
-# Bug component: 20868
-
-rgreenwalt@google.com
-tgunn@google.com
-hallliu@google.com
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index cfd940d..7d116f9 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -270,13 +270,13 @@
      * Allow mobile data connections.
      */
     @UnsupportedAppUsage
-    boolean enableDataConnectivity();
+    boolean enableDataConnectivity(String callingPackage);
 
     /**
      * Disallow mobile data connections.
      */
     @UnsupportedAppUsage
-    boolean disableDataConnectivity();
+    boolean disableDataConnectivity(String callingPackage);
 
     /**
      * Report whether data connectivity is possible.
@@ -959,8 +959,9 @@
      * @param subId user preferred subId.
      * @param reason the reason the data enable change is taking place
      * @param enable true to turn on, else false
+     * @param callingPackage the package that changed the data enabled state
      */
-     void setDataEnabledForReason(int subId, int reason, boolean enable);
+     void setDataEnabledForReason(int subId, int reason, boolean enable, String callingPackage);
 
     /**
      * Return whether data is enabled for certain reason
@@ -1327,7 +1328,7 @@
      */
     PhoneAccountHandle getPhoneAccountHandleForSubscriptionId(int subscriptionId);
 
-    void factoryReset(int subId);
+    void factoryReset(int subId, String callingPackage);
 
     /**
      * Returns users's current locale based on the SIM.
diff --git a/test-legacy/Android.mk b/test-legacy/Android.mk
index 284008c..da9dc25 100644
--- a/test-legacy/Android.mk
+++ b/test-legacy/Android.mk
@@ -40,6 +40,10 @@
 
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
+$(call declare-license-metadata,$(full_classes_jar),\
+    SPDX-license-identifier-Apache-2.0 SPDX-license-identifier-MIT SPDX-license-identifier-Unicode-DFS,\
+    notice,$(LOCAL_PATH)/../NOTICE,Android,frameworks/base)
+
 # Archive a copy of the classes.jar in SDK build.
 $(call dist-for-goals,sdk,$(full_classes_jar):android.test.legacy.jar)
 
diff --git a/tests/FlickerTests/OWNERS b/tests/FlickerTests/OWNERS
index c1221e3..d40ff56 100644
--- a/tests/FlickerTests/OWNERS
+++ b/tests/FlickerTests/OWNERS
@@ -1,4 +1,4 @@
-# Bug component: 909476
+# Bug component: 1157642
 include /services/core/java/com/android/server/wm/OWNERS
 natanieljr@google.com
 pablogamito@google.com
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeEditorPopupDialogAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeEditorPopupDialogAppHelper.kt
new file mode 100644
index 0000000..172c433
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeEditorPopupDialogAppHelper.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.server.wm.flicker.helpers
+
+import android.app.Instrumentation
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.server.wm.traces.parser.toFlickerComponent
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+
+class ImeEditorPopupDialogAppHelper @JvmOverloads constructor(
+    instr: Instrumentation,
+    private val rotation: Int,
+    private val imePackageName: String = IME_PACKAGE,
+    launcherName: String = ActivityOptions.EDITOR_POPUP_DIALOG_ACTIVITY_LAUNCHER_NAME,
+    component: FlickerComponentName =
+            ActivityOptions.EDITOR_POPUP_DIALOG_ACTIVITY_COMPONENT_NAME.toFlickerComponent()
+) : ImeAppHelper(instr, launcherName, component) {
+    override fun openIME(
+        device: UiDevice,
+        wmHelper: WindowManagerStateHelper?
+    ) {
+        val editText = device.wait(Until.findObject(By.text("focused editText")), FIND_TIMEOUT)
+
+        require(editText != null) {
+            "Text field not found, this usually happens when the device " +
+                    "was left in an unknown state (e.g. in split screen)"
+        }
+        editText.click()
+        waitIMEShown(device, wmHelper)
+    }
+
+    fun dismissDialog(wmHelper: WindowManagerStateHelper) {
+        val dismissButton = uiDevice.wait(
+                Until.findObject(By.text("Dismiss")), FIND_TIMEOUT)
+
+        // Pressing back key to dismiss the dialog
+        if (dismissButton != null) {
+            dismissButton.click()
+            wmHelper.waitForAppTransitionIdle()
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt
new file mode 100644
index 0000000..bff099e
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt
@@ -0,0 +1,135 @@
+/*
+ * 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.wm.flicker.ime
+
+import android.app.Instrumentation
+import android.platform.test.annotations.Postsubmit
+import android.view.Surface
+import android.view.WindowManagerPolicyConstants
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.FlickerBuilderProvider
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.annotation.Group4
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.ImeEditorPopupDialogAppHelper
+import com.android.server.wm.flicker.navBarWindowIsVisible
+import com.android.server.wm.flicker.statusBarWindowIsVisible
+import com.android.server.wm.flicker.traces.region.RegionSubject
+import com.android.server.wm.traces.common.FlickerComponentName
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group4
+class CloseImeEditorPopupDialogTest(private val testSpec: FlickerTestParameter) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val imeTestApp = ImeEditorPopupDialogAppHelper(instrumentation, testSpec.startRotation)
+
+    @FlickerBuilderProvider
+    fun buildFlicker(): FlickerBuilder {
+        return FlickerBuilder(instrumentation).apply {
+            setup {
+                eachRun {
+                    imeTestApp.launchViaIntent(wmHelper)
+                    imeTestApp.openIME(device, wmHelper)
+                }
+            }
+            transitions {
+                imeTestApp.dismissDialog(wmHelper)
+                instrumentation.uiAutomation.syncInputTransactions()
+            }
+            teardown {
+                eachRun {
+                    device.pressHome()
+                    wmHelper.waitForHomeActivityVisible()
+                    imeTestApp.exit()
+                }
+            }
+        }
+    }
+
+    @Postsubmit
+    @Test
+    fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
+
+    @Postsubmit
+    @Test
+    fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
+
+    @Postsubmit
+    @Test
+    fun imeWindowBecameInvisible() = testSpec.imeWindowBecomesInvisible()
+
+    @Postsubmit
+    @Test
+    fun imeLayerAndImeSnapshotVisibleOnScreen() {
+        testSpec.assertLayers {
+            this.isVisible(FlickerComponentName.IME)
+                    .then()
+                    .isVisible(FlickerComponentName.IME_SNAPSHOT)
+                    .then()
+                    .isInvisible(FlickerComponentName.IME)
+        }
+    }
+
+    @Postsubmit
+    @Test
+    fun imeSnapshotAssociatedOnAppVisibleRegion() {
+        testSpec.assertLayers {
+            this.invoke("imeSnapshotAssociatedOnAppVisibleRegion") {
+                val imeSnapshotLayers = it.subjects.filter {
+                    subject -> subject.name.contains(
+                        FlickerComponentName.IME_SNAPSHOT.toLayerName()) && subject.isVisible
+                }
+                if (imeSnapshotLayers.isNotEmpty()) {
+                    val visibleAreas = imeSnapshotLayers.mapNotNull { imeSnapshotLayer ->
+                        imeSnapshotLayer.layer?.visibleRegion }.toTypedArray()
+                    val imeVisibleRegion = RegionSubject.assertThat(visibleAreas, this, timestamp)
+                    val appVisibleRegion = it.visibleRegion(imeTestApp.component)
+                    if (imeVisibleRegion.region.isNotEmpty) {
+                        imeVisibleRegion.coversAtMost(appVisibleRegion.region)
+                    }
+                }
+            }
+        }
+    }
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<FlickerTestParameter> {
+            return FlickerTestParameterFactory.getInstance()
+                    .getConfigNonRotationTests(
+                            repetitions = 2,
+                            supportedNavigationModes = listOf(
+                                    WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
+                                    WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
+                            ),
+                            supportedRotations = listOf(Surface.ROTATION_0)
+                    )
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt
index 7f49663..1b60403 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt
@@ -79,7 +79,7 @@
     /**
      * Checks that [FlickerComponentName.IME] layer is visible at the end of the transition
      */
-    @Presubmit
+    @FlakyTest(bugId = 227142436)
     @Test
     fun imeLayerExistsEnd() {
         testSpec.assertLayersEnd {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt
index db2e645..e2e1ae8 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt
@@ -114,7 +114,7 @@
         }
     }
 
-    @Presubmit
+    @FlakyTest(bugId = 228011606)
     @Test
     fun imeLayerIsVisibleAndAssociatedWithAppWidow() {
         testSpec.assertLayersStart {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
index 7e3ed82..4b268a8 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
@@ -32,12 +32,15 @@
 import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.navBarWindowIsVisible
 import com.android.server.wm.flicker.statusBarWindowIsVisible
 import com.android.server.wm.traces.common.FlickerComponentName
 import com.android.server.wm.traces.common.WindowManagerConditionsFactory
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+import org.junit.Assume
+import org.junit.Before
 
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -55,11 +58,16 @@
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @Group4
 @Presubmit
-class SwitchImeWindowsFromGestureNavTest(private val testSpec: FlickerTestParameter) {
+open class SwitchImeWindowsFromGestureNavTest(private val testSpec: FlickerTestParameter) {
     private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
     private val testApp = SimpleAppHelper(instrumentation)
     private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
 
+    @Before
+    open fun before() {
+        Assume.assumeFalse(isShellTransitionsEnabled)
+    }
+
     @FlickerBuilderProvider
     fun buildFlicker(): FlickerBuilder {
         return FlickerBuilder(instrumentation).apply {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTestShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt
similarity index 76%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTestShellTransit.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt
index 2252a94..edd52b7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTestShellTransit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.pip
+package com.android.server.wm.flicker.ime
 
 import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
@@ -24,22 +24,26 @@
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import org.junit.Assume
 import org.junit.Before
+
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
 
+/**
+ * Test IME windows switching with 2-Buttons or gestural navigation.
+ * To run this test: `atest FlickerTests:SwitchImeWindowsFromGestureNavTest`
+ */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @Group4
-class PipRotationTestShellTransit(testSpec: FlickerTestParameter) : PipRotationTest(testSpec) {
+@FlakyTest(bugId = 228012334)
+class SwitchImeWindowsFromGestureNavTest_ShellTransit(testSpec: FlickerTestParameter)
+    : SwitchImeWindowsFromGestureNavTest(testSpec) {
     @Before
     override fun before() {
         Assume.assumeTrue(isShellTransitionsEnabled)
     }
-
-    @FlakyTest(bugId = 227214914)
-    override fun pipLayerRotates_StartingBounds() = super.pipLayerRotates_StartingBounds()
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
index ee0f3d8..6e33f66 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
@@ -26,12 +26,9 @@
 import com.android.server.wm.flicker.LAUNCHER_COMPONENT
 import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.helpers.reopenAppFromOverview
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.traces.common.WindowManagerConditionsFactory
-import org.junit.Assume.assumeFalse
-import org.junit.Before
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -64,10 +61,6 @@
 @Group1
 open class OpenAppFromOverviewTest(testSpec: FlickerTestParameter)
     : OpenAppFromLauncherTransition(testSpec) {
-    @Before
-    open fun before() {
-        assumeFalse(isShellTransitionsEnabled)
-    }
 
     /**
      * Defines the transition used to run the test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest_ShellTransit.kt
deleted file mode 100644
index 55e1e9b..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest_ShellTransit.kt
+++ /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.server.wm.flicker.launch
-
-import androidx.test.filters.FlakyTest
-import android.platform.test.annotations.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.annotation.Group1
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
-import org.junit.Assume.assumeTrue
-import org.junit.Before
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test launching an app from the recents app view (the overview)
- *
- * To run this test: `atest FlickerTests:OpenAppFromOverviewTest`
- *
- * Actions:
- *     Launch [testApp]
- *     Press recents
- *     Relaunch an app [testApp] by selecting it in the overview screen, and wait animation to
- *     complete (only this action is traced)
- *
- * Notes:
- *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
- *        are inherited [OpenAppTransition]
- *     2. Part of the test setup occurs automatically via
- *        [com.android.server.wm.flicker.TransitionRunnerWithRules],
- *        including configuring navigation mode, initial orientation and ensuring no
- *        apps are running before setup
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group1
-class OpenAppFromOverviewTest_ShellTransit(testSpec: FlickerTestParameter)
-    : OpenAppFromOverviewTest(testSpec) {
-    @Before
-    override fun before() {
-        assumeTrue(isShellTransitionsEnabled)
-    }
-
-    /** {@inheritDoc} */
-    @FlakyTest(bugId = 216266712)
-    @Test
-    override fun appWindowBecomesTopWindow() = super.appWindowBecomesTopWindow()
-
-    /** {@inheritDoc} */
-    @FlakyTest(bugId = 216266712)
-    @Test
-    override fun appWindowReplacesLauncherAsTopWindow() =
-            super.appWindowReplacesLauncherAsTopWindow()
-
-    /** {@inheritDoc} */
-    @FlakyTest(bugId = 218470989)
-    @Test
-    override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
-        super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
index fbd611a..f357177 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
@@ -27,10 +27,7 @@
 import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
 import com.android.server.wm.flicker.helpers.WindowUtils
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.traces.common.FlickerComponentName
-import org.junit.Assume
-import org.junit.Before
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -64,11 +61,6 @@
     override val testApp = NonResizeableAppHelper(instrumentation)
     private val colorFadComponent = FlickerComponentName("", "ColorFade BLAST#")
 
-    @Before
-    open fun before() {
-        Assume.assumeFalse(isShellTransitionsEnabled)
-    }
-
     /**
      * Checks that the nav bar layer starts invisible, becomes visible during unlocking animation
      * and remains visible at the end
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
index 4313b8d..02c1a10 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
@@ -17,7 +17,6 @@
 package com.android.server.wm.flicker.launch
 
 import android.app.Instrumentation
-import androidx.test.filters.FlakyTest
 import android.platform.test.annotations.Presubmit
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.server.wm.flicker.FlickerBuilderProvider
@@ -216,7 +215,7 @@
      * Checks that [testApp] window is not on top at the start of the transition, and then becomes
      * the top visible window until the end of the transition.
      */
-    @FlakyTest(bugId = 203538234)
+    @Presubmit
     @Test
     open fun appWindowBecomesTopWindow() {
         testSpec.assertWm {
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 1eb3d8d..c89e6a4 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
@@ -33,12 +33,15 @@
 import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.navBarLayerIsVisible
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.navBarWindowIsVisible
 import com.android.server.wm.flicker.statusBarLayerIsVisible
 import com.android.server.wm.flicker.statusBarWindowIsVisible
 import com.android.server.wm.traces.common.FlickerComponentName
+import org.junit.Assume
+import org.junit.Before
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -70,6 +73,11 @@
 
     private val startDisplayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation)
 
+    @Before
+    open fun before() {
+        Assume.assumeFalse(isShellTransitionsEnabled)
+    }
+
     @FlickerBuilderProvider
     fun buildFlicker(): FlickerBuilder {
         return FlickerBuilder(instrumentation).apply {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt
similarity index 64%
copy from tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest_ShellTransit.kt
copy to tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt
index 8a08d07..b9fef08 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest_ShellTransit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.server.wm.flicker.launch
+package com.android.server.wm.flicker.quickswitch
 
-import androidx.test.filters.FlakyTest
 import android.platform.test.annotations.RequiresDevice
+import androidx.test.filters.FlakyTest
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.annotation.Group1
@@ -30,30 +30,24 @@
 import org.junit.runners.Parameterized
 
 /**
- * Test launching an app while the device is locked
+ * Test quick switching back to previous app from last opened app
  *
- * To run this test: `atest FlickerTests:OpenAppNonResizeableTest`
+ * To run this test: `atest FlickerTests:QuickSwitchBetweenTwoAppsBackTest`
  *
  * Actions:
- *     Lock the device.
- *     Launch an app on top of the lock screen [testApp] and wait animation to complete
+ *     Launch an app [testApp1]
+ *     Launch another app [testApp2]
+ *     Swipe right from the bottom of the screen to quick switch back to the first app [testApp1]
  *
- * Notes:
- *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
- *        are inherited [OpenAppTransition]
- *     2. Part of the test setup occurs automatically via
- *        [com.android.server.wm.flicker.TransitionRunnerWithRules],
- *        including configuring navigation mode, initial orientation and ensuring no
- *        apps are running before setup
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @Group1
-@FlakyTest(bugId = 219688533)
-class OpenAppNonResizeableTest_ShellTransit(testSpec: FlickerTestParameter)
-    : OpenAppNonResizeableTest(testSpec) {
+@FlakyTest(bugId = 228009808)
+open class QuickSwitchBetweenTwoAppsBackTest_ShellTransit(testSpec: FlickerTestParameter)
+    : QuickSwitchBetweenTwoAppsBackTest(testSpec) {
     @Before
     override fun before() {
         Assume.assumeTrue(isShellTransitionsEnabled)
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 5474a42..725d2c3 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
@@ -32,6 +32,7 @@
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.navBarLayerIsVisible
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.navBarWindowIsVisible
@@ -39,6 +40,8 @@
 import com.android.server.wm.flicker.statusBarWindowIsVisible
 import com.android.server.wm.traces.common.FlickerComponentName
 import com.android.server.wm.traces.common.Rect
+import org.junit.Assume
+import org.junit.Before
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -68,6 +71,11 @@
     private val testApp1 = SimpleAppHelper(instrumentation)
     private val testApp2 = NonResizeableAppHelper(instrumentation)
 
+    @Before
+    open fun before() {
+        Assume.assumeFalse(isShellTransitionsEnabled)
+    }
+
     @FlickerBuilderProvider
     fun buildFlicker(): FlickerBuilder {
         return FlickerBuilder(instrumentation).apply {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt
similarity index 64%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest_ShellTransit.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt
index 8a08d07..4b8a8c8 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest_ShellTransit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.server.wm.flicker.launch
+package com.android.server.wm.flicker.quickswitch
 
-import androidx.test.filters.FlakyTest
 import android.platform.test.annotations.RequiresDevice
+import androidx.test.filters.FlakyTest
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.annotation.Group1
@@ -30,30 +30,24 @@
 import org.junit.runners.Parameterized
 
 /**
- * Test launching an app while the device is locked
+ * Test quick switching back to previous app from last opened app
  *
- * To run this test: `atest FlickerTests:OpenAppNonResizeableTest`
+ * To run this test: `atest FlickerTests:QuickSwitchBetweenTwoAppsForwardTest`
  *
  * Actions:
- *     Lock the device.
- *     Launch an app on top of the lock screen [testApp] and wait animation to complete
- *
- * Notes:
- *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
- *        are inherited [OpenAppTransition]
- *     2. Part of the test setup occurs automatically via
- *        [com.android.server.wm.flicker.TransitionRunnerWithRules],
- *        including configuring navigation mode, initial orientation and ensuring no
- *        apps are running before setup
+ *     Launch an app [testApp1]
+ *     Launch another app [testApp2]
+ *     Swipe right from the bottom of the screen to quick switch back to the first app [testApp1]
+ *     Swipe left from the bottom of the screen to quick switch forward to the second app [testApp2]
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @Group1
-@FlakyTest(bugId = 219688533)
-class OpenAppNonResizeableTest_ShellTransit(testSpec: FlickerTestParameter)
-    : OpenAppNonResizeableTest(testSpec) {
+@FlakyTest(bugId = 228009808)
+open class QuickSwitchBetweenTwoAppsForwardTest_ShellTransit(testSpec: FlickerTestParameter)
+    : QuickSwitchBetweenTwoAppsForwardTest(testSpec) {
     @Before
     override fun before() {
         Assume.assumeTrue(isShellTransitionsEnabled)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
index f83ae87..cc4a4b2 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
@@ -28,7 +28,7 @@
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.LAUNCHER_COMPONENT
-import com.android.server.wm.flicker.annotation.Group4
+import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.entireScreenCovered
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
@@ -60,7 +60,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group4
+@Group1
 class QuickSwitchFromLauncherTest(private val testSpec: FlickerTestParameter) {
     private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
     private val taplInstrumentation = LauncherInstrumentation()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
index 2b944c6..8b851e5 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
@@ -25,13 +25,11 @@
 import com.android.server.wm.flicker.annotation.Group3
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.rules.WMFlickerServiceRuleForTestSpec
 import com.android.server.wm.flicker.statusBarLayerIsVisible
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.server.wm.flicker.statusBarWindowIsVisible
 import com.android.server.wm.traces.common.FlickerComponentName
-import org.junit.Assume
 import org.junit.FixMethodOrder
 import org.junit.Rule
 import org.junit.Test
@@ -100,7 +98,7 @@
      * Windows maybe recreated when rotated. Checks that the focus does not change or if it does,
      * focus returns to [testApp]
      */
-    @FlakyTest(bugId = 190185577)
+    @Presubmit
     @Test
     fun focusChanges() {
         testSpec.assertEventLog {
@@ -130,18 +128,6 @@
     @Presubmit
     @Test
     fun rotationLayerAppearsAndVanishes() {
-        Assume.assumeFalse(isShellTransitionsEnabled)
-        rotationLayerAppearsAndVanishesAssertion()
-    }
-
-    /**
-     * Checks that the [FlickerComponentName.ROTATION] layer appears during the transition,
-     * doesn't flicker, and disappears before the transition is complete
-     */
-    @FlakyTest(bugId = 218484127)
-    @Test
-    fun rotationLayerAppearsAndVanishes_shellTransit() {
-        Assume.assumeTrue(isShellTransitionsEnabled)
         rotationLayerAppearsAndVanishesAssertion()
     }
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
index 15fd5e1..fac5baf 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
@@ -26,11 +26,8 @@
 import com.android.server.wm.flicker.annotation.Group3
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.SeamlessRotationAppHelper
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.testapp.ActivityOptions
 import com.android.server.wm.traces.common.FlickerComponentName
-import org.junit.Assume
-import org.junit.Before
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -84,11 +81,6 @@
 ) : RotationTransition(testSpec) {
     override val testApp = SeamlessRotationAppHelper(instrumentation)
 
-    @Before
-    open fun before() {
-        Assume.assumeFalse(isShellTransitionsEnabled)
-    }
-
     override val transition: FlickerBuilder.() -> Unit
         get() = {
             super.transition(this)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest_ShellTransit.kt
deleted file mode 100644
index d397d59..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest_ShellTransit.kt
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker.rotation
-
-import androidx.test.filters.FlakyTest
-import android.platform.test.annotations.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.annotation.Group3
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
-import org.junit.Assume
-import org.junit.Before
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test opening an app and cycling through app rotations using seamless rotations
- *
- * Currently runs:
- *      0 -> 90 degrees
- *      0 -> 90 degrees (with starved UI thread)
- *      90 -> 0 degrees
- *      90 -> 0 degrees (with starved UI thread)
- *
- * Actions:
- *     Launch an app in fullscreen and supporting seamless rotation (via intent)
- *     Set initial device orientation
- *     Start tracing
- *     Change device orientation
- *     Stop tracing
- *
- * To run this test: `atest FlickerTests:SeamlessAppRotationTest`
- *
- * To run only the presubmit assertions add: `--
- *      --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
- *      --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Presubmit`
- *
- * To run only the postsubmit assertions add: `--
- *      --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
- *      --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Postsubmit`
- *
- * To run only the flaky assertions add: `--
- *      --module-arg FlickerTests:include-annotation:androidx.test.filters.FlakyTest`
- *
- * Notes:
- *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
- *        are inherited [RotationTransition]
- *     2. Part of the test setup occurs automatically via
- *        [com.android.server.wm.flicker.TransitionRunnerWithRules],
- *        including configuring navigation mode, initial orientation and ensuring no
- *        apps are running before setup
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group3
-@FlakyTest(bugId = 219689723)
-class SeamlessAppRotationTest_ShellTransit(
-    testSpec: FlickerTestParameter
-) : SeamlessAppRotationTest(testSpec) {
-    @Before
-    override fun before() {
-        Assume.assumeTrue(isShellTransitionsEnabled)
-    }
-}
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index 739fe02..7f513b2 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -119,5 +119,16 @@
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
+        <activity android:name=".ImeEditorPopupDialogActivity"
+            android:taskAffinity="com.android.server.wm.flicker.testapp.ImeEditorPopupDialogActivity"
+            android:configChanges="orientation|screenSize"
+            android:theme="@style/CutoutShortEdges"
+            android:label="ImeEditorPopupDialogActivity"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
     </application>
 </manifest>
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
index 3040a09..18c95cf 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
@@ -56,6 +56,7 @@
     public static final ComponentName LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME =
             new ComponentName(FLICKER_APP_PACKAGE,
                     FLICKER_APP_PACKAGE + ".LaunchNewTaskActivity");
+
     public static final String DIALOG_THEMED_ACTIVITY = "DialogThemedActivity";
     public static final ComponentName DIALOG_THEMED_ACTIVITY_COMPONENT_NAME =
             new ComponentName(FLICKER_APP_PACKAGE,
@@ -65,4 +66,10 @@
     public static final ComponentName PORTRAIT_ONLY_ACTIVITY_COMPONENT_NAME =
             new ComponentName(FLICKER_APP_PACKAGE,
                     FLICKER_APP_PACKAGE + ".PortraitOnlyActivity");
+
+    public static final String EDITOR_POPUP_DIALOG_ACTIVITY_LAUNCHER_NAME =
+            "ImeEditorPopupDialogActivity";
+    public static final ComponentName EDITOR_POPUP_DIALOG_ACTIVITY_COMPONENT_NAME =
+            new ComponentName(FLICKER_APP_PACKAGE,
+                    FLICKER_APP_PACKAGE + ".ImeEditorPopupDialogActivity");
 }
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeEditorPopupDialogActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeEditorPopupDialogActivity.java
new file mode 100644
index 0000000..a8613f5
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeEditorPopupDialogActivity.java
@@ -0,0 +1,46 @@
+/*
+ * 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.wm.flicker.testapp;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.os.Bundle;
+import android.view.WindowManager;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+
+public class ImeEditorPopupDialogActivity extends Activity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        WindowManager.LayoutParams p = getWindow().getAttributes();
+        p.layoutInDisplayCutoutMode = WindowManager.LayoutParams
+                .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+        getWindow().setAttributes(p);
+        LinearLayout layout = new LinearLayout(this);
+        layout.setOrientation(LinearLayout.VERTICAL);
+        setContentView(R.layout.activity_simple);
+
+        final EditText editText = new EditText(this);
+        editText.setHint("focused editText");
+        final AlertDialog dialog = new AlertDialog.Builder(this)
+                .setView(editText)
+                .setPositiveButton("Dismiss", (d, which) -> d.dismiss())
+                .create();
+        dialog.show();
+    }
+}
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
index 4cfa93b..841b81c 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
@@ -252,6 +252,9 @@
                         && TEST_TCP_BUFFER_SIZES_2.equals(lp.getTcpBufferSizes())));
         verify(mNetworkAgent)
                 .setUnderlyingNetworks(eq(singletonList(TEST_UNDERLYING_NETWORK_RECORD_2.network)));
+
+        // Verify revalidation is triggered on VCN network
+        verify(mConnMgr).reportNetworkConnectivity(eq(mNetworkAgent.getNetwork()), eq(false));
     }
 
     private void triggerChildOpened() {
@@ -425,6 +428,9 @@
         triggerValidation(NetworkAgent.VALIDATION_STATUS_NOT_VALID);
         mTestLooper.dispatchAll();
 
+        verify(mConnMgr)
+                .reportNetworkConnectivity(eq(TEST_UNDERLYING_NETWORK_RECORD_1.network), eq(false));
+
         final ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
         verify(mDeps, times(2))
                 .newWakeupMessage(
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index bd0a4bc..bfb3285 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -165,6 +165,7 @@
     ],
     proto: {
         export_proto_headers: true,
+        type: "full",
     },
     defaults: ["aapt2_defaults"],
 }
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
index b165c6b..7b94e71 100644
--- a/tools/aapt2/Android.mk
+++ b/tools/aapt2/Android.mk
@@ -15,6 +15,8 @@
 $(aapt2_results): $(HOST_OUT_NATIVE_TESTS)/aapt2_tests/aapt2_tests
 	-$(HOST_OUT_NATIVE_TESTS)/aapt2_tests/aapt2_tests --gtest_output=xml:$@ > /dev/null 2>&1
 
+$(call declare-0p-target,$(aapt2_results))
+
 aapt2_results :=
 
 include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tools/aapt2/util/Files.cpp b/tools/aapt2/util/Files.cpp
index a266b47..3285d8b 100644
--- a/tools/aapt2/util/Files.cpp
+++ b/tools/aapt2/util/Files.cpp
@@ -349,7 +349,7 @@
   const std::string root_dir = path.to_string();
   std::unique_ptr<DIR, decltype(closedir)*> d(opendir(root_dir.data()), closedir);
   if (!d) {
-    diag->Error(DiagMessage() << SystemErrorCodeToString(errno));
+    diag->Error(DiagMessage() << SystemErrorCodeToString(errno) << ": " << root_dir);
     return {};
   }