Merge "Fix modeOnCanceled for LOCKSCREEN <=> AOD transitions" into main
diff --git a/PACKAGE_MANAGER_OWNERS b/PACKAGE_MANAGER_OWNERS
index eb5842b..45719a7 100644
--- a/PACKAGE_MANAGER_OWNERS
+++ b/PACKAGE_MANAGER_OWNERS
@@ -1,3 +1,6 @@
+# Bug component: 36137
+# Bug template url: https://b.corp.google.com/issues/new?component=36137&template=198919
+
 alexbuy@google.com
 patb@google.com
 schfan@google.com
\ No newline at end of file
diff --git a/api/Android.bp b/api/Android.bp
index 8e06366..093ee4b 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -298,6 +298,28 @@
     "org.xmlpull",
 ]
 
+// These are libs from framework-internal-utils that are required (i.e. being referenced)
+// from framework-non-updatable-sources. Add more here when there's a need.
+// DO NOT add the entire framework-internal-utils. It might cause unnecessary circular
+// dependencies gets bigger.
+android_non_updatable_stubs_libs = [
+    "android.hardware.cas-V1.2-java",
+    "android.hardware.health-V1.0-java-constants",
+    "android.hardware.thermal-V1.0-java-constants",
+    "android.hardware.thermal-V2.0-java",
+    "android.hardware.tv.input-V1.0-java-constants",
+    "android.hardware.usb-V1.0-java-constants",
+    "android.hardware.usb-V1.1-java-constants",
+    "android.hardware.usb.gadget-V1.0-java",
+    "android.hardware.vibrator-V1.3-java",
+    "framework-protos",
+]
+
+java_defaults {
+    name: "android-non-updatable-stubs-libs-defaults",
+    libs: android_non_updatable_stubs_libs,
+}
+
 // Defaults for all stubs that include the non-updatable framework. These defaults do not include
 // module symbols, so will not compile correctly on their own. Users must add module APIs to the
 // classpath (or sources) somehow.
@@ -329,18 +351,7 @@
     // from framework-non-updatable-sources. Add more here when there's a need.
     // DO NOT add the entire framework-internal-utils. It might cause unnecessary circular
     // dependencies gets bigger.
-    libs: [
-        "android.hardware.cas-V1.2-java",
-        "android.hardware.health-V1.0-java-constants",
-        "android.hardware.thermal-V1.0-java-constants",
-        "android.hardware.thermal-V2.0-java",
-        "android.hardware.tv.input-V1.0-java-constants",
-        "android.hardware.usb-V1.0-java-constants",
-        "android.hardware.usb-V1.1-java-constants",
-        "android.hardware.usb.gadget-V1.0-java",
-        "android.hardware.vibrator-V1.3-java",
-        "framework-protos",
-    ],
+    libs: android_non_updatable_stubs_libs,
     flags: [
         "--error NoSettingsProvider",
         "--error UnhiddenSystemApi",
diff --git a/cmds/telecom/Android.bp b/cmds/telecom/Android.bp
index be02710..494d2ae3 100644
--- a/cmds/telecom/Android.bp
+++ b/cmds/telecom/Android.bp
@@ -21,5 +21,8 @@
 java_binary {
     name: "telecom",
     wrapper: "telecom.sh",
-    srcs: ["**/*.java"],
+    srcs: [
+        ":telecom-shell-commands-src",
+        "**/*.java",
+    ],
 }
diff --git a/cmds/telecom/src/com/android/commands/telecom/Telecom.java b/cmds/telecom/src/com/android/commands/telecom/Telecom.java
index 1488e14cf..50af5a7 100644
--- a/cmds/telecom/src/com/android/commands/telecom/Telecom.java
+++ b/cmds/telecom/src/com/android/commands/telecom/Telecom.java
@@ -17,30 +17,22 @@
 package com.android.commands.telecom;
 
 import android.app.ActivityThread;
-import android.content.ComponentName;
 import android.content.Context;
-import android.net.Uri;
-import android.os.IUserManager;
 import android.os.Looper;
-import android.os.Process;
-import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.os.UserHandle;
-import android.sysprop.TelephonyProperties;
-import android.telecom.Log;
-import android.telecom.PhoneAccount;
-import android.telecom.PhoneAccountHandle;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
 
-import com.android.internal.os.BaseCommand;
 import com.android.internal.telecom.ITelecomService;
+import com.android.server.telecom.TelecomShellCommand;
 
-import java.io.PrintStream;
-import java.util.Arrays;
-import java.util.stream.Collectors;
+import java.io.FileDescriptor;
 
-public final class Telecom extends BaseCommand {
+/**
+ * @deprecated Use {@code com.android.server.telecom.TelecomShellCommand} instead and execute the
+ * shell command using {@code adb shell cmd telecom...}. This is only here for backwards
+ * compatibility reasons.
+ */
+@Deprecated
+public final class Telecom {
 
     /**
      * Command-line entry point.
@@ -52,458 +44,11 @@
         // TODO: Do it in zygote and RuntimeInit. b/148897549
         ActivityThread.initializeMainlineModules();
 
-      (new Telecom()).run(args);
-    }
-    private static final String CALLING_PACKAGE = Telecom.class.getPackageName();
-    private static final String COMMAND_SET_PHONE_ACCOUNT_ENABLED = "set-phone-account-enabled";
-    private static final String COMMAND_SET_PHONE_ACCOUNT_DISABLED = "set-phone-account-disabled";
-    private static final String COMMAND_REGISTER_PHONE_ACCOUNT = "register-phone-account";
-    private static final String COMMAND_SET_USER_SELECTED_OUTGOING_PHONE_ACCOUNT =
-            "set-user-selected-outgoing-phone-account";
-    private static final String COMMAND_REGISTER_SIM_PHONE_ACCOUNT = "register-sim-phone-account";
-    private static final String COMMAND_SET_TEST_CALL_REDIRECTION_APP = "set-test-call-redirection-app";
-    private static final String COMMAND_SET_TEST_CALL_SCREENING_APP = "set-test-call-screening-app";
-    private static final String COMMAND_ADD_OR_REMOVE_CALL_COMPANION_APP =
-            "add-or-remove-call-companion-app";
-    private static final String COMMAND_SET_PHONE_ACCOUNT_SUGGESTION_COMPONENT =
-            "set-phone-acct-suggestion-component";
-    private static final String COMMAND_UNREGISTER_PHONE_ACCOUNT = "unregister-phone-account";
-    private static final String COMMAND_SET_CALL_DIAGNOSTIC_SERVICE = "set-call-diagnostic-service";
-    private static final String COMMAND_SET_DEFAULT_DIALER = "set-default-dialer";
-    private static final String COMMAND_GET_DEFAULT_DIALER = "get-default-dialer";
-    private static final String COMMAND_STOP_BLOCK_SUPPRESSION = "stop-block-suppression";
-    private static final String COMMAND_CLEANUP_STUCK_CALLS = "cleanup-stuck-calls";
-    private static final String COMMAND_CLEANUP_ORPHAN_PHONE_ACCOUNTS =
-            "cleanup-orphan-phone-accounts";
-    private static final String COMMAND_RESET_CAR_MODE = "reset-car-mode";
-    private static final String COMMAND_IS_NON_IN_CALL_SERVICE_BOUND =
-            "is-non-ui-in-call-service-bound";
-
-    /**
-     * Change the system dialer package name if a package name was specified,
-     * Example: adb shell telecom set-system-dialer <PACKAGE>
-     *
-     * Restore it to the default if if argument is "default" or no argument is passed.
-     * Example: adb shell telecom set-system-dialer default
-     */
-    private static final String COMMAND_SET_SYSTEM_DIALER = "set-system-dialer";
-    private static final String COMMAND_GET_SYSTEM_DIALER = "get-system-dialer";
-    private static final String COMMAND_WAIT_ON_HANDLERS = "wait-on-handlers";
-    private static final String COMMAND_SET_SIM_COUNT = "set-sim-count";
-    private static final String COMMAND_GET_SIM_CONFIG = "get-sim-config";
-    private static final String COMMAND_GET_MAX_PHONES = "get-max-phones";
-    private static final String COMMAND_SET_TEST_EMERGENCY_PHONE_ACCOUNT_PACKAGE_FILTER =
-            "set-test-emergency-phone-account-package-filter";
-    /**
-     * Command used to emit a distinct "mark" in the logs.
-     */
-    private static final String COMMAND_LOG_MARK = "log-mark";
-
-    private ComponentName mComponent;
-    private String mAccountId;
-    private ITelecomService mTelecomService;
-    private TelephonyManager mTelephonyManager;
-    private IUserManager mUserManager;
-
-    @Override
-    public void onShowUsage(PrintStream out) {
-        out.println("usage: telecom [subcommand] [options]\n"
-                + "usage: telecom set-phone-account-enabled <COMPONENT> <ID> <USER_SN>\n"
-                + "usage: telecom set-phone-account-disabled <COMPONENT> <ID> <USER_SN>\n"
-                + "usage: telecom register-phone-account <COMPONENT> <ID> <USER_SN> <LABEL>\n"
-                + "usage: telecom register-sim-phone-account [-e] <COMPONENT> <ID> <USER_SN>"
-                        + " <LABEL>: registers a PhoneAccount with CAPABILITY_SIM_SUBSCRIPTION"
-                        + " and optionally CAPABILITY_PLACE_EMERGENCY_CALLS if \"-e\" is provided\n"
-                + "usage: telecom set-user-selected-outgoing-phone-account [-e] <COMPONENT> <ID> "
-                + "<USER_SN>\n"
-                + "usage: telecom set-test-call-redirection-app <PACKAGE>\n"
-                + "usage: telecom set-test-call-screening-app <PACKAGE>\n"
-                + "usage: telecom set-phone-acct-suggestion-component <COMPONENT>\n"
-                + "usage: telecom add-or-remove-call-companion-app <PACKAGE> <1/0>\n"
-                + "usage: telecom register-sim-phone-account <COMPONENT> <ID> <USER_SN>"
-                + " <LABEL> <ADDRESS>\n"
-                + "usage: telecom unregister-phone-account <COMPONENT> <ID> <USER_SN>\n"
-                + "usage: telecom set-call-diagnostic-service <PACKAGE>\n"
-                + "usage: telecom set-default-dialer <PACKAGE>\n"
-                + "usage: telecom get-default-dialer\n"
-                + "usage: telecom get-system-dialer\n"
-                + "usage: telecom wait-on-handlers\n"
-                + "usage: telecom set-sim-count <COUNT>\n"
-                + "usage: telecom get-sim-config\n"
-                + "usage: telecom get-max-phones\n"
-                + "usage: telecom stop-block-suppression: Stop suppressing the blocked number"
-                        + " provider after a call to emergency services.\n"
-                + "usage: telecom cleanup-stuck-calls: Clear any disconnected calls that have"
-                + " gotten wedged in Telecom.\n"
-                + "usage: telecom cleanup-orphan-phone-accounts: remove any phone accounts that"
-                + " no longer have a valid UserHandle or accounts that no longer belongs to an"
-                + " installed package.\n"
-                + "usage: telecom set-emer-phone-account-filter <PACKAGE>\n"
-                + "\n"
-                + "telecom set-phone-account-enabled: Enables the given phone account, if it has"
-                        + " already been registered with Telecom.\n"
-                + "\n"
-                + "telecom set-phone-account-disabled: Disables the given phone account, if it"
-                        + " has already been registered with telecom.\n"
-                + "\n"
-                + "telecom set-call-diagnostic-service: overrides call diagnostic service.\n"
-                + "telecom set-default-dialer: Sets the override default dialer to the given"
-                        + " component; this will override whatever the dialer role is set to.\n"
-                + "\n"
-                + "telecom get-default-dialer: Displays the current default dialer.\n"
-                + "\n"
-                + "telecom get-system-dialer: Displays the current system dialer.\n"
-                + "telecom set-system-dialer: Set the override system dialer to the given"
-                        + " component. To remove the override, send \"default\"\n"
-                + "\n"
-                + "telecom wait-on-handlers: Wait until all handlers finish their work.\n"
-                + "\n"
-                + "telecom set-sim-count: Set num SIMs (2 for DSDS, 1 for single SIM."
-                        + " This may restart the device.\n"
-                + "\n"
-                + "telecom get-sim-config: Get the mSIM config string. \"DSDS\" for DSDS mode,"
-                        + " or \"\" for single SIM\n"
-                + "\n"
-                + "telecom get-max-phones: Get the max supported phones from the modem.\n"
-                + "telecom set-test-emergency-phone-account-package-filter <PACKAGE>: sets a"
-                        + " package name that will be used for test emergency calls. To clear,"
-                        + " send an empty package name. Real emergency calls will still be placed"
-                        + " over Telephony.\n"
-                + "telecom log-mark <MESSAGE>: emits a message into the telecom logs.  Useful for "
-                        + "testers to indicate where in the logs various test steps take place.\n"
-                + "telecom is-non-ui-in-call-service-bound <PACKAGE>: queries a particular "
-                + "non-ui-InCallService in InCallController to determine if it is bound \n"
-        );
-    }
-
-    @Override
-    public void onRun() throws Exception {
-        mTelecomService = ITelecomService.Stub.asInterface(
-                ServiceManager.getService(Context.TELECOM_SERVICE));
-        if (mTelecomService == null) {
-            Log.w(this, "onRun: Can't access telecom manager.");
-            showError("Error: Could not access the Telecom Manager. Is the system running?");
-            return;
-        }
-
         Looper.prepareMainLooper();
+        ITelecomService service = ITelecomService.Stub.asInterface(
+                ServiceManager.getService(Context.TELECOM_SERVICE));
         Context context = ActivityThread.systemMain().getSystemContext();
-        mTelephonyManager = context.getSystemService(TelephonyManager.class);
-        if (mTelephonyManager == null) {
-            Log.w(this, "onRun: Can't access telephony service.");
-            showError("Error: Could not access the Telephony Service. Is the system running?");
-            return;
-        }
-
-        mUserManager = IUserManager.Stub
-                .asInterface(ServiceManager.getService(Context.USER_SERVICE));
-        if (mUserManager == null) {
-            Log.w(this, "onRun: Can't access user manager.");
-            showError("Error: Could not access the User Manager. Is the system running?");
-            return;
-        }
-        Log.i(this, "onRun: parsing command.");
-        String command = nextArgRequired();
-        switch (command) {
-            case COMMAND_SET_PHONE_ACCOUNT_ENABLED:
-                runSetPhoneAccountEnabled(true);
-                break;
-            case COMMAND_SET_PHONE_ACCOUNT_DISABLED:
-                runSetPhoneAccountEnabled(false);
-                break;
-            case COMMAND_REGISTER_PHONE_ACCOUNT:
-                runRegisterPhoneAccount();
-                break;
-            case COMMAND_SET_TEST_CALL_REDIRECTION_APP:
-                runSetTestCallRedirectionApp();
-                break;
-            case COMMAND_SET_TEST_CALL_SCREENING_APP:
-                runSetTestCallScreeningApp();
-                break;
-            case COMMAND_ADD_OR_REMOVE_CALL_COMPANION_APP:
-                runAddOrRemoveCallCompanionApp();
-                break;
-            case COMMAND_SET_PHONE_ACCOUNT_SUGGESTION_COMPONENT:
-                runSetTestPhoneAcctSuggestionComponent();
-                break;
-            case COMMAND_SET_CALL_DIAGNOSTIC_SERVICE:
-                runSetCallDiagnosticService();
-                break;
-            case COMMAND_REGISTER_SIM_PHONE_ACCOUNT:
-                runRegisterSimPhoneAccount();
-                break;
-            case COMMAND_SET_USER_SELECTED_OUTGOING_PHONE_ACCOUNT:
-                runSetUserSelectedOutgoingPhoneAccount();
-                break;
-            case COMMAND_UNREGISTER_PHONE_ACCOUNT:
-                runUnregisterPhoneAccount();
-                break;
-            case COMMAND_STOP_BLOCK_SUPPRESSION:
-                runStopBlockSuppression();
-                break;
-            case COMMAND_CLEANUP_STUCK_CALLS:
-                runCleanupStuckCalls();
-                break;
-            case COMMAND_CLEANUP_ORPHAN_PHONE_ACCOUNTS:
-                runCleanupOrphanPhoneAccounts();
-                break;
-            case COMMAND_RESET_CAR_MODE:
-                runResetCarMode();
-                break;
-            case COMMAND_SET_DEFAULT_DIALER:
-                runSetDefaultDialer();
-                break;
-            case COMMAND_GET_DEFAULT_DIALER:
-                runGetDefaultDialer();
-                break;
-            case COMMAND_SET_SYSTEM_DIALER:
-                runSetSystemDialer();
-                break;
-            case COMMAND_GET_SYSTEM_DIALER:
-                runGetSystemDialer();
-                break;
-            case COMMAND_WAIT_ON_HANDLERS:
-                runWaitOnHandler();
-                break;
-            case COMMAND_SET_SIM_COUNT:
-                runSetSimCount();
-                break;
-            case COMMAND_GET_SIM_CONFIG:
-                runGetSimConfig();
-                break;
-            case COMMAND_GET_MAX_PHONES:
-                runGetMaxPhones();
-                break;
-            case COMMAND_IS_NON_IN_CALL_SERVICE_BOUND:
-                runIsNonUiInCallServiceBound();
-                break;
-            case COMMAND_SET_TEST_EMERGENCY_PHONE_ACCOUNT_PACKAGE_FILTER:
-                runSetEmergencyPhoneAccountPackageFilter();
-                break;
-            case COMMAND_LOG_MARK:
-                runLogMark();
-                break;
-            default:
-                Log.w(this, "onRun: unknown command: %s", command);
-                throw new IllegalArgumentException ("unknown command '" + command + "'");
-        }
-    }
-
-    private void runSetPhoneAccountEnabled(boolean enabled) throws RemoteException {
-        final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs();
-        final boolean success =  mTelecomService.enablePhoneAccount(handle, enabled);
-        if (success) {
-            System.out.println("Success - " + handle + (enabled ? " enabled." : " disabled."));
-        } else {
-            System.out.println("Error - is " + handle + " a valid PhoneAccount?");
-        }
-    }
-
-    private void runRegisterPhoneAccount() throws RemoteException {
-        final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs();
-        final String label = nextArgRequired();
-        PhoneAccount account = PhoneAccount.builder(handle, label)
-                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER).build();
-        mTelecomService.registerPhoneAccount(account, CALLING_PACKAGE);
-        System.out.println("Success - " + handle + " registered.");
-    }
-
-    private void runRegisterSimPhoneAccount() throws RemoteException {
-        boolean isEmergencyAccount = false;
-        String opt;
-        while ((opt = nextOption()) != null) {
-            switch (opt) {
-                case "-e": {
-                    isEmergencyAccount = true;
-                    break;
-                }
-            }
-        }
-        final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs();
-        final String label = nextArgRequired();
-        final String address = nextArgRequired();
-        int capabilities = PhoneAccount.CAPABILITY_CALL_PROVIDER
-                | PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION
-                | (isEmergencyAccount ? PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS : 0);
-        PhoneAccount account = PhoneAccount.builder(
-            handle, label)
-                .setAddress(Uri.parse(address))
-                .setSubscriptionAddress(Uri.parse(address))
-                .setCapabilities(capabilities)
-                .setShortDescription(label)
-                .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
-                .addSupportedUriScheme(PhoneAccount.SCHEME_VOICEMAIL)
-                .build();
-        mTelecomService.registerPhoneAccount(account, CALLING_PACKAGE);
-        System.out.println("Success - " + handle + " registered.");
-    }
-
-    private void runSetTestCallRedirectionApp() throws RemoteException {
-        final String packageName = nextArg();
-        mTelecomService.setTestDefaultCallRedirectionApp(packageName);
-    }
-
-    private void runSetTestCallScreeningApp() throws RemoteException {
-        final String packageName = nextArg();
-        mTelecomService.setTestDefaultCallScreeningApp(packageName);
-    }
-
-    private void runAddOrRemoveCallCompanionApp() throws RemoteException {
-        final String packageName = nextArgRequired();
-        String isAdded = nextArgRequired();
-        boolean isAddedBool = "1".equals(isAdded);
-        mTelecomService.addOrRemoveTestCallCompanionApp(packageName, isAddedBool);
-    }
-
-    private void runSetCallDiagnosticService() throws RemoteException {
-        String packageName = nextArg();
-        if ("default".equals(packageName)) packageName = null;
-        mTelecomService.setTestCallDiagnosticService(packageName);
-        System.out.println("Success - " + packageName + " set as call diagnostic service.");
-    }
-
-    private void runSetTestPhoneAcctSuggestionComponent() throws RemoteException {
-        final String componentName = nextArg();
-        mTelecomService.setTestPhoneAcctSuggestionComponent(componentName);
-    }
-
-    private void runSetUserSelectedOutgoingPhoneAccount() throws RemoteException {
-        Log.i(this, "runSetUserSelectedOutgoingPhoneAccount");
-        final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs();
-        mTelecomService.setUserSelectedOutgoingPhoneAccount(handle);
-        System.out.println("Success - " + handle + " set as default outgoing account.");
-    }
-
-    private void runUnregisterPhoneAccount() throws RemoteException {
-        final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs();
-        mTelecomService.unregisterPhoneAccount(handle, CALLING_PACKAGE);
-        System.out.println("Success - " + handle + " unregistered.");
-    }
-
-    private void runStopBlockSuppression() throws RemoteException {
-        mTelecomService.stopBlockSuppression();
-    }
-
-    private void runCleanupStuckCalls() throws RemoteException {
-        mTelecomService.cleanupStuckCalls();
-    }
-
-    private void runCleanupOrphanPhoneAccounts() throws RemoteException {
-        System.out.println("Success - cleaned up " + mTelecomService.cleanupOrphanPhoneAccounts()
-                + "  phone accounts.");
-    }
-
-    private void runResetCarMode() throws RemoteException {
-        mTelecomService.resetCarMode();
-    }
-
-    private void runSetDefaultDialer() throws RemoteException {
-        String packageName = nextArg();
-        if ("default".equals(packageName)) packageName = null;
-        mTelecomService.setTestDefaultDialer(packageName);
-        System.out.println("Success - " + packageName + " set as override default dialer.");
-    }
-
-    private void runSetSystemDialer() throws RemoteException {
-        final String flatComponentName = nextArg();
-        final ComponentName componentName = (flatComponentName.equals("default")
-                ? null : parseComponentName(flatComponentName));
-        mTelecomService.setSystemDialer(componentName);
-        System.out.println("Success - " + componentName + " set as override system dialer.");
-    }
-
-    private void runGetDefaultDialer() throws RemoteException {
-        System.out.println(mTelecomService.getDefaultDialerPackage(CALLING_PACKAGE));
-    }
-
-    private void runGetSystemDialer() throws RemoteException {
-        System.out.println(mTelecomService.getSystemDialerPackage(CALLING_PACKAGE));
-    }
-
-    private void runWaitOnHandler() throws RemoteException {
-
-    }
-
-    private void runSetSimCount() throws RemoteException {
-        if (!callerIsRoot()) {
-            System.out.println("set-sim-count requires adb root");
-            return;
-        }
-        int numSims = Integer.parseInt(nextArgRequired());
-        System.out.println("Setting sim count to " + numSims + ". Device may reboot");
-        mTelephonyManager.switchMultiSimConfig(numSims);
-    }
-
-    /**
-     * prints out whether a particular non-ui InCallServices is bound in a call
-     */
-    public void runIsNonUiInCallServiceBound() throws RemoteException {
-        if (TextUtils.isEmpty(mArgs.peekNextArg())) {
-            System.out.println("No Argument passed. Please pass a <PACKAGE_NAME> to lookup.");
-        } else {
-            System.out.println(
-                    String.valueOf(mTelecomService.isNonUiInCallServiceBound(nextArg())));
-        }
-    }
-
-    /**
-     * Prints the mSIM config to the console.
-     * "DSDS" for a phone in DSDS mode
-     * "" (empty string) for a phone in SS mode
-     */
-    private void runGetSimConfig() throws RemoteException {
-        System.out.println(TelephonyProperties.multi_sim_config().orElse(""));
-    }
-
-    private void runGetMaxPhones() throws RemoteException {
-        // how many logical modems can be potentially active simultaneously
-        System.out.println(mTelephonyManager.getSupportedModemCount());
-    }
-
-    private void runSetEmergencyPhoneAccountPackageFilter() throws RemoteException {
-        String packageName = mArgs.getNextArg();
-        if (TextUtils.isEmpty(packageName)) {
-            mTelecomService.setTestEmergencyPhoneAccountPackageNameFilter(null);
-            System.out.println("Success - filter cleared");
-        } else {
-            mTelecomService.setTestEmergencyPhoneAccountPackageNameFilter(packageName);
-            System.out.println("Success = filter set to " + packageName);
-        }
-
-    }
-
-    private void runLogMark() throws RemoteException {
-        String message = Arrays.stream(mArgs.peekRemainingArgs()).collect(Collectors.joining(" "));
-        mTelecomService.requestLogMark(message);
-    }
-
-    private PhoneAccountHandle getPhoneAccountHandleFromArgs() throws RemoteException {
-        if (TextUtils.isEmpty(mArgs.peekNextArg())) {
-            return null;
-        }
-        final ComponentName component = parseComponentName(nextArgRequired());
-        final String accountId = nextArgRequired();
-        final String userSnInStr = nextArgRequired();
-        UserHandle userHandle;
-        try {
-            final int userSn = Integer.parseInt(userSnInStr);
-            userHandle = UserHandle.of(mUserManager.getUserHandle(userSn));
-        } catch (NumberFormatException ex) {
-            Log.w(this, "getPhoneAccountHandleFromArgs - invalid user %s", userSnInStr);
-            throw new IllegalArgumentException ("Invalid user serial number " + userSnInStr);
-        }
-        return new PhoneAccountHandle(component, accountId, userHandle);
-    }
-
-    private boolean callerIsRoot() {
-        return Process.ROOT_UID == Process.myUid();
-    }
-
-    private ComponentName parseComponentName(String component) {
-        ComponentName cn = ComponentName.unflattenFromString(component);
-        if (cn == null) {
-            throw new IllegalArgumentException ("Invalid component " + component);
-        }
-        return cn;
+        new TelecomShellCommand(service, context).exec(null, FileDescriptor.in,
+                FileDescriptor.out, FileDescriptor.err, args);
     }
 }
diff --git a/core/api/current.txt b/core/api/current.txt
index 42ac6b7..b9ad92b 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -5395,7 +5395,7 @@
 
   public final class AutomaticZenRule implements android.os.Parcelable {
     ctor @Deprecated public AutomaticZenRule(String, android.content.ComponentName, android.net.Uri, int, boolean);
-    ctor public AutomaticZenRule(@NonNull String, @Nullable android.content.ComponentName, @Nullable android.content.ComponentName, @NonNull android.net.Uri, @Nullable android.service.notification.ZenPolicy, int, boolean);
+    ctor @Deprecated public AutomaticZenRule(@NonNull String, @Nullable android.content.ComponentName, @Nullable android.content.ComponentName, @NonNull android.net.Uri, @Nullable android.service.notification.ZenPolicy, int, boolean);
     ctor public AutomaticZenRule(android.os.Parcel);
     method public int describeContents();
     method public android.net.Uri getConditionId();
@@ -6674,9 +6674,9 @@
     method @Deprecated public android.app.Notification.Builder addPerson(String);
     method @NonNull public android.app.Notification.Builder addPerson(android.app.Person);
     method @NonNull public android.app.Notification build();
-    method public android.widget.RemoteViews createBigContentView();
-    method public android.widget.RemoteViews createContentView();
-    method public android.widget.RemoteViews createHeadsUpContentView();
+    method @Deprecated public android.widget.RemoteViews createBigContentView();
+    method @Deprecated public android.widget.RemoteViews createContentView();
+    method @Deprecated public android.widget.RemoteViews createHeadsUpContentView();
     method @NonNull public android.app.Notification.Builder extend(android.app.Notification.Extender);
     method public android.os.Bundle getExtras();
     method @Deprecated public android.app.Notification getNotification();
@@ -9608,7 +9608,7 @@
     method public void partiallyUpdateAppWidget(int, android.widget.RemoteViews);
     method @FlaggedApi("android.appwidget.flags.generated_previews") public void removeWidgetPreview(@NonNull android.content.ComponentName, int);
     method public boolean requestPinAppWidget(@NonNull android.content.ComponentName, @Nullable android.os.Bundle, @Nullable android.app.PendingIntent);
-    method @FlaggedApi("android.appwidget.flags.generated_previews") public void setWidgetPreview(@NonNull android.content.ComponentName, int, @NonNull android.widget.RemoteViews);
+    method @FlaggedApi("android.appwidget.flags.generated_previews") public boolean setWidgetPreview(@NonNull android.content.ComponentName, int, @NonNull android.widget.RemoteViews);
     method public void updateAppWidget(int[], android.widget.RemoteViews);
     method public void updateAppWidget(int, android.widget.RemoteViews);
     method public void updateAppWidget(android.content.ComponentName, android.widget.RemoteViews);
@@ -15708,7 +15708,7 @@
     method public boolean clipRect(int, int, int, int);
     method @FlaggedApi("com.android.graphics.hwui.flags.clip_shader") public void clipShader(@NonNull android.graphics.Shader);
     method public void concat(@Nullable android.graphics.Matrix);
-    method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void concat44(@Nullable android.graphics.Matrix44);
+    method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void concat(@Nullable android.graphics.Matrix44);
     method public void disableZ();
     method public void drawARGB(int, int, int, int);
     method public void drawArc(@NonNull android.graphics.RectF, float, float, boolean, @NonNull android.graphics.Paint);
@@ -16361,7 +16361,7 @@
     ctor @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public Matrix44();
     ctor @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public Matrix44(@NonNull android.graphics.Matrix);
     method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") @NonNull public android.graphics.Matrix44 concat(@NonNull android.graphics.Matrix44);
-    method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public float get(int, int);
+    method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public float get(@IntRange(from=0, to=3) int, @IntRange(from=0, to=3) int);
     method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void getValues(@NonNull float[]);
     method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public boolean invert();
     method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public boolean isIdentity();
@@ -16370,7 +16370,7 @@
     method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void reset();
     method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") @NonNull public android.graphics.Matrix44 rotate(float, float, float, float);
     method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") @NonNull public android.graphics.Matrix44 scale(float, float, float);
-    method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void set(int, int, float);
+    method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void set(@IntRange(from=0, to=3) int, @IntRange(from=0, to=3) int, float);
     method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void setValues(@NonNull float[]);
     method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") @NonNull public android.graphics.Matrix44 translate(float, float, float);
   }
@@ -41153,19 +41153,19 @@
     method public final android.service.notification.StatusBarNotification[] getSnoozedNotifications();
     method public final void migrateNotificationFilter(int, @Nullable java.util.List<java.lang.String>);
     method public android.os.IBinder onBind(android.content.Intent);
-    method public void onInterruptionFilterChanged(int);
-    method public void onListenerConnected();
-    method public void onListenerDisconnected();
-    method public void onListenerHintsChanged(int);
-    method public void onNotificationChannelGroupModified(String, android.os.UserHandle, android.app.NotificationChannelGroup, int);
-    method public void onNotificationChannelModified(String, android.os.UserHandle, android.app.NotificationChannel, int);
-    method public void onNotificationPosted(android.service.notification.StatusBarNotification);
-    method public void onNotificationPosted(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap);
-    method public void onNotificationRankingUpdate(android.service.notification.NotificationListenerService.RankingMap);
-    method public void onNotificationRemoved(android.service.notification.StatusBarNotification);
-    method public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap);
-    method public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap, int);
-    method public void onSilentStatusBarIconsVisibilityChanged(boolean);
+    method @UiThread public void onInterruptionFilterChanged(int);
+    method @UiThread public void onListenerConnected();
+    method @UiThread public void onListenerDisconnected();
+    method @UiThread public void onListenerHintsChanged(int);
+    method @UiThread public void onNotificationChannelGroupModified(String, android.os.UserHandle, android.app.NotificationChannelGroup, int);
+    method @UiThread public void onNotificationChannelModified(String, android.os.UserHandle, android.app.NotificationChannel, int);
+    method @UiThread public void onNotificationPosted(android.service.notification.StatusBarNotification);
+    method @UiThread public void onNotificationPosted(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap);
+    method @UiThread public void onNotificationRankingUpdate(android.service.notification.NotificationListenerService.RankingMap);
+    method @UiThread public void onNotificationRemoved(android.service.notification.StatusBarNotification);
+    method @UiThread public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap);
+    method @UiThread public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap, int);
+    method @UiThread public void onSilentStatusBarIconsVisibilityChanged(boolean);
     method public final void requestInterruptionFilter(int);
     method public final void requestListenerHints(int);
     method public static void requestRebind(android.content.ComponentName);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index d190c62..b73f199 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -70,7 +70,7 @@
     field public static final String BIND_NETWORK_RECOMMENDATION_SERVICE = "android.permission.BIND_NETWORK_RECOMMENDATION_SERVICE";
     field public static final String BIND_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE";
     field @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public static final String BIND_ON_DEVICE_INTELLIGENCE_SERVICE = "android.permission.BIND_ON_DEVICE_INTELLIGENCE_SERVICE";
-    field @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public static final String BIND_ON_DEVICE_TRUSTED_SERVICE = "android.permission.BIND_ON_DEVICE_TRUSTED_SERVICE";
+    field @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public static final String BIND_ON_DEVICE_SANDBOXED_INFERENCE_SERVICE = "android.permission.BIND_ON_DEVICE_SANDBOXED_INFERENCE_SERVICE";
     field public static final String BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE = "android.permission.BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE";
     field public static final String BIND_PRINT_RECOMMENDATION_SERVICE = "android.permission.BIND_PRINT_RECOMMENDATION_SERVICE";
     field public static final String BIND_REMOTE_LOCKSCREEN_VALIDATION_SERVICE = "android.permission.BIND_REMOTE_LOCKSCREEN_VALIDATION_SERVICE";
@@ -403,7 +403,6 @@
     field @Deprecated public static final String UPDATE_TIME_ZONE_RULES = "android.permission.UPDATE_TIME_ZONE_RULES";
     field public static final String UPGRADE_RUNTIME_PERMISSIONS = "android.permission.UPGRADE_RUNTIME_PERMISSIONS";
     field public static final String USER_ACTIVITY = "android.permission.USER_ACTIVITY";
-    field @FlaggedApi("android.hardware.biometrics.face_background_authentication") public static final String USE_BACKGROUND_FACE_AUTHENTICATION = "android.permission.USE_BACKGROUND_FACE_AUTHENTICATION";
     field public static final String USE_COLORIZED_NOTIFICATIONS = "android.permission.USE_COLORIZED_NOTIFICATIONS";
     field @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public static final String USE_ON_DEVICE_INTELLIGENCE = "android.permission.USE_ON_DEVICE_INTELLIGENCE";
     field public static final String USE_RESERVED_DISK = "android.permission.USE_RESERVED_DISK";
@@ -2223,10 +2222,9 @@
   }
 
   public static final class Feature.Builder {
-    ctor public Feature.Builder(int, int, int, @NonNull android.os.PersistableBundle);
+    ctor public Feature.Builder(int);
     method @NonNull public android.app.ondeviceintelligence.Feature build();
     method @NonNull public android.app.ondeviceintelligence.Feature.Builder setFeatureParams(@NonNull android.os.PersistableBundle);
-    method @NonNull public android.app.ondeviceintelligence.Feature.Builder setId(int);
     method @NonNull public android.app.ondeviceintelligence.Feature.Builder setModelName(@NonNull String);
     method @NonNull public android.app.ondeviceintelligence.Feature.Builder setName(@NonNull String);
     method @NonNull public android.app.ondeviceintelligence.Feature.Builder setType(int);
@@ -2238,7 +2236,7 @@
     ctor public FeatureDetails(@android.app.ondeviceintelligence.FeatureDetails.Status int);
     method public int describeContents();
     method @NonNull public android.os.PersistableBundle getFeatureDetailParams();
-    method @android.app.ondeviceintelligence.FeatureDetails.Status public int getStatus();
+    method @android.app.ondeviceintelligence.FeatureDetails.Status public int getFeatureStatus();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.FeatureDetails> CREATOR;
     field public static final int FEATURE_STATUS_AVAILABLE = 3; // 0x3
@@ -2251,27 +2249,16 @@
   @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE_USE, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD}) public static @interface FeatureDetails.Status {
   }
 
-  @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class FilePart implements android.os.Parcelable {
-    ctor public FilePart(@NonNull String, @NonNull android.os.PersistableBundle, @NonNull String) throws java.io.FileNotFoundException;
-    ctor public FilePart(@NonNull String, @NonNull android.os.PersistableBundle, @NonNull java.io.FileInputStream) throws java.io.IOException;
-    method public int describeContents();
-    method @NonNull public java.io.FileInputStream getFileInputStream();
-    method @NonNull public String getFilePartKey();
-    method @NonNull public android.os.PersistableBundle getFilePartParams();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.FilePart> CREATOR;
-  }
-
   @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public class OnDeviceIntelligenceManager {
     method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getFeature(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
     method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getFeatureDetails(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
+    method @Nullable @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public String getRemoteServicePackageName();
     method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getVersion(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.LongConsumer);
     method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void listFeatures(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
-    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequest(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>);
-    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequestStreaming(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.StreamingResponseReceiver<android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>);
+    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequest(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.ProcessingOutcomeReceiver);
+    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequestStreaming(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.StreamedProcessingOutcomeReceiver);
     method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestFeatureDownload(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.DownloadCallback);
-    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestTokenCount(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Long,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
-    field public static final String API_VERSION_BUNDLE_KEY = "ApiVersionBundleKey";
+    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestTokenInfo(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.TokenInfo,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
     field public static final int REQUEST_TYPE_EMBEDDINGS = 2; // 0x2
     field public static final int REQUEST_TYPE_INFERENCE = 0; // 0x0
     field public static final int REQUEST_TYPE_PREPARE = 1; // 0x1
@@ -2305,6 +2292,10 @@
     field public static final int PROCESSING_ERROR_UNKNOWN = 1; // 0x1
   }
 
+  @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface ProcessingOutcomeReceiver extends android.os.OutcomeReceiver<android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> {
+    method public default void onDataAugmentRequest(@NonNull android.app.ondeviceintelligence.Content, @NonNull java.util.function.Consumer<android.app.ondeviceintelligence.Content>);
+  }
+
   @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class ProcessingSignal {
     ctor public ProcessingSignal();
     method public void sendSignal(@NonNull android.os.PersistableBundle);
@@ -2315,8 +2306,18 @@
     method public void onSignalReceived(@NonNull android.os.PersistableBundle);
   }
 
-  @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface StreamingResponseReceiver<R, T, E extends java.lang.Throwable> extends android.os.OutcomeReceiver<R,E> {
-    method public void onNewContent(@NonNull T);
+  @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface StreamedProcessingOutcomeReceiver extends android.app.ondeviceintelligence.ProcessingOutcomeReceiver {
+    method public void onNewContent(@NonNull android.app.ondeviceintelligence.Content);
+  }
+
+  @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class TokenInfo implements android.os.Parcelable {
+    ctor public TokenInfo(long, @NonNull android.os.PersistableBundle);
+    ctor public TokenInfo(long);
+    method public int describeContents();
+    method public long getCount();
+    method @NonNull public android.os.PersistableBundle getInfoParams();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.TokenInfo> CREATOR;
   }
 
 }
@@ -3759,7 +3760,6 @@
     field @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") public static final String ECM_ENHANCED_CONFIRMATION_SERVICE = "ecm_enhanced_confirmation";
     field public static final String ETHERNET_SERVICE = "ethernet";
     field public static final String EUICC_CARD_SERVICE = "euicc_card";
-    field @FlaggedApi("android.hardware.biometrics.face_background_authentication") public static final String FACE_SERVICE = "face";
     field public static final String FONT_SERVICE = "font";
     field public static final String HDMI_CONTROL_SERVICE = "hdmi_control";
     field public static final String MEDIA_TRANSCODING_SERVICE = "media_transcoding";
@@ -5137,15 +5137,6 @@
 
 }
 
-package android.hardware.face {
-
-  @FlaggedApi("android.hardware.biometrics.face_background_authentication") public class FaceManager {
-    method @FlaggedApi("android.hardware.biometrics.face_background_authentication") @RequiresPermission(android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION) public void authenticateInBackground(@Nullable java.util.concurrent.Executor, @Nullable android.hardware.biometrics.BiometricPrompt.CryptoObject, @Nullable android.os.CancellationSignal, @NonNull android.hardware.biometrics.BiometricPrompt.AuthenticationCallback);
-    method @FlaggedApi("android.hardware.biometrics.face_background_authentication") @RequiresPermission(anyOf={"android.permission.USE_BIOMETRIC_INTERNAL", android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION}) public boolean hasEnrolledTemplates();
-  }
-
-}
-
 package android.hardware.hdmi {
 
   public abstract class HdmiClient {
@@ -11537,7 +11528,7 @@
     method public int checkPermissionForPreflight(@NonNull String, @NonNull android.content.AttributionSource);
     method @RequiresPermission(value=android.Manifest.permission.UPDATE_APP_OPS_STATS, conditional=true) public int checkPermissionForStartDataDelivery(@NonNull String, @NonNull android.content.AttributionSource, @Nullable String);
     method public void finishDataDelivery(@NonNull String, @NonNull android.content.AttributionSource);
-    method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") @NonNull @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS) public java.util.Map<java.lang.String,android.permission.PermissionManager.PermissionState> getAllPermissionStates(@NonNull String, @NonNull String);
+    method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") @NonNull @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, android.Manifest.permission.GET_RUNTIME_PERMISSIONS}) public java.util.Map<java.lang.String,android.permission.PermissionManager.PermissionState> getAllPermissionStates(@NonNull String, @NonNull String);
     method @NonNull @RequiresPermission(android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY) public java.util.Set<java.lang.String> getAutoRevokeExemptionGrantedPackages();
     method @NonNull @RequiresPermission(android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY) public java.util.Set<java.lang.String> getAutoRevokeExemptionRequestedPackages();
     method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, android.Manifest.permission.GET_RUNTIME_PERMISSIONS}) public int getPermissionFlags(@NonNull String, @NonNull String, @NonNull String);
@@ -12891,7 +12882,7 @@
   }
 
   public abstract class NotificationListenerService extends android.app.Service {
-    method public void onNotificationRemoved(@NonNull android.service.notification.StatusBarNotification, @NonNull android.service.notification.NotificationListenerService.RankingMap, @NonNull android.service.notification.NotificationStats, int);
+    method @UiThread public void onNotificationRemoved(@NonNull android.service.notification.StatusBarNotification, @NonNull android.service.notification.NotificationListenerService.RankingMap, @NonNull android.service.notification.NotificationStats, int);
   }
 
   public static class NotificationListenerService.Ranking {
@@ -12963,12 +12954,14 @@
   @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public abstract class OnDeviceIntelligenceService extends android.app.Service {
     ctor public OnDeviceIntelligenceService();
     method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
-    method public abstract void onDownloadFeature(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.os.CancellationSignal, @NonNull android.app.ondeviceintelligence.DownloadCallback);
-    method public abstract void onGetFeature(int, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
-    method public abstract void onGetFeatureDetails(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
+    method public abstract void onDownloadFeature(int, @NonNull android.app.ondeviceintelligence.Feature, @Nullable android.os.CancellationSignal, @NonNull android.app.ondeviceintelligence.DownloadCallback);
+    method public abstract void onGetFeature(int, int, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
+    method public abstract void onGetFeatureDetails(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
     method public abstract void onGetReadOnlyFeatureFileDescriptorMap(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,android.os.ParcelFileDescriptor>>);
     method public abstract void onGetVersion(@NonNull java.util.function.LongConsumer);
-    method public abstract void onListFeatures(@NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
+    method public abstract void onInferenceServiceConnected();
+    method public abstract void onInferenceServiceDisconnected();
+    method public abstract void onListFeatures(int, @NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
     method public final void updateProcessingState(@NonNull android.os.Bundle, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.os.PersistableBundle,android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceUpdateProcessingException>);
     field public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceIntelligenceService";
   }
@@ -12985,17 +12978,18 @@
     field public static final int PROCESSING_UPDATE_STATUS_CONNECTION_FAILED = 1; // 0x1
   }
 
-  @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public abstract class OnDeviceTrustedInferenceService extends android.app.Service {
-    ctor public OnDeviceTrustedInferenceService();
+  @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public abstract class OnDeviceSandboxedInferenceService extends android.app.Service {
+    ctor public OnDeviceSandboxedInferenceService();
     method public final void fetchFeatureFileInputStreamMap(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,java.io.FileInputStream>>);
+    method @NonNull public java.util.concurrent.Executor getCallbackExecutor();
     method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
-    method @NonNull public abstract void onCountTokens(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, @Nullable android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<java.lang.Long,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>);
-    method @NonNull public abstract void onProcessRequest(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>);
-    method @NonNull public abstract void onProcessRequestStreaming(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.StreamingResponseReceiver<android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>);
+    method @NonNull public abstract void onProcessRequest(int, @NonNull android.app.ondeviceintelligence.Feature, @Nullable android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.ProcessingOutcomeReceiver);
+    method @NonNull public abstract void onProcessRequestStreaming(int, @NonNull android.app.ondeviceintelligence.Feature, @Nullable android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.StreamedProcessingOutcomeReceiver);
+    method @NonNull public abstract void onTokenInfoRequest(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, @Nullable android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.TokenInfo,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>);
     method public abstract void onUpdateProcessingState(@NonNull android.os.Bundle, @NonNull android.os.OutcomeReceiver<android.os.PersistableBundle,android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceUpdateProcessingException>);
     method public final java.io.FileInputStream openFileInput(@NonNull String) throws java.io.FileNotFoundException;
     method public final void openFileInputAsync(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.io.FileInputStream>) throws java.io.FileNotFoundException;
-    field public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceTrustedInferenceService";
+    field public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService";
   }
 
 }
diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt
index 1923641..1e72a06 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -511,9 +511,9 @@
 
 InvalidNullabilityOverride: android.service.ondeviceintelligence.OnDeviceIntelligenceService#onBind(android.content.Intent) parameter #0:
     Invalid nullability on parameter `intent` in method `onBind`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
-InvalidNullabilityOverride: android.service.ondeviceintelligence.OnDeviceTrustedInferenceService#onBind(android.content.Intent) parameter #0:
+InvalidNullabilityOverride: android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService#onBind(android.content.Intent) parameter #0:
     Invalid nullability on parameter `intent` in method `onBind`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
-InvalidNullabilityOverride: android.service.ondeviceintelligence.OnDeviceTrustedInferenceService#openFileInput(String) parameter #0:
+InvalidNullabilityOverride: android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService#openFileInput(String) parameter #0:
     Invalid nullability on parameter `filename` in method `openFileInput`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
 InvalidNullabilityOverride: android.service.textclassifier.TextClassifierService#onUnbind(android.content.Intent) parameter #0:
     Invalid nullability on parameter `intent` in method `onUnbind`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
@@ -571,7 +571,7 @@
     Missing nullability on parameter `args` in method `dump`
 MissingNullability: android.service.notification.NotificationAssistantService#attachBaseContext(android.content.Context) parameter #0:
     Missing nullability on parameter `base` in method `attachBaseContext`
-MissingNullability: android.service.ondeviceintelligence.OnDeviceTrustedInferenceService#openFileInput(String):
+MissingNullability: android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService#openFileInput(String):
     Missing nullability on method `openFileInput` return
 MissingNullability: android.telephony.NetworkService#onUnbind(android.content.Intent) parameter #0:
     Missing nullability on parameter `intent` in method `onUnbind`
diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java
index f6ec370..5e2397d 100644
--- a/core/java/android/app/AutomaticZenRule.java
+++ b/core/java/android/app/AutomaticZenRule.java
@@ -173,8 +173,8 @@
      *                           interrupt the user (e.g. via sound &amp; vibration) while this rule
      *                           is active.
      * @param enabled Whether the rule is enabled.
-     * @deprecated use {@link #AutomaticZenRule(String, ComponentName, ComponentName, Uri,
-     * ZenPolicy, int, boolean)}.
+     *
+     * @deprecated Use {@link AutomaticZenRule.Builder} to construct an {@link AutomaticZenRule}.
      */
     @Deprecated
     public AutomaticZenRule(String name, ComponentName owner, Uri conditionId,
@@ -206,8 +206,10 @@
      *               while this rule is active. This overrides the global policy while this rule is
      *               action ({@link Condition#STATE_TRUE}).
      * @param enabled Whether the rule is enabled.
+     *
+     * @deprecated Use {@link AutomaticZenRule.Builder} to construct an {@link AutomaticZenRule}.
      */
-    // TODO (b/309088420): deprecate this constructor in favor of the builder
+    @Deprecated
     public AutomaticZenRule(@NonNull String name, @Nullable ComponentName owner,
             @Nullable ComponentName configurationActivity, @NonNull Uri conditionId,
             @Nullable ZenPolicy policy, int interruptionFilter, boolean enabled) {
@@ -368,6 +370,9 @@
 
     /**
      * Sets the zen policy.
+     *
+     * <p>When updating an existing rule via {@link NotificationManager#updateAutomaticZenRule},
+     * a {@code null} value here means the previous policy is retained.
      */
     public void setZenPolicy(@Nullable ZenPolicy zenPolicy) {
         this.mZenPolicy = (zenPolicy == null ? null : zenPolicy.copy());
@@ -390,7 +395,12 @@
      * Sets the configuration activity - an activity that handles
      * {@link NotificationManager#ACTION_AUTOMATIC_ZEN_RULE} that shows the user more information
      * about this rule and/or allows them to configure it. This is required to be non-null for rules
-     * that are not backed by {@link android.service.notification.ConditionProviderService}.
+     * that are not backed by a {@link android.service.notification.ConditionProviderService}.
+     *
+     * <p>This is exclusive with the {@code owner} supplied in the constructor; rules where a
+     * configuration activity is set will not use the
+     * {@link android.service.notification.ConditionProviderService} supplied there to determine
+     * whether the rule should be active.
      */
     public void setConfigurationActivity(@Nullable ComponentName componentName) {
         this.configurationActivity = getTrimmedComponentName(componentName);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 1129f9d..79cb09d 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -6068,11 +6068,19 @@
         }
 
         /**
-         * Construct a RemoteViews for the final 1U notification layout. In order:
-         *   1. Custom contentView from the caller
-         *   2. Style's proposed content view
-         *   3. Standard template view
+         * Construct a RemoteViews representing the standard notification layout.
+         *
+         * @deprecated For performance and system health reasons, this API is no longer required to
+         *  be used directly by the System UI when rendering Notifications to the user. While the UI
+         *  returned by this method will still represent the content of the Notification being
+         *  built, it may differ from the visual style of the system.
+         *
+         *  NOTE: this API has always had severe limitations; for example it does not support any
+         *  interactivity, it ignores the app theme, it hard-codes the colors from the system theme
+         *  at the time it is called, and it does Bitmap decoding on the main thread which can cause
+         *  UI jank.
          */
+        @Deprecated
         public RemoteViews createContentView() {
             return createContentView(false /* increasedheight */ );
         }
@@ -6181,8 +6189,19 @@
         }
 
         /**
-         * Construct a RemoteViews for the final big notification layout.
+         * Construct a RemoteViews representing the expanded notification layout.
+         *
+         * @deprecated For performance and system health reasons, this API is no longer required to
+         *  be used directly by the System UI when rendering Notifications to the user. While the UI
+         *  returned by this method will still represent the content of the Notification being
+         *  built, it may differ from the visual style of the system.
+         *
+         *  NOTE: this API has always had severe limitations; for example it does not support any
+         *  interactivity, it ignores the app theme, it hard-codes the colors from the system theme
+         *  at the time it is called, and it does Bitmap decoding on the main thread which can cause
+         *  UI jank.
          */
+        @Deprecated
         public RemoteViews createBigContentView() {
             RemoteViews result = null;
             if (useExistingRemoteView(mN.bigContentView)) {
@@ -6315,8 +6334,19 @@
         }
 
         /**
-         * Construct a RemoteViews for the final heads-up notification layout.
+         * Construct a RemoteViews representing the heads up notification layout.
+         *
+         * @deprecated For performance and system health reasons, this API is no longer required to
+         *  be used directly by the System UI when rendering Notifications to the user. While the UI
+         *  returned by this method will still represent the content of the Notification being
+         *  built, it may differ from the visual style of the system.
+         *
+         *  NOTE: this API has always had severe limitations; for example it does not support any
+         *  interactivity, it ignores the app theme, it hard-codes the colors from the system theme
+         *  at the time it is called, and it does Bitmap decoding on the main thread which can cause
+         *  UI jank.
          */
+        @Deprecated
         public RemoteViews createHeadsUpContentView() {
             return createHeadsUpContentView(false /* useIncreasedHeight */);
         }
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 283e21a..b82a1e3 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -270,13 +270,16 @@
      * Integer extra for {@link #ACTION_AUTOMATIC_ZEN_RULE_STATUS_CHANGED} containing the state of
      * the {@link AutomaticZenRule}.
      *
-     * <p>
-     *     The value will be one of {@link #AUTOMATIC_RULE_STATUS_ENABLED},
-     *     {@link #AUTOMATIC_RULE_STATUS_DISABLED}, {@link #AUTOMATIC_RULE_STATUS_REMOVED},
-     *     {@link #AUTOMATIC_RULE_STATUS_UNKNOWN}.
-     * </p>
+     * <p>The value will be one of {@link #AUTOMATIC_RULE_STATUS_ENABLED},
+     * {@link #AUTOMATIC_RULE_STATUS_DISABLED}, {@link #AUTOMATIC_RULE_STATUS_REMOVED},
+     * {@link #AUTOMATIC_RULE_STATUS_ACTIVATED}, {@link #AUTOMATIC_RULE_STATUS_DEACTIVATED}, or
+     * {@link #AUTOMATIC_RULE_STATUS_UNKNOWN}.
+     *
+     * <p>Note that the {@link #AUTOMATIC_RULE_STATUS_ACTIVATED} and
+     * {@link #AUTOMATIC_RULE_STATUS_DEACTIVATED} statuses are only sent to packages targeting
+     * {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above; apps targeting a lower SDK version
+     * will be sent {@link #AUTOMATIC_RULE_STATUS_UNKNOWN} in their place instead.
      */
-    // TODO (b/309101513): Add new status types to javadoc
     public static final String EXTRA_AUTOMATIC_ZEN_RULE_STATUS =
             "android.app.extra.AUTOMATIC_ZEN_RULE_STATUS";
 
@@ -370,11 +373,15 @@
             = "android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED";
 
     /**
-     * Intent that is broadcast when the state of getNotificationPolicy() changes.
+     * Intent that is broadcast when the state of {@link #getNotificationPolicy()} changes.
      *
      * <p>This broadcast is only sent to registered receivers and (starting from
      * {@link Build.VERSION_CODES#Q}) receivers in packages that have been granted Do Not
      * Disturb access (see {@link #isNotificationPolicyAccessGranted()}).
+     *
+     * <p>Starting with {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, most calls to
+     * {@link #setNotificationPolicy(Policy)} will update the app's implicit rule policy instead of
+     * the global policy, so this broadcast will be sent much less frequently.
      */
     @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_NOTIFICATION_POLICY_CHANGED
@@ -1378,12 +1385,16 @@
     /**
      * Updates the given zen rule.
      *
-     * <p>
-     * Throws a SecurityException if policy access is not granted to this package.
+     * <p>Before {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, updating a rule that is not backed
+     * up by a {@link android.service.notification.ConditionProviderService} will deactivate it if
+     * it was previously active. Starting with {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, this
+     * will only happen if the rule's definition is actually changing.
+     *
+     * <p>Throws a SecurityException if policy access is not granted to this package.
      * See {@link #isNotificationPolicyAccessGranted}.
      *
-     * <p>
-     * Callers can only update rules that they own. See {@link AutomaticZenRule#getOwner}.
+     * <p>Callers can only update rules that they own. See {@link AutomaticZenRule#getOwner}.
+     *
      * @param id The id of the rule to update
      * @param automaticZenRule the rule to update.
      * @return Whether the rule was successfully updated.
@@ -1744,9 +1755,11 @@
     /**
      * Gets the current user-specified default notification policy.
      *
-     * <p>
+     * <p>For apps targeting {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above (with some
+     * exceptions, such as companion device managers) this method will return the policy associated
+     * to their implicit {@link AutomaticZenRule} instead, if it exists. See
+     * {@link #setNotificationPolicy(Policy)}.
      */
-    // TODO(b/309457271): Update documentation with VANILLA_ICE_CREAM behavior.
     public Policy getNotificationPolicy() {
         INotificationManager service = getService();
         try {
@@ -1757,15 +1770,20 @@
     }
 
     /**
-     * Sets the current notification policy.
+     * Sets the current notification policy (which applies when {@link #setInterruptionFilter} is
+     * called with the {@link #INTERRUPTION_FILTER_PRIORITY} value).
      *
-     * <p>
-     * Only available if policy access is granted to this package.
-     * See {@link #isNotificationPolicyAccessGranted}.
+     * <p>Apps targeting {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above (with some
+     * exceptions, such as companion device managers) cannot modify the global notification policy.
+     * Calling this method will instead create or update an {@link AutomaticZenRule} associated to
+     * the app, using a {@link ZenPolicy} corresponding to the {@link Policy} supplied here, and
+     * which will be activated/deactivated by calls to {@link #setInterruptionFilter(int)}.
+     *
+     * <p>Only available if policy access is granted to this package. See
+     * {@link #isNotificationPolicyAccessGranted}.
      *
      * @param policy The new desired policy.
      */
-    // TODO(b/309457271): Update documentation with VANILLA_ICE_CREAM behavior.
     public void setNotificationPolicy(@NonNull Policy policy) {
         setNotificationPolicy(policy, /* fromUser= */ false);
     }
@@ -2786,11 +2804,17 @@
      * The interruption filter defines which notifications are allowed to
      * interrupt the user (e.g. via sound &amp; vibration) and is applied
      * globally.
-     * <p>
-     * Only available if policy access is granted to this package. See
+     *
+     * <p>Apps targeting {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above (with some
+     * exceptions, such as companion device managers) cannot modify the global interruption filter.
+     * Calling this method will instead activate or deactivate an {@link AutomaticZenRule}
+     * associated to the app, using a {@link ZenPolicy} that corresponds to the {@link Policy}
+     * supplied to {@link #setNotificationPolicy(Policy)} (or the global policy when one wasn't
+     * provided).
+     *
+     * <p> Only available if policy access is granted to this package. See
      * {@link #isNotificationPolicyAccessGranted}.
      */
-    // TODO(b/309457271): Update documentation with VANILLA_ICE_CREAM behavior.
     public final void setInterruptionFilter(@InterruptionFilter int interruptionFilter) {
         setInterruptionFilter(interruptionFilter, /* fromUser= */ false);
     }
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index fb1b17b..89199ca 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -1,5 +1,7 @@
 package android.app.assist;
 
+import static android.credentials.Constants.FAILURE_CREDMAN_SELECTOR;
+import static android.credentials.Constants.SUCCESS_CREDMAN_SELECTOR;
 import static android.service.autofill.Flags.FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION;
 
 import android.annotation.FlaggedApi;
@@ -20,14 +22,17 @@
 import android.os.BadParcelableException;
 import android.os.Binder;
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.LocaleList;
+import android.os.Looper;
 import android.os.OutcomeReceiver;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.PooledStringReader;
 import android.os.PooledStringWriter;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.os.SystemClock;
 import android.service.autofill.FillRequest;
 import android.service.credentials.CredentialProviderService;
@@ -37,6 +42,7 @@
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Pair;
+import android.util.Slog;
 import android.view.View;
 import android.view.View.AutofillImportance;
 import android.view.ViewRootImpl;
@@ -652,6 +658,9 @@
         @Nullable OutcomeReceiver<GetCredentialResponse, GetCredentialException>
                 mGetCredentialCallback;
 
+        @Nullable ResultReceiver mGetCredentialResultReceiver;
+
+
         AutofillValue mAutofillValue;
         CharSequence[] mAutofillOptions;
         boolean mSanitized;
@@ -916,6 +925,7 @@
                 mExtras = in.readBundle();
             }
             mGetCredentialRequest = in.readTypedObject(GetCredentialRequest.CREATOR);
+            mGetCredentialResultReceiver = in.readTypedObject(ResultReceiver.CREATOR);
         }
 
         /**
@@ -1153,6 +1163,7 @@
                 out.writeBundle(mExtras);
             }
             out.writeTypedObject(mGetCredentialRequest, flags);
+            out.writeTypedObject(mGetCredentialResultReceiver, flags);
             return flags;
         }
 
@@ -1295,9 +1306,8 @@
          */
         @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
         @Nullable
-        public OutcomeReceiver<GetCredentialResponse,
-                GetCredentialException> getCredentialManagerCallback() {
-            return mGetCredentialCallback;
+        public ResultReceiver getCredentialManagerCallback() {
+            return mGetCredentialResultReceiver;
         }
 
         /**
@@ -1894,6 +1904,7 @@
         final AssistStructure mAssist;
         final ViewNode mNode;
         final boolean mAsync;
+        private Handler mHandler;
 
         /**
          * Used to instantiate a builder for a stand-alone {@link ViewNode} which is not associated
@@ -2271,6 +2282,56 @@
                 option.getCandidateQueryData()
                         .putParcelableArrayList(CredentialProviderService.EXTRA_AUTOFILL_ID, ids);
             }
+            setUpResultReceiver(callback);
+        }
+
+        private void setUpResultReceiver(
+                OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {
+
+            if (mHandler == null) {
+                mHandler = new Handler(Looper.getMainLooper(), null, true);
+            }
+            final ResultReceiver resultReceiver = new ResultReceiver(mHandler) {
+                @Override
+                protected void onReceiveResult(int resultCode, Bundle resultData) {
+                    if (resultCode == SUCCESS_CREDMAN_SELECTOR) {
+                        Slog.d(TAG, "onReceiveResult from Credential Manager");
+                        GetCredentialResponse getCredentialResponse =
+                                resultData.getParcelable(
+                                        CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE,
+                                        GetCredentialResponse.class);
+
+                        callback.onResult(getCredentialResponse);
+                    } else if (resultCode == FAILURE_CREDMAN_SELECTOR) {
+                        String[] exception =  resultData.getStringArray(
+                                CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION);
+                        if (exception != null && exception.length >= 2) {
+                            Slog.w(TAG, "Credman bottom sheet from pinned "
+                                    + "entry failed with: + " + exception[0] + " , "
+                                    + exception[1]);
+                            callback.onError(new GetCredentialException(
+                                    exception[0], exception[1]));
+                        }
+                    } else {
+                        Slog.d(TAG, "Unknown resultCode from credential "
+                                + "manager bottom sheet: " + resultCode);
+                    }
+                }
+            };
+            ResultReceiver ipcFriendlyResultReceiver =
+                    toIpcFriendlyResultReceiver(resultReceiver);
+            mNode.mGetCredentialResultReceiver = ipcFriendlyResultReceiver;
+        }
+
+        private ResultReceiver toIpcFriendlyResultReceiver(ResultReceiver resultReceiver) {
+            final Parcel parcel = Parcel.obtain();
+            resultReceiver.writeToParcel(parcel, 0);
+            parcel.setDataPosition(0);
+
+            final ResultReceiver ipcFriendly = ResultReceiver.CREATOR.createFromParcel(parcel);
+            parcel.recycle();
+
+            return ipcFriendly;
         }
 
         @Override
diff --git a/core/java/android/app/ondeviceintelligence/Feature.java b/core/java/android/app/ondeviceintelligence/Feature.java
index 5107354..4a38c92 100644
--- a/core/java/android/app/ondeviceintelligence/Feature.java
+++ b/core/java/android/app/ondeviceintelligence/Feature.java
@@ -199,24 +199,13 @@
 
         private long mBuilderFieldsSet = 0L;
 
-        public Builder(
-                int id,
-                int type,
-                int variant,
-                @NonNull PersistableBundle featureParams) {
+        /**
+         * Provides a builder instance to create a feature for given id.
+         * @param id the unique identifier for the feature.
+         */
+        public Builder(int id) {
             mId = id;
-            mType = type;
-            mVariant = variant;
-            mFeatureParams = featureParams;
-            com.android.internal.util.AnnotationValidations.validate(
-                    NonNull.class, null, mFeatureParams);
-        }
-
-        public @NonNull Builder setId(int value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x1;
-            mId = value;
-            return this;
+            mFeatureParams = new PersistableBundle();
         }
 
         public @NonNull Builder setName(@NonNull String value) {
diff --git a/core/java/android/app/ondeviceintelligence/FeatureDetails.java b/core/java/android/app/ondeviceintelligence/FeatureDetails.java
index 92f3513..f3cbd26 100644
--- a/core/java/android/app/ondeviceintelligence/FeatureDetails.java
+++ b/core/java/android/app/ondeviceintelligence/FeatureDetails.java
@@ -41,7 +41,7 @@
 @FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
 public final class FeatureDetails implements Parcelable {
     @Status
-    private final int mStatus;
+    private final int mFeatureStatus;
     @NonNull
     private final PersistableBundle mFeatureDetailParams;
 
@@ -73,21 +73,21 @@
     }
 
     public FeatureDetails(
-            @Status int status,
+            @Status int featureStatus,
             @NonNull PersistableBundle featureDetailParams) {
-        this.mStatus = status;
+        this.mFeatureStatus = featureStatus;
         com.android.internal.util.AnnotationValidations.validate(
-                Status.class, null, mStatus);
+                Status.class, null, mFeatureStatus);
         this.mFeatureDetailParams = featureDetailParams;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mFeatureDetailParams);
     }
 
     public FeatureDetails(
-            @Status int status) {
-        this.mStatus = status;
+            @Status int featureStatus) {
+        this.mFeatureStatus = featureStatus;
         com.android.internal.util.AnnotationValidations.validate(
-                Status.class, null, mStatus);
+                Status.class, null, mFeatureStatus);
         this.mFeatureDetailParams = new PersistableBundle();
     }
 
@@ -95,8 +95,8 @@
     /**
      * Returns an integer value associated with the feature status.
      */
-    public @Status int getStatus() {
-        return mStatus;
+    public @Status int getFeatureStatus() {
+        return mFeatureStatus;
     }
 
 
@@ -111,7 +111,7 @@
     public String toString() {
         return MessageFormat.format("FeatureDetails '{' status = {0}, "
                         + "persistableBundle = {1} '}'",
-                mStatus,
+                mFeatureStatus,
                 mFeatureDetailParams);
     }
 
@@ -121,21 +121,21 @@
         if (o == null || getClass() != o.getClass()) return false;
         @SuppressWarnings("unchecked")
         FeatureDetails that = (FeatureDetails) o;
-        return mStatus == that.mStatus
+        return mFeatureStatus == that.mFeatureStatus
                 && java.util.Objects.equals(mFeatureDetailParams, that.mFeatureDetailParams);
     }
 
     @Override
     public int hashCode() {
         int _hash = 1;
-        _hash = 31 * _hash + mStatus;
+        _hash = 31 * _hash + mFeatureStatus;
         _hash = 31 * _hash + java.util.Objects.hashCode(mFeatureDetailParams);
         return _hash;
     }
 
     @Override
     public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
-        dest.writeInt(mStatus);
+        dest.writeInt(mFeatureStatus);
         dest.writeTypedObject(mFeatureDetailParams, flags);
     }
 
@@ -151,9 +151,9 @@
         PersistableBundle persistableBundle = (PersistableBundle) in.readTypedObject(
                 PersistableBundle.CREATOR);
 
-        this.mStatus = status;
+        this.mFeatureStatus = status;
         com.android.internal.util.AnnotationValidations.validate(
-                Status.class, null, mStatus);
+                Status.class, null, mFeatureStatus);
         this.mFeatureDetailParams = persistableBundle;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mFeatureDetailParams);
diff --git a/core/java/android/app/ondeviceintelligence/FilePart.java b/core/java/android/app/ondeviceintelligence/FilePart.java
deleted file mode 100644
index e9fb5f2..0000000
--- a/core/java/android/app/ondeviceintelligence/FilePart.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (C) 2024 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.app.ondeviceintelligence;
-
-import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
-
-import android.annotation.FlaggedApi;
-import android.annotation.SystemApi;
-import android.os.Parcel;
-import android.os.ParcelFileDescriptor;
-import android.os.Parcelable;
-import android.os.PersistableBundle;
-
-import android.annotation.NonNull;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.util.Objects;
-
-/**
- * Represents file data with an associated file descriptor sent to and received from remote
- * processing. The interface ensures that the underlying file-descriptor is always opened in
- * read-only mode.
- *
- * @hide
- */
-@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
-@SystemApi
-public final class FilePart implements Parcelable {
-    private final String mPartKey;
-    private final PersistableBundle mPartParams;
-    private final ParcelFileDescriptor mParcelFileDescriptor;
-
-    private FilePart(@NonNull String partKey, @NonNull PersistableBundle partParams,
-            @NonNull ParcelFileDescriptor parcelFileDescriptor) {
-        Objects.requireNonNull(partKey);
-        Objects.requireNonNull(partParams);
-        this.mPartKey = partKey;
-        this.mPartParams = partParams;
-        this.mParcelFileDescriptor = Objects.requireNonNull(parcelFileDescriptor);
-    }
-
-    /**
-     * Create a file part using a filePath and any additional params.
-     */
-    public FilePart(@NonNull String partKey, @NonNull PersistableBundle partParams,
-            @NonNull String filePath)
-            throws FileNotFoundException {
-        this(partKey, partParams, Objects.requireNonNull(ParcelFileDescriptor.open(
-                new File(Objects.requireNonNull(filePath)), ParcelFileDescriptor.MODE_READ_ONLY)));
-    }
-
-    /**
-     * Create a file part using a file input stream and any additional params.
-     * It is the caller's responsibility to close the stream. It is safe to do so as soon as this
-     * call returns.
-     */
-    public FilePart(@NonNull String partKey, @NonNull PersistableBundle partParams,
-            @NonNull FileInputStream fileInputStream)
-            throws IOException {
-        this(partKey, partParams, ParcelFileDescriptor.dup(fileInputStream.getFD()));
-    }
-
-    /**
-     * Returns a FileInputStream for the associated File.
-     * Caller must close the associated stream when done reading from it.
-     *
-     * @return the FileInputStream associated with the FilePart.
-     */
-    @NonNull
-    public FileInputStream getFileInputStream() {
-        return new FileInputStream(mParcelFileDescriptor.getFileDescriptor());
-    }
-
-    /**
-     * Returns the unique key associated with the part. Each Part key added to a content object
-     * should be ensured to be unique.
-     */
-    @NonNull
-    public String getFilePartKey() {
-        return mPartKey;
-    }
-
-    /**
-     * Returns the params associated with Part.
-     */
-    @NonNull
-    public PersistableBundle getFilePartParams() {
-        return mPartParams;
-    }
-
-
-    @Override
-    public int describeContents() {
-        return CONTENTS_FILE_DESCRIPTOR;
-    }
-
-    @Override
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeString8(getFilePartKey());
-        dest.writePersistableBundle(getFilePartParams());
-        mParcelFileDescriptor.writeToParcel(dest, flags
-                | Parcelable.PARCELABLE_WRITE_RETURN_VALUE); // This flag ensures that the sender's
-        // copy of the Pfd is closed as soon as the Binder call succeeds.
-    }
-
-    @NonNull
-    public static final Creator<FilePart> CREATOR = new Creator<>() {
-        @Override
-        public FilePart createFromParcel(Parcel in) {
-            return new FilePart(in.readString(), in.readTypedObject(PersistableBundle.CREATOR),
-                    in.readParcelable(
-                            getClass().getClassLoader(), ParcelFileDescriptor.class));
-        }
-
-        @Override
-        public FilePart[] newArray(int size) {
-            return new FilePart[size];
-        }
-    };
-}
diff --git a/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl b/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
index b925f48..360a809 100644
--- a/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
+++ b/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
@@ -31,7 +31,7 @@
  import android.app.ondeviceintelligence.IResponseCallback;
  import android.app.ondeviceintelligence.IStreamingResponseCallback;
  import android.app.ondeviceintelligence.IProcessingSignal;
- import android.app.ondeviceintelligence.ITokenCountCallback;
+ import android.app.ondeviceintelligence.ITokenInfoCallback;
 
 
  /**
@@ -56,8 +56,8 @@
       void requestFeatureDownload(in Feature feature, ICancellationSignal signal, in IDownloadCallback callback) = 5;
 
       @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
-      void requestTokenCount(in Feature feature, in Content request, in  ICancellationSignal signal,
-                                                        in ITokenCountCallback tokenCountcallback) = 6;
+      void requestTokenInfo(in Feature feature, in Content request, in  ICancellationSignal signal,
+                                                        in ITokenInfoCallback tokenInfocallback) = 6;
 
       @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
       void processRequest(in Feature feature, in Content request, int requestType, in  ICancellationSignal cancellationSignal, in IProcessingSignal signal,
diff --git a/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl b/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl
index 9848e1d..0adf305 100644
--- a/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl
+++ b/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl
@@ -3,6 +3,7 @@
 import android.app.ondeviceintelligence.Content;
 import android.app.ondeviceintelligence.IProcessingSignal;
 import android.os.PersistableBundle;
+import android.os.RemoteCallback;
 
 /**
   * Interface for a IResponseCallback for receiving response from on-device intelligence service.
@@ -12,4 +13,5 @@
 interface IResponseCallback {
     void onSuccess(in Content result) = 1;
     void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 2;
+    void onDataAugmentRequest(in Content content, in RemoteCallback contentCallback) = 3;
 }
diff --git a/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl b/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl
index a680574..132e53e 100644
--- a/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl
+++ b/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl
@@ -4,6 +4,7 @@
 import android.app.ondeviceintelligence.IResponseCallback;
 import android.app.ondeviceintelligence.IProcessingSignal;
 import android.os.PersistableBundle;
+import android.os.RemoteCallback;
 
 
 /**
@@ -15,4 +16,5 @@
     void onNewContent(in Content result) = 1;
     void onSuccess(in Content result) = 2;
     void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 3;
+    void onDataAugmentRequest(in Content content, in RemoteCallback contentCallback) = 4;
 }
diff --git a/core/java/android/app/ondeviceintelligence/ITokenCountCallback.aidl b/core/java/android/app/ondeviceintelligence/ITokenCountCallback.aidl
deleted file mode 100644
index b724e03..0000000
--- a/core/java/android/app/ondeviceintelligence/ITokenCountCallback.aidl
+++ /dev/null
@@ -1,13 +0,0 @@
-package android.app.ondeviceintelligence;
-
-import android.os.PersistableBundle;
-
-/**
-  * Interface for receiving the token count of a request for a given features.
-  *
-  * @hide
-  */
-interface ITokenCountCallback {
-    void onSuccess(long tokenCount) = 1;
-    void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 2;
-}
diff --git a/core/java/android/app/ondeviceintelligence/ITokenInfoCallback.aidl b/core/java/android/app/ondeviceintelligence/ITokenInfoCallback.aidl
new file mode 100644
index 0000000..9219a89
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/ITokenInfoCallback.aidl
@@ -0,0 +1,14 @@
+package android.app.ondeviceintelligence;
+
+import android.os.PersistableBundle;
+import android.app.ondeviceintelligence.TokenInfo;
+
+/**
+  * Interface for receiving the token info of a request for a given feature.
+  *
+  * @hide
+  */
+interface ITokenInfoCallback {
+    void onSuccess(in TokenInfo tokenInfo) = 1;
+    void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 2;
+}
diff --git a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
index 4d8e0d5..d195c4d 100644
--- a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
+++ b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
@@ -26,8 +26,10 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
+import android.content.ComponentName;
 import android.content.Context;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.os.ICancellationSignal;
 import android.os.OutcomeReceiver;
@@ -37,6 +39,8 @@
 
 import androidx.annotation.IntDef;
 
+import com.android.internal.R;
+
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -60,7 +64,16 @@
 @SystemService(Context.ON_DEVICE_INTELLIGENCE_SERVICE)
 @FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
 public class OnDeviceIntelligenceManager {
+    /**
+     * @hide
+     */
     public static final String API_VERSION_BUNDLE_KEY = "ApiVersionBundleKey";
+
+    /**
+     * @hide
+     */
+    public static final String AUGMENT_REQUEST_CONTENT_BUNDLE_KEY =
+            "AugmentRequestContentBundleKey";
     private final Context mContext;
     private final IOnDeviceIntelligenceManager mService;
 
@@ -82,8 +95,6 @@
     public void getVersion(
             @NonNull @CallbackExecutor Executor callbackExecutor,
             @NonNull LongConsumer versionConsumer) {
-        // TODO explore modifying this method into getServicePackageDetails and return both
-        //  version and package name of the remote service implementing this.
         try {
             RemoteCallback callback = new RemoteCallback(result -> {
                 if (result == null) {
@@ -100,6 +111,23 @@
         }
     }
 
+
+    /**
+     * Get package name configured for providing the remote implementation for this system service.
+     */
+    @Nullable
+    @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
+    public String getRemoteServicePackageName() {
+        String serviceConfigValue = mContext.getResources().getString(
+                R.string.config_defaultOnDeviceSandboxedInferenceService);
+        ComponentName componentName = ComponentName.unflattenFromString(serviceConfigValue);
+        if (componentName != null) {
+            return componentName.getPackageName();
+        }
+
+        return null;
+    }
+
     /**
      * Asynchronously get feature for a given id.
      *
@@ -273,29 +301,29 @@
     }
 
     /**
-     * The methods computes the token-count for a given request payload using the provided Feature
-     * details.
+     * The methods computes the token related information for a given request payload using the
+     * provided {@link Feature}.
      *
      * @param feature            feature associated with the request.
      * @param request            request that contains the content data and associated params.
-     * @param outcomeReceiver    callback to populate the token count or exception in case of
+     * @param outcomeReceiver    callback to populate the token info or exception in case of
      *                           failure.
      * @param cancellationSignal signal to invoke cancellation on the operation in the remote
      *                           implementation.
      * @param callbackExecutor   executor to run the callback on.
      */
     @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
-    public void requestTokenCount(@NonNull Feature feature, @NonNull Content request,
+    public void requestTokenInfo(@NonNull Feature feature, @NonNull Content request,
             @Nullable CancellationSignal cancellationSignal,
             @NonNull @CallbackExecutor Executor callbackExecutor,
-            @NonNull OutcomeReceiver<Long,
+            @NonNull OutcomeReceiver<TokenInfo,
                     OnDeviceIntelligenceManagerException> outcomeReceiver) {
         try {
-            ITokenCountCallback callback = new ITokenCountCallback.Stub() {
+            ITokenInfoCallback callback = new ITokenInfoCallback.Stub() {
                 @Override
-                public void onSuccess(long tokenCount) {
+                public void onSuccess(TokenInfo tokenInfo) {
                     Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
-                            () -> outcomeReceiver.onResult(tokenCount)));
+                            () -> outcomeReceiver.onResult(tokenInfo)));
                 }
 
                 @Override
@@ -314,7 +342,7 @@
                 cancellationSignal.setRemote(transport);
             }
 
-            mService.requestTokenCount(feature, request, transport, callback);
+            mService.requestTokenInfo(feature, request, transport, callback);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -328,33 +356,31 @@
      * was a
      * failure.
      *
-     * @param feature                 feature associated with the request.
-     * @param request                 request that contains the Content data and
-     *                                associated params.
-     * @param requestType             type of request being sent for processing the content.
-     * @param responseOutcomeReceiver callback to populate the response content and
-     *                                associated
-     *                                params.
-     * @param processingSignal        signal to invoke custom actions in the
-     *                                remote implementation.
-     * @param cancellationSignal      signal to invoke cancellation or
-     * @param callbackExecutor        executor to run the callback on.
+     * @param feature            feature associated with the request.
+     * @param request            request that contains the Content data and
+     *                           associated params.
+     * @param requestType        type of request being sent for processing the content.
+     * @param cancellationSignal signal to invoke cancellation.
+     * @param processingSignal   signal to send custom signals in the
+     *                           remote implementation.
+     * @param callbackExecutor   executor to run the callback on.
+     * @param responseCallback   callback to populate the response content and
+     *                           associated params.
      */
     @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
 
-    public void processRequest(@NonNull Feature feature, @NonNull Content request,
+    public void processRequest(@NonNull Feature feature, @Nullable Content request,
             @RequestType int requestType,
             @Nullable CancellationSignal cancellationSignal,
             @Nullable ProcessingSignal processingSignal,
             @NonNull @CallbackExecutor Executor callbackExecutor,
-            @NonNull OutcomeReceiver<Content,
-                    OnDeviceIntelligenceManagerProcessingException> responseOutcomeReceiver) {
+            @NonNull ProcessingOutcomeReceiver responseCallback) {
         try {
             IResponseCallback callback = new IResponseCallback.Stub() {
                 @Override
                 public void onSuccess(Content result) {
                     Binder.withCleanCallingIdentity(() -> {
-                        callbackExecutor.execute(() -> responseOutcomeReceiver.onResult(result));
+                        callbackExecutor.execute(() -> responseCallback.onResult(result));
                     });
                 }
 
@@ -362,12 +388,24 @@
                 public void onFailure(int errorCode, String errorMessage,
                         PersistableBundle errorParams) {
                     Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
-                            () -> responseOutcomeReceiver.onError(
+                            () -> responseCallback.onError(
                                     new OnDeviceIntelligenceManagerProcessingException(
                                             errorCode, errorMessage, errorParams))));
                 }
+
+                @Override
+                public void onDataAugmentRequest(@NonNull Content content,
+                        @NonNull RemoteCallback contentCallback) {
+                    Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+                            () -> responseCallback.onDataAugmentRequest(content, result -> {
+                                Bundle bundle = new Bundle();
+                                bundle.putParcelable(AUGMENT_REQUEST_CONTENT_BUNDLE_KEY, result);
+                                callbackExecutor.execute(() -> contentCallback.sendResult(bundle));
+                            })));
+                }
             };
 
+
             IProcessingSignal transport = null;
             if (processingSignal != null) {
                 transport = ProcessingSignal.createTransport();
@@ -389,46 +427,48 @@
     }
 
     /**
-     * Variation of {@link #processRequest} that asynchronously processes a request in a streaming
+     * Variation of {@link #processRequest} that asynchronously processes a request in a
+     * streaming
      * fashion, where new content is pushed to caller in chunks via the
-     * {@link StreamingResponseReceiver#onNewContent}. After the streaming is complete,
-     * the service should call {@link StreamingResponseReceiver#onResult} and can optionally
-     * populate the complete {@link Response}'s Content as part of the callback when the final
-     * {@link Response} contains an enhanced aggregation of the Contents already streamed.
+     * {@link StreamedProcessingOutcomeReceiver#onNewContent}. After the streaming is complete,
+     * the service should call {@link StreamedProcessingOutcomeReceiver#onResult} and can optionally
+     * populate the complete the full response {@link Content} as part of the callback in cases
+     * when the final response contains an enhanced aggregation of the Contents already
+     * streamed.
      *
      * @param feature                   feature associated with the request.
      * @param request                   request that contains the Content data and associated
      *                                  params.
      * @param requestType               type of request being sent for processing the content.
-     * @param processingSignal          signal to invoke  other custom actions in the
+     * @param cancellationSignal        signal to invoke cancellation.
+     * @param processingSignal          signal to send custom signals in the
      *                                  remote implementation.
-     * @param cancellationSignal        signal to invoke cancellation
-     * @param streamingResponseReceiver streaming callback to populate the response content and
+     * @param streamingResponseCallback streaming callback to populate the response content and
      *                                  associated params.
      * @param callbackExecutor          executor to run the callback on.
      */
     @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
-    public void processRequestStreaming(@NonNull Feature feature, @NonNull Content request,
+    public void processRequestStreaming(@NonNull Feature feature, @Nullable Content request,
             @RequestType int requestType,
             @Nullable CancellationSignal cancellationSignal,
             @Nullable ProcessingSignal processingSignal,
             @NonNull @CallbackExecutor Executor callbackExecutor,
-            @NonNull StreamingResponseReceiver<Content, Content,
-                    OnDeviceIntelligenceManagerProcessingException> streamingResponseReceiver) {
+            @NonNull StreamedProcessingOutcomeReceiver streamingResponseCallback) {
         try {
             IStreamingResponseCallback callback = new IStreamingResponseCallback.Stub() {
                 @Override
                 public void onNewContent(Content result) {
                     Binder.withCleanCallingIdentity(() -> {
                         callbackExecutor.execute(
-                                () -> streamingResponseReceiver.onNewContent(result));
+                                () -> streamingResponseCallback.onNewContent(result));
                     });
                 }
 
                 @Override
                 public void onSuccess(Content result) {
                     Binder.withCleanCallingIdentity(() -> {
-                        callbackExecutor.execute(() -> streamingResponseReceiver.onResult(result));
+                        callbackExecutor.execute(
+                                () -> streamingResponseCallback.onResult(result));
                     });
                 }
 
@@ -437,11 +477,26 @@
                         PersistableBundle errorParams) {
                     Binder.withCleanCallingIdentity(() -> {
                         callbackExecutor.execute(
-                                () -> streamingResponseReceiver.onError(
+                                () -> streamingResponseCallback.onError(
                                         new OnDeviceIntelligenceManagerProcessingException(
                                                 errorCode, errorMessage, errorParams)));
                     });
                 }
+
+
+                @Override
+                public void onDataAugmentRequest(@NonNull Content content,
+                        @NonNull RemoteCallback contentCallback) {
+                    Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+                            () -> streamingResponseCallback.onDataAugmentRequest(content,
+                                    contentResponse -> {
+                                        Bundle bundle = new Bundle();
+                                        bundle.putParcelable(AUGMENT_REQUEST_CONTENT_BUNDLE_KEY,
+                                                contentResponse);
+                                        callbackExecutor.execute(
+                                                () -> contentCallback.sendResult(bundle));
+                                    })));
+                }
             };
 
             IProcessingSignal transport = null;
@@ -468,7 +523,8 @@
     public static final int REQUEST_TYPE_INFERENCE = 0;
 
     /**
-     * Prepares the remote implementation environment for e.g.loading inference runtime etc.which
+     * Prepares the remote implementation environment for e.g.loading inference runtime etc
+     * .which
      * are time consuming beforehand to remove overhead and allow quick processing of requests
      * thereof.
      */
@@ -485,7 +541,8 @@
             REQUEST_TYPE_PREPARE,
             REQUEST_TYPE_EMBEDDINGS
     }, open = true)
-    @Target({ElementType.TYPE_USE, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})
+    @Target({ElementType.TYPE_USE, ElementType.METHOD, ElementType.PARAMETER,
+            ElementType.FIELD})
     @Retention(RetentionPolicy.SOURCE)
     public @interface RequestType {
     }
@@ -501,17 +558,32 @@
          */
         public static final int ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE = 1000;
 
+        /**
+         * Error code to be used for on device intelligence manager failures.
+         *
+         * @hide
+         */
+        @IntDef(
+                value = {
+                        ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE
+                }, open = true)
+        @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+        @interface OnDeviceIntelligenceManagerErrorCode {
+        }
+
         private final int mErrorCode;
         private final PersistableBundle errorParams;
 
-        public OnDeviceIntelligenceManagerException(int errorCode, @NonNull String errorMessage,
+        public OnDeviceIntelligenceManagerException(
+                @OnDeviceIntelligenceManagerErrorCode int errorCode, @NonNull String errorMessage,
                 @NonNull PersistableBundle errorParams) {
             super(errorMessage);
             this.mErrorCode = errorCode;
             this.errorParams = errorParams;
         }
 
-        public OnDeviceIntelligenceManagerException(int errorCode,
+        public OnDeviceIntelligenceManagerException(
+                @OnDeviceIntelligenceManagerErrorCode int errorCode,
                 @NonNull PersistableBundle errorParams) {
             this.mErrorCode = errorCode;
             this.errorParams = errorParams;
@@ -573,11 +645,15 @@
         /** Inference suspended so that higher-priority inference can run. */
         public static final int PROCESSING_ERROR_SUSPENDED = 13;
 
-        /** Underlying processing encountered an internal error, like a violated precondition. */
+        /**
+         * Underlying processing encountered an internal error, like a violated precondition
+         * .
+         */
         public static final int PROCESSING_ERROR_INTERNAL = 14;
 
         /**
-         * The processing was not able to be passed on to the remote implementation, as the service
+         * The processing was not able to be passed on to the remote implementation, as the
+         * service
          * was unavailable.
          */
         public static final int PROCESSING_ERROR_SERVICE_UNAVAILABLE = 15;
diff --git a/core/java/android/app/ondeviceintelligence/ProcessingOutcomeReceiver.java b/core/java/android/app/ondeviceintelligence/ProcessingOutcomeReceiver.java
new file mode 100644
index 0000000..b0b6e19
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/ProcessingOutcomeReceiver.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 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.app.ondeviceintelligence;
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.OutcomeReceiver;
+
+import java.util.function.Consumer;
+
+/**
+ * Response Callback to populate the processed response or any error that occurred during the
+ * request processing. This callback also provides a method to request additional data to be
+ * augmented to the request-processing, using  the partial {@link Content} that was already
+ * processed in the remote implementation.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public interface ProcessingOutcomeReceiver extends
+        OutcomeReceiver<Content,
+                OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> {
+    /**
+     * Callback to be invoked in cases where the remote service needs to perform retrieval or
+     * transformation operations based on a partially processed request, in order to augment the
+     * final response, by using the additional context sent via this callback.
+     *
+     * @param content         The content payload that should be used to augment ongoing request.
+     * @param contentConsumer The augmentation data that should be sent to remote
+     *                        service for further processing a request.
+     */
+    default void onDataAugmentRequest(@NonNull Content content,
+            @NonNull Consumer<Content> contentConsumer) {
+        contentConsumer.accept(null);
+    }
+}
diff --git a/core/java/android/app/ondeviceintelligence/StreamingResponseReceiver.java b/core/java/android/app/ondeviceintelligence/StreamedProcessingOutcomeReceiver.java
similarity index 71%
rename from core/java/android/app/ondeviceintelligence/StreamingResponseReceiver.java
rename to core/java/android/app/ondeviceintelligence/StreamedProcessingOutcomeReceiver.java
index ebcf61c..ac2b032 100644
--- a/core/java/android/app/ondeviceintelligence/StreamingResponseReceiver.java
+++ b/core/java/android/app/ondeviceintelligence/StreamedProcessingOutcomeReceiver.java
@@ -21,23 +21,19 @@
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
-import android.os.OutcomeReceiver;
 
 /**
  * Streaming variant of outcome receiver to populate response while processing a given request,
- * possibly in
- * chunks to provide a async processing behaviour to the caller.
+ * possibly in chunks to provide a async processing behaviour to the caller.
  *
  * @hide
  */
 @SystemApi
 @FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
-public interface StreamingResponseReceiver<R, T, E extends Throwable> extends
-        OutcomeReceiver<R, E> {
+public interface StreamedProcessingOutcomeReceiver extends ProcessingOutcomeReceiver {
     /**
-     * Callback to be invoked when a part of the response i.e. some {@link Content} is already
-     * processed and
-     * needs to be passed onto the caller.
+     * Callback that would be invoked when a part of the response i.e. some {@link Content} is
+     * already processed and needs to be passed onto the caller.
      */
-    void onNewContent(@NonNull T content);
+    void onNewContent(@NonNull Content content);
 }
diff --git a/core/java/android/app/ondeviceintelligence/TokenInfo.aidl b/core/java/android/app/ondeviceintelligence/TokenInfo.aidl
new file mode 100644
index 0000000..2c19c1e
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/TokenInfo.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 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.app.ondeviceintelligence;
+
+/**
+  * @hide
+  */
+parcelable TokenInfo;
diff --git a/core/java/android/app/ondeviceintelligence/TokenInfo.java b/core/java/android/app/ondeviceintelligence/TokenInfo.java
new file mode 100644
index 0000000..035cc4b
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/TokenInfo.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2024 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.app.ondeviceintelligence;
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.PersistableBundle;
+
+/**
+ * This class is used to provide a token count response for the
+ * {@link OnDeviceIntelligenceManager#requestTokenInfo} outcome receiver.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public final class TokenInfo implements Parcelable {
+    private final long mCount;
+    private final PersistableBundle mInfoParams;
+
+    /**
+     * Construct a token count using the count value and associated params.
+     */
+    public TokenInfo(long count, @NonNull PersistableBundle persistableBundle) {
+        this.mCount = count;
+        mInfoParams = persistableBundle;
+    }
+
+    /**
+     * Construct a token count using the count value.
+     */
+    public TokenInfo(long count) {
+        this.mCount = count;
+        this.mInfoParams = new PersistableBundle();
+    }
+
+    /**
+     * Returns the token count associated with a request payload.
+     */
+    public long getCount() {
+        return mCount;
+    }
+
+    /**
+     * Returns the params representing token info.
+     */
+    @NonNull
+    public PersistableBundle getInfoParams() {
+        return mInfoParams;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeLong(mCount);
+        dest.writePersistableBundle(mInfoParams);
+    }
+
+    public static final @NonNull Parcelable.Creator<TokenInfo> CREATOR
+            = new Parcelable.Creator<>() {
+        @Override
+        public TokenInfo[] newArray(int size) {
+            return new TokenInfo[size];
+        }
+
+        @Override
+        public TokenInfo createFromParcel(@NonNull Parcel in) {
+            return new TokenInfo(in.readLong(), in.readPersistableBundle());
+        }
+    };
+}
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index eb82e1f..cda4d89 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -1417,13 +1417,15 @@
      * @see AppWidgetProviderInfo#WIDGET_CATEGORY_HOME_SCREEN
      * @see AppWidgetProviderInfo#WIDGET_CATEGORY_KEYGUARD
      * @see AppWidgetProviderInfo#WIDGET_CATEGORY_SEARCHBOX
+     *
+     * @return true if the call was successful, false if it was rate-limited.
      */
     @FlaggedApi(Flags.FLAG_GENERATED_PREVIEWS)
-    public void setWidgetPreview(@NonNull ComponentName provider,
+    public boolean setWidgetPreview(@NonNull ComponentName provider,
             @AppWidgetProviderInfo.CategoryFlags int widgetCategories,
             @NonNull RemoteViews preview) {
         try {
-            mService.setWidgetPreview(provider, widgetCategories, preview);
+            return mService.setWidgetPreview(provider, widgetCategories, preview);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 70d2c7a..c7e5d88 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -5081,8 +5081,6 @@
      * @see #getSystemService
      * @see android.hardware.face.FaceManager
      */
-    @FlaggedApi(android.hardware.biometrics.Flags.FLAG_FACE_BACKGROUND_AUTHENTICATION)
-    @SystemApi
     public static final String FACE_SERVICE = "face";
 
     /**
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 6bb9c33..41c1f17 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -697,8 +697,9 @@
     public List<UserHandle> getProfiles() {
         if (mUserManager.isManagedProfile()
                 || (android.multiuser.Flags.enableLauncherAppsHiddenProfileChecks()
-                        && android.os.Flags.allowPrivateProfile()
-                        && mUserManager.isPrivateProfile())) {
+                    && android.os.Flags.allowPrivateProfile()
+                    && android.multiuser.Flags.enablePrivateSpaceFeatures()
+                    && mUserManager.isPrivateProfile())) {
             // If it's a managed or private profile, only return the current profile.
             final List result = new ArrayList(1);
             result.add(android.os.Process.myUserHandle());
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index ac80561..48a7cc9 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -184,3 +184,11 @@
     description: "Enable Private Space telephony and SMS intent redirection to the main user"
     bug: "325576602"
 }
+
+flag {
+    name: "block_private_space_creation"
+    namespace: "profile_experiences"
+    description: "Allow blocking private space creation based on specific conditions"
+    bug: "290333800"
+    is_fixed_read_only: true
+}
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
index 4dcc517..a908456 100644
--- a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
@@ -163,14 +163,31 @@
     }
 
     /**
-     * Update the URI relative filter groups for a package. All previously existing groups
-     * will be cleared before the new groups will be applied.
+     * Update the URI relative filter groups for a package. The groups set using this API acts
+     * as an additional filtering layer during intent resolution. It does not replace any
+     * existing groups that have been added to the package's intent filters either using the
+     * {@link android.content.IntentFilter#addUriRelativeFilterGroup(UriRelativeFilterGroup)}
+     * API or defined in the manifest.
+     * <p>
+     * Groups can be indexed to any domain or can be indexed for all subdomains by prefixing the
+     * hostname with a wildcard (i.e. "*.example.com"). Priority will be first given to groups
+     * that are indexed to the specific subdomain of the intent's data URI followed by any groups
+     * indexed to wildcard subdomains. If the subdomain consists of more than one label, priority
+     * will decrease corresponding to the decreasing number of subdomain labels after the wildcard.
+     * For example "a.b.c.d" will match "*.b.c.d" before "*.c.d".
+     * <p>
+     * All previously existing groups set for a domain index using this API will be cleared when
+     * new groups are set.
      *
      * @param packageName The name of the package.
      * @param domainToGroupsMap A map of domains to a list of {@link UriRelativeFilterGroup}s that
      *                         should apply to them. Groups for each domain will replace any groups
-     *                         provided for that domain in a prior call to this method. Groups will
+     *                         provided for that domain in a prior call to this method. To clear
+     *                         existing groups, set the list to null or a empty list. Groups will
      *                         be evaluated in the order they are provided.
+     *
+     * @see UriRelativeFilterGroup
+     * @see android.content.IntentFilter
      * @hide
      */
     @SystemApi
diff --git a/core/java/android/hardware/SensorPrivacyManager.java b/core/java/android/hardware/SensorPrivacyManager.java
index 08b9064..4cdaaddd 100644
--- a/core/java/android/hardware/SensorPrivacyManager.java
+++ b/core/java/android/hardware/SensorPrivacyManager.java
@@ -727,7 +727,7 @@
      * Returns if camera privacy is enabled for a specific package.
      *
      * @param packageName The package to check
-     * @return boolean sensor privacy state.
+     * @return boolean camera privacy state.
      *
      * @hide
      */
diff --git a/core/java/android/hardware/biometrics/flags.aconfig b/core/java/android/hardware/biometrics/flags.aconfig
index 8165d44..3ba8be4 100644
--- a/core/java/android/hardware/biometrics/flags.aconfig
+++ b/core/java/android/hardware/biometrics/flags.aconfig
@@ -28,10 +28,3 @@
   bug: "302735104"
 }
 
-flag {
-  name: "face_background_authentication"
-  namespace: "biometrics_framework"
-  description: "Feature flag for allowing face background authentication with USE_BACKGROUND_FACE_AUTHENTICATION."
-  bug: "318584190"
-}
-
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 066c45f..210ce2b 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -18,23 +18,18 @@
 
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 import static android.Manifest.permission.MANAGE_BIOMETRIC;
-import static android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION;
 import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
 import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_NONE;
-import static android.hardware.biometrics.Flags.FLAG_FACE_BACKGROUND_AUTHENTICATION;
 
-import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
-import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricFaceConstants;
-import android.hardware.biometrics.BiometricPrompt;
 import android.hardware.biometrics.BiometricStateListener;
 import android.hardware.biometrics.CryptoObject;
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
@@ -42,9 +37,9 @@
 import android.os.CancellationSignal;
 import android.os.CancellationSignal.OnCancelListener;
 import android.os.Handler;
-import android.os.HandlerExecutor;
 import android.os.IBinder;
 import android.os.IRemoteCallback;
+import android.os.Looper;
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.Trace;
@@ -54,21 +49,15 @@
 import android.view.Surface;
 
 import com.android.internal.R;
+import com.android.internal.os.SomeArgs;
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.concurrent.Executor;
 
 /**
  * A class that coordinates access to the face authentication hardware.
- *
- * <p>Please use {@link BiometricPrompt} for face authentication unless the experience must be
- * customized for unique system-level utilities, like the lock screen or ambient background usage.
- *
  * @hide
  */
-@FlaggedApi(FLAG_FACE_BACKGROUND_AUTHENTICATION)
-@SystemApi
 @SystemService(Context.FACE_SERVICE)
 public class FaceManager implements BiometricAuthenticator, BiometricFaceConstants {
 
@@ -99,76 +88,81 @@
     @Nullable private GenerateChallengeCallback mGenerateChallengeCallback;
     private CryptoObject mCryptoObject;
     private Face mRemovalFace;
-    private Executor mExecutor;
+    private Handler mHandler;
     private List<FaceSensorPropertiesInternal> mProps = new ArrayList<>();
 
     private final IFaceServiceReceiver mServiceReceiver = new IFaceServiceReceiver.Stub() {
 
         @Override // binder call
         public void onEnrollResult(Face face, int remaining) {
-            mExecutor.execute(() -> sendEnrollResult(face, remaining));
+            mHandler.obtainMessage(MSG_ENROLL_RESULT, remaining, 0, face).sendToTarget();
         }
 
         @Override // binder call
         public void onAcquired(int acquireInfo, int vendorCode) {
-            mExecutor.execute(() -> sendAcquiredResult(acquireInfo, vendorCode));
+            mHandler.obtainMessage(MSG_ACQUIRED, acquireInfo, vendorCode).sendToTarget();
         }
 
         @Override // binder call
         public void onAuthenticationSucceeded(Face face, int userId, boolean isStrongBiometric) {
-            mExecutor.execute(() -> sendAuthenticatedSucceeded(face, userId, isStrongBiometric));
+            mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId,
+                    isStrongBiometric ? 1 : 0, face).sendToTarget();
         }
 
         @Override // binder call
         public void onFaceDetected(int sensorId, int userId, boolean isStrongBiometric) {
-            mExecutor.execute(() -> sendFaceDetected(sensorId, userId, isStrongBiometric));
+            mHandler.obtainMessage(MSG_FACE_DETECTED, sensorId, userId, isStrongBiometric)
+                    .sendToTarget();
         }
 
         @Override // binder call
         public void onAuthenticationFailed() {
-            mExecutor.execute(() -> sendAuthenticatedFailed());
+            mHandler.obtainMessage(MSG_AUTHENTICATION_FAILED).sendToTarget();
         }
 
         @Override // binder call
         public void onError(int error, int vendorCode) {
-            mExecutor.execute(() -> sendErrorResult(error, vendorCode));
+            mHandler.obtainMessage(MSG_ERROR, error, vendorCode).sendToTarget();
         }
 
         @Override // binder call
         public void onRemoved(Face face, int remaining) {
-            mExecutor.execute(() -> {
-                sendRemovedResult(face, remaining);
-                if (remaining == 0) {
-                    Settings.Secure.putIntForUser(mContext.getContentResolver(),
-                            Settings.Secure.FACE_UNLOCK_RE_ENROLL, 0,
-                            UserHandle.USER_CURRENT);
-                }
-            });
+            mHandler.obtainMessage(MSG_REMOVED, remaining, 0, face).sendToTarget();
+            if (remaining == 0) {
+                Settings.Secure.putIntForUser(mContext.getContentResolver(),
+                        Settings.Secure.FACE_UNLOCK_RE_ENROLL, 0,
+                        UserHandle.USER_CURRENT);
+            }
         }
 
         @Override
         public void onFeatureSet(boolean success, int feature) {
-            mExecutor.execute(() -> sendSetFeatureCompleted(success, feature));
+            mHandler.obtainMessage(MSG_SET_FEATURE_COMPLETED, feature, 0, success).sendToTarget();
         }
 
         @Override
         public void onFeatureGet(boolean success, int[] features, boolean[] featureState) {
-            mExecutor.execute(() -> sendGetFeatureCompleted(success, features, featureState));
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = success;
+            args.arg2 = features;
+            args.arg3 = featureState;
+            mHandler.obtainMessage(MSG_GET_FEATURE_COMPLETED, args).sendToTarget();
         }
 
         @Override
         public void onChallengeGenerated(int sensorId, int userId, long challenge) {
-            mExecutor.execute(() -> sendChallengeGenerated(sensorId, userId, challenge));
+            mHandler.obtainMessage(MSG_CHALLENGE_GENERATED, sensorId, userId, challenge)
+                    .sendToTarget();
         }
 
         @Override
         public void onAuthenticationFrame(FaceAuthenticationFrame frame) {
-            mExecutor.execute(() -> sendAuthenticationFrame(frame));
+            mHandler.obtainMessage(MSG_AUTHENTICATION_FRAME, frame).sendToTarget();
         }
 
         @Override
         public void onEnrollmentFrame(FaceEnrollFrame frame) {
-            mExecutor.execute(() -> sendEnrollmentFrame(frame));
+            mHandler.obtainMessage(MSG_ENROLLMENT_FRAME, frame).sendToTarget();
         }
     };
 
@@ -181,7 +175,7 @@
         if (mService == null) {
             Slog.v(TAG, "FaceAuthenticationManagerService was null");
         }
-        mExecutor = context.getMainExecutor();
+        mHandler = new MyHandler(context);
         if (context.checkCallingOrSelfPermission(USE_BIOMETRIC_INTERNAL)
                 == PackageManager.PERMISSION_GRANTED) {
             addAuthenticatorsRegisteredCallback(new IFaceAuthenticatorsRegisteredCallback.Stub() {
@@ -195,16 +189,18 @@
     }
 
     /**
-     * Returns an {@link Executor} for the given {@link Handler} or the main {@link Executor} if
-     * {@code handler} is {@code null}.
+     * Use the provided handler thread for events.
      */
-    private @NonNull Executor createExecutorForHandlerIfNeeded(@Nullable Handler handler) {
-        return handler != null ? new HandlerExecutor(handler) : mContext.getMainExecutor();
+    private void useHandler(Handler handler) {
+        if (handler != null) {
+            mHandler = new MyHandler(handler.getLooper());
+        } else if (mHandler.getLooper() != mContext.getMainLooper()) {
+            mHandler = new MyHandler(mContext.getMainLooper());
+        }
     }
 
     /**
      * @deprecated use {@link #authenticate(CryptoObject, CancellationSignal, AuthenticationCallback, Handler, FaceAuthenticateOptions)}.
-     * @hide
      */
     @Deprecated
     @RequiresPermission(USE_BIOMETRIC_INTERNAL)
@@ -216,22 +212,17 @@
     }
 
     /**
-     * Request authentication.
-     *
-     * <p>This call operates the face recognition hardware and starts capturing images.
+     * Request authentication. This call operates the face recognition hardware and starts capturing images.
      * It terminates when
      * {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)} or
      * {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)} is called, at
      * which point the object is no longer valid. The operation can be canceled by using the
-     * provided {@code cancel} object.
+     * provided cancel object.
      *
-     * @param crypto   the cryptographic operations to use for authentication or {@code null} if
-     *                 none required
-     * @param cancel   an object that can be used to cancel authentication or {@code null} if not
-     *                 needed
+     * @param crypto   object associated with the call or null if none required
+     * @param cancel   an object that can be used to cancel authentication
      * @param callback an object to receive authentication events
-     * @param handler  an optional handler to handle callback events or {@code null} to obtain main
-     *                 {@link Executor} from {@link Context}
+     * @param handler  an optional handler to handle callback events
      * @param options  additional options to customize this request
      * @throws IllegalArgumentException if the crypto operation is not supported or is not backed
      *                                  by
@@ -244,14 +235,6 @@
     public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel,
             @NonNull AuthenticationCallback callback, @Nullable Handler handler,
             @NonNull FaceAuthenticateOptions options) {
-        authenticate(crypto, cancel, callback, createExecutorForHandlerIfNeeded(handler),
-                options, false /* allowBackgroundAuthentication */);
-    }
-
-    @RequiresPermission(anyOf = {USE_BIOMETRIC_INTERNAL, USE_BACKGROUND_FACE_AUTHENTICATION})
-    private void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel,
-            @NonNull AuthenticationCallback callback, @NonNull Executor executor,
-            @NonNull FaceAuthenticateOptions options, boolean allowBackgroundAuthentication) {
         if (callback == null) {
             throw new IllegalArgumentException("Must supply an authentication callback");
         }
@@ -266,15 +249,13 @@
 
         if (mService != null) {
             try {
-                mExecutor = executor;
+                useHandler(handler);
                 mAuthenticationCallback = callback;
                 mCryptoObject = crypto;
                 final long operationId = crypto != null ? crypto.getOpId() : 0;
                 Trace.beginSection("FaceManager#authenticate");
-                final long authId = allowBackgroundAuthentication
-                        ? mService.authenticateInBackground(
-                                mToken, operationId, mServiceReceiver, options)
-                        : mService.authenticate(mToken, operationId, mServiceReceiver, options);
+                final long authId = mService.authenticate(
+                        mToken, operationId, mServiceReceiver, options);
                 if (cancel != null) {
                     cancel.setOnCancelListener(new OnAuthenticationCancelListener(authId));
                 }
@@ -292,67 +273,6 @@
     }
 
     /**
-     * Request background face authentication.
-     *
-     * <p>This call operates the face recognition hardware and starts capturing images.
-     * It terminates when
-     * {@link BiometricPrompt.AuthenticationCallback#onAuthenticationError(int, CharSequence)} or
-     * {@link BiometricPrompt.AuthenticationCallback#onAuthenticationSucceeded(
-     * BiometricPrompt.AuthenticationResult)} is called, at which point the object is no longer
-     * valid. The operation can be canceled by using the provided cancel object.
-     *
-     * <p>See {@link BiometricPrompt#authenticate} for more details. Please use
-     * {@link BiometricPrompt} for face authentication unless the experience must be customized for
-     * unique system-level utilities, like the lock screen or ambient background usage.
-     *
-     * @param executor the specified {@link Executor} to handle callback events; if {@code null},
-     *                 the callback will be executed on the main {@link Executor}.
-     * @param crypto   the cryptographic operations to use for authentication or {@code null} if
-     *                 none required.
-     * @param cancel   an object that can be used to cancel authentication or {@code null} if not
-     *                 needed.
-     * @param callback an object to receive authentication events.
-     * @throws IllegalArgumentException if the crypto operation is not supported or is not backed
-     *                                  by
-     *                                  <a href="{@docRoot}training/articles/keystore.html">Android
-     *                                  Keystore facility</a>.
-     * @hide
-     */
-    @RequiresPermission(USE_BACKGROUND_FACE_AUTHENTICATION)
-    @FlaggedApi(FLAG_FACE_BACKGROUND_AUTHENTICATION)
-    @SystemApi
-    public void authenticateInBackground(@Nullable Executor executor,
-            @Nullable BiometricPrompt.CryptoObject crypto, @Nullable CancellationSignal cancel,
-            @NonNull BiometricPrompt.AuthenticationCallback callback) {
-        authenticate(crypto, cancel, new AuthenticationCallback() {
-                    @Override
-                    public void onAuthenticationError(int errorCode, CharSequence errString) {
-                        callback.onAuthenticationError(errorCode, errString);
-                    }
-
-                    @Override
-                    public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
-                        callback.onAuthenticationHelp(helpCode, helpString);
-                    }
-
-                    @Override
-                    public void onAuthenticationSucceeded(AuthenticationResult result) {
-                        callback.onAuthenticationSucceeded(
-                                new BiometricPrompt.AuthenticationResult(
-                                        crypto,
-                                        BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC));
-                    }
-
-                    @Override
-                    public void onAuthenticationFailed() {
-                        callback.onAuthenticationFailed();
-                    }
-                }, executor == null ? mContext.getMainExecutor() : executor,
-                new FaceAuthenticateOptions.Builder().build(),
-                true /* allowBackgroundAuthentication */);
-    }
-
-    /**
      * Uses the face hardware to detect for the presence of a face, without giving details about
      * accept/reject/lockout.
      * @hide
@@ -710,14 +630,12 @@
     }
 
     /**
-     * Determine if there are enrolled {@link Face} templates.
+     * Determine if there is a face enrolled.
      *
-     * @return {@code true} if there are enrolled {@link Face} templates, {@code false} otherwise
+     * @return true if a face is enrolled, false otherwise
      * @hide
      */
-    @RequiresPermission(anyOf = {USE_BIOMETRIC_INTERNAL, USE_BACKGROUND_FACE_AUTHENTICATION})
-    @FlaggedApi(FLAG_FACE_BACKGROUND_AUTHENTICATION)
-    @SystemApi
+    @RequiresPermission(USE_BIOMETRIC_INTERNAL)
     public boolean hasEnrolledTemplates() {
         return hasEnrolledTemplates(UserHandle.myUserId());
     }
@@ -882,7 +800,7 @@
                                             PowerManager.PARTIAL_WAKE_LOCK,
                                             "faceLockoutResetCallback");
                                     wakeLock.acquire();
-                                    mExecutor.execute(() -> {
+                                    mHandler.post(() -> {
                                         try {
                                             callback.onLockoutReset(sensorId);
                                         } finally {
@@ -1352,6 +1270,70 @@
         }
     }
 
+    private class MyHandler extends Handler {
+        private MyHandler(Context context) {
+            super(context.getMainLooper());
+        }
+
+        private MyHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(android.os.Message msg) {
+            Trace.beginSection("FaceManager#handleMessage: " + Integer.toString(msg.what));
+            switch (msg.what) {
+                case MSG_ENROLL_RESULT:
+                    sendEnrollResult((Face) msg.obj, msg.arg1 /* remaining */);
+                    break;
+                case MSG_ACQUIRED:
+                    sendAcquiredResult(msg.arg1 /* acquire info */, msg.arg2 /* vendorCode */);
+                    break;
+                case MSG_AUTHENTICATION_SUCCEEDED:
+                    sendAuthenticatedSucceeded((Face) msg.obj, msg.arg1 /* userId */,
+                            msg.arg2 == 1 /* isStrongBiometric */);
+                    break;
+                case MSG_AUTHENTICATION_FAILED:
+                    sendAuthenticatedFailed();
+                    break;
+                case MSG_ERROR:
+                    sendErrorResult(msg.arg1 /* errMsgId */, msg.arg2 /* vendorCode */);
+                    break;
+                case MSG_REMOVED:
+                    sendRemovedResult((Face) msg.obj, msg.arg1 /* remaining */);
+                    break;
+                case MSG_SET_FEATURE_COMPLETED:
+                    sendSetFeatureCompleted((boolean) msg.obj /* success */,
+                            msg.arg1 /* feature */);
+                    break;
+                case MSG_GET_FEATURE_COMPLETED:
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    sendGetFeatureCompleted((boolean) args.arg1 /* success */,
+                            (int[]) args.arg2 /* features */,
+                            (boolean[]) args.arg3 /* featureState */);
+                    args.recycle();
+                    break;
+                case MSG_CHALLENGE_GENERATED:
+                    sendChallengeGenerated(msg.arg1 /* sensorId */, msg.arg2 /* userId */,
+                            (long) msg.obj /* challenge */);
+                    break;
+                case MSG_FACE_DETECTED:
+                    sendFaceDetected(msg.arg1 /* sensorId */, msg.arg2 /* userId */,
+                            (boolean) msg.obj /* isStrongBiometric */);
+                    break;
+                case MSG_AUTHENTICATION_FRAME:
+                    sendAuthenticationFrame((FaceAuthenticationFrame) msg.obj /* frame */);
+                    break;
+                case MSG_ENROLLMENT_FRAME:
+                    sendEnrollmentFrame((FaceEnrollFrame) msg.obj /* frame */);
+                    break;
+                default:
+                    Slog.w(TAG, "Unknown message: " + msg.what);
+            }
+            Trace.endSection();
+        }
+    }
+
     private void sendSetFeatureCompleted(boolean success, int feature) {
         if (mSetFeatureCallback == null) {
             return;
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index b98c0cb..553d9f7 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -47,7 +47,7 @@
     byte[] dumpSensorServiceStateProto(int sensorId, boolean clearSchedulerBuffer);
 
     // Retrieve static sensor properties for all face sensors
-    @EnforcePermission(anyOf = {"USE_BIOMETRIC_INTERNAL", "USE_BACKGROUND_FACE_AUTHENTICATION"})
+    @EnforcePermission("USE_BIOMETRIC_INTERNAL")
     List<FaceSensorPropertiesInternal> getSensorPropertiesInternal(String opPackageName);
 
     // Retrieve static sensor properties for the specified sensor
@@ -59,11 +59,6 @@
     long authenticate(IBinder token, long operationId, IFaceServiceReceiver receiver,
             in FaceAuthenticateOptions options);
 
-    // Authenticate with a face. A requestId is returned that can be used to cancel this operation.
-    @EnforcePermission("USE_BACKGROUND_FACE_AUTHENTICATION")
-    long authenticateInBackground(IBinder token, long operationId, IFaceServiceReceiver receiver,
-            in FaceAuthenticateOptions options);
-
     // Uses the face hardware to detect for the presence of a face, without giving details
     // about accept/reject/lockout. A requestId is returned that can be used to cancel this
     // operation.
@@ -138,7 +133,7 @@
     void revokeChallenge(IBinder token, int sensorId, int userId, String opPackageName, long challenge);
 
     // Determine if a user has at least one enrolled face
-    @EnforcePermission(anyOf = {"USE_BIOMETRIC_INTERNAL", "USE_BACKGROUND_FACE_AUTHENTICATION"})
+    @EnforcePermission("USE_BIOMETRIC_INTERNAL")
     boolean hasEnrolledFaces(int sensorId, int userId, String opPackageName);
 
     // Return the LockoutTracker status for the specified user
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 800ba6d..23d6007 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -85,6 +85,7 @@
     boolean isUserSwitcherEnabled(boolean showEvenIfNotActionable, int mUserId);
     boolean isRestricted(int userId);
     boolean canHaveRestrictedProfile(int userId);
+    boolean canAddPrivateProfile(int userId);
     int getUserSerialNumber(int userId);
     int getUserHandle(int userSerialNumber);
     int getUserRestrictionSource(String restrictionKey, int userId);
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 9757a10..fdaa0b4 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2353,6 +2353,17 @@
     public static final int USER_OPERATION_ERROR_USER_ACCOUNT_ALREADY_EXISTS = 7;
 
     /**
+     * Indicates user operation failed because user is disabled on the device.
+     * @hide
+     */
+    public static final int USER_OPERATION_ERROR_DISABLED_USER = 8;
+    /**
+     * Indicates user operation failed because user is disabled on the device.
+     * @hide
+     */
+    public static final int USER_OPERATION_ERROR_PRIVATE_PROFILE = 9;
+
+    /**
      * Result returned from various user operations.
      *
      * @hide
@@ -2366,7 +2377,9 @@
             USER_OPERATION_ERROR_CURRENT_USER,
             USER_OPERATION_ERROR_LOW_STORAGE,
             USER_OPERATION_ERROR_MAX_USERS,
-            USER_OPERATION_ERROR_USER_ACCOUNT_ALREADY_EXISTS
+            USER_OPERATION_ERROR_USER_ACCOUNT_ALREADY_EXISTS,
+            USER_OPERATION_ERROR_DISABLED_USER,
+            USER_OPERATION_ERROR_PRIVATE_PROFILE,
     })
     public @interface UserOperationResult {}
 
@@ -2563,6 +2576,17 @@
     }
 
     /**
+     * Returns whether the device supports Private Profile
+     * @hide
+     */
+    public static boolean isPrivateProfileEnabled() {
+        if (android.multiuser.Flags.blockPrivateSpaceCreation()) {
+            return !ActivityManager.isLowRamDeviceStatic();
+        }
+        return true;
+    }
+
+    /**
      * Returns whether multiple admins are enabled on the device
      * @hide
      */
@@ -3155,6 +3179,27 @@
     }
 
     /**
+     * Checks if it's possible to add a private profile to the context user
+     * @return whether the context user can add a private profile.
+     * @hide
+     */
+    @RequiresPermission(anyOf = {
+            Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS},
+            conditional = true)
+    @UserHandleAware
+    public boolean canAddPrivateProfile() {
+        if (android.multiuser.Flags.blockPrivateSpaceCreation()) {
+            try {
+                return mService.canAddPrivateProfile(mUserId);
+            } catch (RemoteException re) {
+                throw re.rethrowFromSystemServer();
+            }
+        }
+        return true;
+    }
+
+    /**
      * Returns whether the context user has at least one restricted profile associated with it.
      * @return whether the user has a restricted profile associated with it
      * @hide
diff --git a/core/java/android/os/vibrator/VibrationConfig.java b/core/java/android/os/vibrator/VibrationConfig.java
index a14a2c7..555a120 100644
--- a/core/java/android/os/vibrator/VibrationConfig.java
+++ b/core/java/android/os/vibrator/VibrationConfig.java
@@ -70,6 +70,8 @@
 
     private final boolean mDefaultKeyboardVibrationEnabled;
 
+    private final boolean mHasFixedKeyboardAmplitude;
+
     /** @hide */
     public VibrationConfig(@Nullable Resources resources) {
         mHapticChannelMaxVibrationAmplitude = loadFloat(resources,
@@ -87,6 +89,8 @@
                 com.android.internal.R.bool.config_ignoreVibrationsOnWirelessCharger, false);
         mDefaultKeyboardVibrationEnabled = loadBoolean(resources,
                 com.android.internal.R.bool.config_defaultKeyboardVibrationEnabled, true);
+        mHasFixedKeyboardAmplitude = loadFloat(resources,
+                com.android.internal.R.dimen.config_keyboardHapticFeedbackFixedAmplitude, -1) > 0;
 
         mDefaultAlarmVibrationIntensity = loadDefaultIntensity(resources,
                 com.android.internal.R.integer.config_defaultAlarmVibrationIntensity);
@@ -197,6 +201,14 @@
         return mDefaultKeyboardVibrationEnabled;
     }
 
+    /**
+     * Whether the device has a fixed amplitude for keyboard.
+     * @hide
+     */
+    public boolean hasFixedKeyboardAmplitude() {
+        return mHasFixedKeyboardAmplitude;
+    }
+
     /** Get the default vibration intensity for given usage. */
     @VibrationIntensity
     public int getDefaultVibrationIntensity(@VibrationAttributes.Usage int usage) {
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 410f510..3c7692d 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -1800,7 +1800,11 @@
      */
     @SystemApi
     @NonNull
-    @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS)
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+            android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
+            android.Manifest.permission.GET_RUNTIME_PERMISSIONS
+    })
     @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
     public Map<String, PermissionState> getAllPermissionStates(@NonNull String packageName,
             @NonNull String persistentDeviceId) {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index eea6464..e26dc73 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3583,6 +3583,12 @@
                                                     + " and user:" + userHandle
                                                     + " with index:" + index);
                                         }
+                                        // Always make sure to close any pre-existing tracker before
+                                        // replacing it, to prevent memory leaks
+                                        var oldTracker = mGenerationTrackers.get(name);
+                                        if (oldTracker != null) {
+                                            oldTracker.destroy();
+                                        }
                                         mGenerationTrackers.put(name, new GenerationTracker(name,
                                                 array, index, generation,
                                                 mGenerationTrackerErrorHandler));
@@ -3805,6 +3811,12 @@
                                         + " in package:" + cr.getPackageName()
                                         + " with index:" + index);
                             }
+                            // Always make sure to close any pre-existing tracker before
+                            // replacing it, to prevent memory leaks
+                            var oldTracker = mGenerationTrackers.get(prefix);
+                            if (oldTracker != null) {
+                                oldTracker.destroy();
+                            }
                             mGenerationTrackers.put(prefix,
                                     new GenerationTracker(prefix, array, index, generation,
                                             mGenerationTrackerErrorHandler));
@@ -19992,6 +20004,13 @@
             @Readable
             public static final String CONSISTENT_NOTIFICATION_BLOCKING_ENABLED =
                     "consistent_notification_blocking_enabled";
+
+            /**
+             * Whether the Auto Bedtime Mode experience is enabled.
+             *
+             * @hide
+             */
+            public static final String AUTO_BEDTIME_MODE = "auto_bedtime_mode";
         }
     }
 
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 7658af5..bd9ab86 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -22,6 +22,7 @@
 import android.annotation.Nullable;
 import android.annotation.SdkConstant;
 import android.annotation.SystemApi;
+import android.annotation.UiThread;
 import android.app.ActivityManager;
 import android.app.INotificationManager;
 import android.app.Notification;
@@ -468,6 +469,7 @@
      *            object as well as its identifying information (tag and id) and source
      *            (package name).
      */
+    @UiThread
     public void onNotificationPosted(StatusBarNotification sbn) {
         // optional
     }
@@ -481,6 +483,7 @@
      * @param rankingMap The current ranking map that can be used to retrieve ranking information
      *                   for active notifications, including the newly posted one.
      */
+    @UiThread
     public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
         onNotificationPosted(sbn);
     }
@@ -499,6 +502,7 @@
      *            and source (package name) used to post the {@link android.app.Notification} that
      *            was just removed.
      */
+    @UiThread
     public void onNotificationRemoved(StatusBarNotification sbn) {
         // optional
     }
@@ -520,6 +524,7 @@
      *                   for active notifications.
      *
      */
+    @UiThread
     public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) {
         onNotificationRemoved(sbn);
     }
@@ -541,6 +546,7 @@
      * @param rankingMap The current ranking map that can be used to retrieve ranking information
      *                   for active notifications.
      */
+    @UiThread
     public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap,
             @NotificationCancelReason int reason) {
         onNotificationRemoved(sbn, rankingMap);
@@ -552,6 +558,7 @@
      *
      * @hide
      */
+    @UiThread
     @SystemApi
     public void onNotificationRemoved(@NonNull StatusBarNotification sbn,
             @NonNull RankingMap rankingMap, @NonNull NotificationStats stats, int reason) {
@@ -563,6 +570,7 @@
      * the notification manager.  You are safe to call {@link #getActiveNotifications()}
      * at this time.
      */
+    @UiThread
     public void onListenerConnected() {
         // optional
     }
@@ -572,6 +580,7 @@
      * notification manager.You will not receive any events after this call, and may only
      * call {@link #requestRebind(ComponentName)} at this time.
      */
+    @UiThread
     public void onListenerDisconnected() {
         // optional
     }
@@ -582,6 +591,7 @@
      * @param rankingMap The current ranking map that can be used to retrieve ranking information
      *                   for active notifications.
      */
+    @UiThread
     public void onNotificationRankingUpdate(RankingMap rankingMap) {
         // optional
     }
@@ -592,6 +602,7 @@
      *
      * @param hints The current {@link #getCurrentListenerHints() listener hints}.
      */
+    @UiThread
     public void onListenerHintsChanged(int hints) {
         // optional
     }
@@ -603,6 +614,7 @@
      * @param hideSilentStatusIcons whether or not status bar icons should be hidden for silent
      *                              notifications
      */
+    @UiThread
     public void onSilentStatusBarIconsVisibilityChanged(boolean hideSilentStatusIcons) {
         // optional
     }
@@ -620,6 +632,7 @@
      *                   {@link #NOTIFICATION_CHANNEL_OR_GROUP_UPDATED},
      *                   {@link #NOTIFICATION_CHANNEL_OR_GROUP_DELETED}.
      */
+    @UiThread
     public void onNotificationChannelModified(String pkg, UserHandle user,
             NotificationChannel channel, @ChannelOrGroupModificationTypes int modificationType) {
         // optional
@@ -638,6 +651,7 @@
      *                   {@link #NOTIFICATION_CHANNEL_OR_GROUP_UPDATED},
      *                   {@link #NOTIFICATION_CHANNEL_OR_GROUP_DELETED}.
      */
+    @UiThread
     public void onNotificationChannelGroupModified(String pkg, UserHandle user,
             NotificationChannelGroup group, @ChannelOrGroupModificationTypes int modificationType) {
         // optional
@@ -650,6 +664,7 @@
      * @param interruptionFilter The current
      *     {@link #getCurrentInterruptionFilter() interruption filter}.
      */
+    @UiThread
     public void onInterruptionFilterChanged(int interruptionFilter) {
         // optional
     }
@@ -1197,6 +1212,11 @@
      * <p>
      * Listen for updates using {@link #onInterruptionFilterChanged(int)}.
      *
+     * <p>Apps targeting {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above (with some
+     * exceptions, such as companion device managers) cannot modify the global interruption filter.
+     * Calling this method will instead activate or deactivate an
+     * {@link android.app.AutomaticZenRule} associated to the app.
+     *
      * <p>The service should wait for the {@link #onListenerConnected()} event
      * before performing this operation.
      *
diff --git a/core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl b/core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl
index e44c69c..6dbff71 100644
--- a/core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl
+++ b/core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl
@@ -36,11 +36,13 @@
  */
 oneway interface IOnDeviceIntelligenceService {
     void getVersion(in RemoteCallback remoteCallback);
-    void getFeature(in int featureId, in IFeatureCallback featureCallback);
-    void listFeatures(in IListFeaturesCallback listFeaturesCallback);
-    void getFeatureDetails(in Feature feature, in IFeatureDetailsCallback featureDetailsCallback);
+    void getFeature(int callerUid, int featureId, in IFeatureCallback featureCallback);
+    void listFeatures(int callerUid, in IListFeaturesCallback listFeaturesCallback);
+    void getFeatureDetails(int callerUid, in Feature feature, in IFeatureDetailsCallback featureDetailsCallback);
     void getReadOnlyFileDescriptor(in String fileName, in AndroidFuture<ParcelFileDescriptor> future);
     void getReadOnlyFeatureFileDescriptorMap(in Feature feature, in RemoteCallback remoteCallback);
-    void requestFeatureDownload(in Feature feature, in ICancellationSignal cancellationSignal, in IDownloadCallback downloadCallback);
+    void requestFeatureDownload(int callerUid, in Feature feature, in ICancellationSignal cancellationSignal, in IDownloadCallback downloadCallback);
     void registerRemoteServices(in IRemoteProcessingService remoteProcessingService);
+    void notifyInferenceServiceConnected();
+    void notifyInferenceServiceDisconnected();
 }
\ No newline at end of file
diff --git a/core/java/android/service/ondeviceintelligence/IOnDeviceTrustedInferenceService.aidl b/core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl
similarity index 74%
rename from core/java/android/service/ondeviceintelligence/IOnDeviceTrustedInferenceService.aidl
rename to core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl
index e3fda04..73257ed 100644
--- a/core/java/android/service/ondeviceintelligence/IOnDeviceTrustedInferenceService.aidl
+++ b/core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl
@@ -18,7 +18,7 @@
 
 import android.app.ondeviceintelligence.IStreamingResponseCallback;
 import android.app.ondeviceintelligence.IResponseCallback;
-import android.app.ondeviceintelligence.ITokenCountCallback;
+import android.app.ondeviceintelligence.ITokenInfoCallback;
 import android.app.ondeviceintelligence.IProcessingSignal;
 import android.app.ondeviceintelligence.Content;
 import android.app.ondeviceintelligence.Feature;
@@ -29,18 +29,18 @@
 import android.service.ondeviceintelligence.IProcessingUpdateStatusCallback;
 
 /**
- * Interface for a concrete implementation to provide on device trusted inference.
+ * Interface for a concrete implementation to provide on-device sandboxed inference.
  *
  * @hide
  */
-oneway interface IOnDeviceTrustedInferenceService {
+oneway interface IOnDeviceSandboxedInferenceService {
     void registerRemoteStorageService(in IRemoteStorageService storageService);
-    void requestTokenCount(in Feature feature, in Content request, in ICancellationSignal cancellationSignal,
-                            in ITokenCountCallback tokenCountCallback);
-    void processRequest(in Feature feature, in Content request, in int requestType,
+    void requestTokenInfo(int callerUid, in Feature feature, in Content request, in ICancellationSignal cancellationSignal,
+                            in ITokenInfoCallback tokenInfoCallback);
+    void processRequest(int callerUid, in Feature feature, in Content request, in int requestType,
                         in ICancellationSignal cancellationSignal, in IProcessingSignal processingSignal,
                         in IResponseCallback callback);
-    void processRequestStreaming(in Feature feature, in Content request, in int requestType,
+    void processRequestStreaming(int callerUid, in Feature feature, in Content request, in int requestType,
                                 in ICancellationSignal cancellationSignal, in IProcessingSignal processingSignal,
                                 in IStreamingResponseCallback callback);
     void updateProcessingState(in Bundle processingState,
diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java b/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
index 46ba25d..fce3689 100644
--- a/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
+++ b/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
@@ -65,7 +65,7 @@
 
 /**
  * Abstract base class for performing setup for on-device inference and providing file access to
- * the isolated counter part {@link OnDeviceTrustedInferenceService}.
+ * the isolated counter part {@link OnDeviceSandboxedInferenceService}.
  *
  * <p> A service that provides configuration and model files relevant to performing inference on
  * device. The system's default OnDeviceIntelligenceService implementation is configured in
@@ -110,6 +110,8 @@
     @Override
     public final IBinder onBind(@NonNull Intent intent) {
         if (SERVICE_INTERFACE.equals(intent.getAction())) {
+            // TODO(326052028) : Move the remote method calls to an app handler from the binder
+            //  thread.
             return new IOnDeviceIntelligenceService.Stub() {
                 /** {@inheritDoc} */
                 @Override
@@ -123,38 +125,40 @@
                 }
 
                 @Override
-                public void listFeatures(IListFeaturesCallback listFeaturesCallback) {
+                public void listFeatures(int callerUid,
+                        IListFeaturesCallback listFeaturesCallback) {
                     Objects.requireNonNull(listFeaturesCallback);
-                    OnDeviceIntelligenceService.this.onListFeatures(
+                    OnDeviceIntelligenceService.this.onListFeatures(callerUid,
                             wrapListFeaturesCallback(listFeaturesCallback));
                 }
 
                 @Override
-                public void getFeature(int id, IFeatureCallback featureCallback) {
+                public void getFeature(int callerUid, int id, IFeatureCallback featureCallback) {
                     Objects.requireNonNull(featureCallback);
-                    OnDeviceIntelligenceService.this.onGetFeature(id,
-                            wrapFeatureCallback(featureCallback));
+                    OnDeviceIntelligenceService.this.onGetFeature(callerUid,
+                            id, wrapFeatureCallback(featureCallback));
                 }
 
 
                 @Override
-                public void getFeatureDetails(Feature feature,
+                public void getFeatureDetails(int callerUid, Feature feature,
                         IFeatureDetailsCallback featureDetailsCallback) {
                     Objects.requireNonNull(feature);
                     Objects.requireNonNull(featureDetailsCallback);
 
-                    OnDeviceIntelligenceService.this.onGetFeatureDetails(feature,
-                            wrapFeatureDetailsCallback(featureDetailsCallback));
+                    OnDeviceIntelligenceService.this.onGetFeatureDetails(callerUid,
+                            feature, wrapFeatureDetailsCallback(featureDetailsCallback));
                 }
 
                 @Override
-                public void requestFeatureDownload(Feature feature,
+                public void requestFeatureDownload(int callerUid, Feature feature,
                         ICancellationSignal cancellationSignal,
                         IDownloadCallback downloadCallback) {
                     Objects.requireNonNull(feature);
                     Objects.requireNonNull(downloadCallback);
 
-                    OnDeviceIntelligenceService.this.onDownloadFeature(feature,
+                    OnDeviceIntelligenceService.this.onDownloadFeature(callerUid,
+                            feature,
                             CancellationSignal.fromTransport(cancellationSignal),
                             wrapDownloadCallback(downloadCallback));
                 }
@@ -188,12 +192,38 @@
                         IRemoteProcessingService remoteProcessingService) {
                     mRemoteProcessingService = remoteProcessingService;
                 }
+
+                @Override
+                public void notifyInferenceServiceConnected() {
+                    OnDeviceIntelligenceService.this.onInferenceServiceConnected();
+                }
+
+                @Override
+                public void notifyInferenceServiceDisconnected() {
+                    OnDeviceIntelligenceService.this.onInferenceServiceDisconnected();
+                }
             };
         }
         Slog.w(TAG, "Incorrect service interface, returning null.");
         return null;
     }
 
+
+    /**
+     * Invoked when a new instance of the remote inference service is created.
+     * This method should be used as a signal to perform any initialization operations, for e.g. by
+     * invoking the {@link #updateProcessingState} method to initialize the remote processing
+     * service.
+     */
+    public abstract void onInferenceServiceConnected();
+
+
+    /**
+     * Invoked when an instance of the remote inference service is disconnected.
+     */
+    public abstract void onInferenceServiceDisconnected();
+
+
     /**
      * Invoked by the {@link OnDeviceIntelligenceService} inorder to send updates to the inference
      * service if there is a state change to be performed.
@@ -391,6 +421,7 @@
      * Request download for feature that is requested and listen to download progress updates. If
      * the download completes successfully, success callback should be populated.
      *
+     * @param callerUid          UID of the caller that initiated this call chain.
      * @param feature            the feature for which files need to be downlaoded.
      *                           process.
      * @param cancellationSignal signal to attach a listener to, and receive cancellation signals
@@ -398,7 +429,7 @@
      * @param downloadCallback   callback to populate download updates for clients to listen on..
      */
     public abstract void onDownloadFeature(
-            @NonNull Feature feature,
+            int callerUid, @NonNull Feature feature,
             @Nullable CancellationSignal cancellationSignal,
             @NonNull DownloadCallback downloadCallback);
 
@@ -407,20 +438,22 @@
      * implementation use the {@link Feature#getFeatureParams()} as a hint to communicate what
      * details the client is looking for.
      *
-     * @param feature               the feature for which status needs to be known.
-     * @param featureStatusCallback callback to populate the resulting feature status.
+     * @param callerUid              UID of the caller that initiated this call chain.
+     * @param feature                the feature for which status needs to be known.
+     * @param featureDetailsCallback callback to populate the resulting feature status.
      */
-    public abstract void onGetFeatureDetails(@NonNull Feature feature,
+    public abstract void onGetFeatureDetails(int callerUid, @NonNull Feature feature,
             @NonNull OutcomeReceiver<FeatureDetails,
-                    OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> featureStatusCallback);
+                    OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> featureDetailsCallback);
 
 
     /**
      * Get feature using the provided identifier to the remote implementation.
      *
+     * @param callerUid       UID of the caller that initiated this call chain.
      * @param featureCallback callback to populate the features list.
      */
-    public abstract void onGetFeature(int featureId,
+    public abstract void onGetFeature(int callerUid, int featureId,
             @NonNull OutcomeReceiver<Feature,
                     OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> featureCallback);
 
@@ -428,9 +461,10 @@
      * List all features which are available in the remote implementation. The implementation might
      * choose to provide only a certain list of features based on the caller.
      *
+     * @param callerUid            UID of the caller that initiated this call chain.
      * @param listFeaturesCallback callback to populate the features list.
      */
-    public abstract void onListFeatures(@NonNull OutcomeReceiver<List<Feature>,
+    public abstract void onListFeatures(int callerUid, @NonNull OutcomeReceiver<List<Feature>,
             OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> listFeaturesCallback);
 
     /**
diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceTrustedInferenceService.java b/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
similarity index 73%
rename from core/java/android/service/ondeviceintelligence/OnDeviceTrustedInferenceService.java
rename to core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
index 8600197..7f7f9c2 100644
--- a/core/java/android/service/ondeviceintelligence/OnDeviceTrustedInferenceService.java
+++ b/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
@@ -16,6 +16,7 @@
 
 package android.service.ondeviceintelligence;
 
+import static android.app.ondeviceintelligence.OnDeviceIntelligenceManager.AUGMENT_REQUEST_CONTENT_BUNDLE_KEY;
 import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
 
 import android.annotation.CallbackExecutor;
@@ -23,6 +24,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SdkConstant;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.app.Service;
 import android.app.ondeviceintelligence.Content;
@@ -30,14 +32,18 @@
 import android.app.ondeviceintelligence.IProcessingSignal;
 import android.app.ondeviceintelligence.IResponseCallback;
 import android.app.ondeviceintelligence.IStreamingResponseCallback;
-import android.app.ondeviceintelligence.ITokenCountCallback;
+import android.app.ondeviceintelligence.ITokenInfoCallback;
 import android.app.ondeviceintelligence.OnDeviceIntelligenceManager;
 import android.app.ondeviceintelligence.ProcessingSignal;
-import android.app.ondeviceintelligence.StreamingResponseReceiver;
+import android.app.ondeviceintelligence.ProcessingOutcomeReceiver;
+import android.app.ondeviceintelligence.StreamedProcessingOutcomeReceiver;
+import android.app.ondeviceintelligence.TokenInfo;
 import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
 import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.IBinder;
 import android.os.ICancellationSignal;
 import android.os.OutcomeReceiver;
@@ -75,8 +81,8 @@
  *
  * <pre>
  * {@literal
- * <service android:name=".SampleTrustedInferenceService"
- *          android:permission="android.permission.BIND_ONDEVICE_TRUSTED_INFERENCE_SERVICE"
+ * <service android:name=".SampleSandboxedInferenceService"
+ *          android:permission="android.permission.BIND_ONDEVICE_SANDBOXED_INFERENCE_SERVICE"
  *          android:isolatedProcess="true">
  * </service>}
  * </pre>
@@ -85,18 +91,18 @@
  */
 @SystemApi
 @FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
-public abstract class OnDeviceTrustedInferenceService extends Service {
-    private static final String TAG = OnDeviceTrustedInferenceService.class.getSimpleName();
+public abstract class OnDeviceSandboxedInferenceService extends Service {
+    private static final String TAG = OnDeviceSandboxedInferenceService.class.getSimpleName();
 
     /**
      * The {@link Intent} that must be declared as handled by the service. To be supported, the
      * service must also require the
-     * {@link android.Manifest.permission#BIND_ON_DEVICE_TRUSTED_INFERENCE_SERVICE}
+     * {@link android.Manifest.permission#BIND_ON_DEVICE_SANDBOXED_INFERENCE_SERVICE}
      * permission so that other applications can not abuse it.
      */
     @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
     public static final String SERVICE_INTERFACE =
-            "android.service.ondeviceintelligence.OnDeviceTrustedInferenceService";
+            "android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService";
 
     private IRemoteStorageService mRemoteStorageService;
 
@@ -107,7 +113,7 @@
     @Override
     public final IBinder onBind(@NonNull Intent intent) {
         if (SERVICE_INTERFACE.equals(intent.getAction())) {
-            return new IOnDeviceTrustedInferenceService.Stub() {
+            return new IOnDeviceSandboxedInferenceService.Stub() {
                 @Override
                 public void registerRemoteStorageService(IRemoteStorageService storageService) {
                     Objects.requireNonNull(storageService);
@@ -115,50 +121,48 @@
                 }
 
                 @Override
-                public void requestTokenCount(Feature feature, Content request,
+                public void requestTokenInfo(int callerUid, Feature feature, Content request,
                         ICancellationSignal cancellationSignal,
-                        ITokenCountCallback tokenCountCallback) {
+                        ITokenInfoCallback tokenInfoCallback) {
                     Objects.requireNonNull(feature);
-                    Objects.requireNonNull(tokenCountCallback);
-                    OnDeviceTrustedInferenceService.this.onCountTokens(feature,
+                    Objects.requireNonNull(tokenInfoCallback);
+                    OnDeviceSandboxedInferenceService.this.onTokenInfoRequest(callerUid,
+                            feature,
                             request,
                             CancellationSignal.fromTransport(cancellationSignal),
-                            wrapTokenCountCallback(tokenCountCallback));
+                            wrapTokenInfoCallback(tokenInfoCallback));
                 }
 
                 @Override
-                public void processRequestStreaming(Feature feature, Content request,
+                public void processRequestStreaming(int callerUid, Feature feature, Content request,
                         int requestType, ICancellationSignal cancellationSignal,
                         IProcessingSignal processingSignal,
                         IStreamingResponseCallback callback) {
                     Objects.requireNonNull(feature);
-                    Objects.requireNonNull(request);
                     Objects.requireNonNull(callback);
 
-                    OnDeviceTrustedInferenceService.this.onProcessRequestStreaming(feature,
+                    OnDeviceSandboxedInferenceService.this.onProcessRequestStreaming(callerUid,
+                            feature,
                             request,
                             requestType,
                             CancellationSignal.fromTransport(cancellationSignal),
                             ProcessingSignal.fromTransport(processingSignal),
-                            wrapStreamingResponseCallback(callback)
-                    );
+                            wrapStreamingResponseCallback(callback));
                 }
 
                 @Override
-                public void processRequest(Feature feature, Content request,
+                public void processRequest(int callerUid, Feature feature, Content request,
                         int requestType, ICancellationSignal cancellationSignal,
                         IProcessingSignal processingSignal,
                         IResponseCallback callback) {
                     Objects.requireNonNull(feature);
-                    Objects.requireNonNull(request);
                     Objects.requireNonNull(callback);
 
-
-                    OnDeviceTrustedInferenceService.this.onProcessRequest(feature, request,
-                            requestType, CancellationSignal.fromTransport(cancellationSignal),
+                    OnDeviceSandboxedInferenceService.this.onProcessRequest(callerUid, feature,
+                            request, requestType,
+                            CancellationSignal.fromTransport(cancellationSignal),
                             ProcessingSignal.fromTransport(processingSignal),
-                            wrapResponseCallback(callback)
-                    );
+                            wrapResponseCallback(callback));
                 }
 
                 @Override
@@ -167,7 +171,7 @@
                     Objects.requireNonNull(processingState);
                     Objects.requireNonNull(callback);
 
-                    OnDeviceTrustedInferenceService.this.onUpdateProcessingState(processingState,
+                    OnDeviceSandboxedInferenceService.this.onUpdateProcessingState(processingState,
                             wrapOutcomeReceiver(callback)
                     );
                 }
@@ -178,35 +182,37 @@
     }
 
     /**
-     * Invoked when caller  wants to obtain a count of number of tokens present in the passed in
-     * Request associated with the provided feature.
+     * Invoked when caller  wants to obtain token info related to the payload in the passed
+     * content, associated with the provided feature.
      * The expectation from the implementation is that when processing is complete, it
-     * should provide the token count in the {@link OutcomeReceiver#onResult}.
+     * should provide the token info in the {@link OutcomeReceiver#onResult}.
      *
+     * @param callerUid          UID of the caller that initiated this call chain.
      * @param feature            feature which is associated with the request.
      * @param request            request that requires processing.
      * @param cancellationSignal Cancellation Signal to receive cancellation events from client and
      *                           configure a listener to.
-     * @param callback           callback to populate failure and full response for the provided
+     * @param callback           callback to populate failure or the token info for the provided
      *                           request.
      */
     @NonNull
-    public abstract void onCountTokens(
-            @NonNull Feature feature,
+    public abstract void onTokenInfoRequest(
+            int callerUid, @NonNull Feature feature,
             @NonNull Content request,
             @Nullable CancellationSignal cancellationSignal,
-            @NonNull OutcomeReceiver<Long,
+            @NonNull OutcomeReceiver<TokenInfo,
                     OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> callback);
 
     /**
      * Invoked when caller provides a request for a particular feature to be processed in a
      * streaming manner. The expectation from the implementation is that when processing the
      * request,
-     * it periodically populates the {@link StreamingResponseReceiver#onNewContent} to continuously
+     * it periodically populates the {@link StreamedProcessingOutcomeReceiver#onNewContent} to continuously
      * provide partial Content results for the caller to utilize. Optionally the implementation can
-     * provide the complete response in the {@link StreamingResponseReceiver#onResult} upon
+     * provide the complete response in the {@link StreamedProcessingOutcomeReceiver#onResult} upon
      * processing completion.
      *
+     * @param callerUid          UID of the caller that initiated this call chain.
      * @param feature            feature which is associated with the request.
      * @param request            request that requires processing.
      * @param requestType        identifier representing the type of request.
@@ -218,13 +224,12 @@
      */
     @NonNull
     public abstract void onProcessRequestStreaming(
-            @NonNull Feature feature,
-            @NonNull Content request,
+            int callerUid, @NonNull Feature feature,
+            @Nullable Content request,
             @OnDeviceIntelligenceManager.RequestType int requestType,
             @Nullable CancellationSignal cancellationSignal,
             @Nullable ProcessingSignal processingSignal,
-            @NonNull StreamingResponseReceiver<Content, Content,
-                    OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> callback);
+            @NonNull StreamedProcessingOutcomeReceiver callback);
 
     /**
      * Invoked when caller provides a request for a particular feature to be processed in one shot
@@ -233,6 +238,7 @@
      * should
      * provide the complete response in the {@link OutcomeReceiver#onResult}.
      *
+     * @param callerUid          UID of the caller that initiated this call chain.
      * @param feature            feature which is associated with the request.
      * @param request            request that requires processing.
      * @param requestType        identifier representing the type of request.
@@ -244,13 +250,12 @@
      */
     @NonNull
     public abstract void onProcessRequest(
-            @NonNull Feature feature,
-            @NonNull Content request,
+            int callerUid, @NonNull Feature feature,
+            @Nullable Content request,
             @OnDeviceIntelligenceManager.RequestType int requestType,
             @Nullable CancellationSignal cancellationSignal,
             @Nullable ProcessingSignal processingSignal,
-            @NonNull OutcomeReceiver<Content,
-                    OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> callback);
+            @NonNull ProcessingOutcomeReceiver callback);
 
 
     /**
@@ -335,6 +340,26 @@
         }
     }
 
+
+    /**
+     * Returns the {@link Executor} to use for incoming IPC from request sender into your service
+     * implementation. For e.g. see
+     * {@link ProcessingOutcomeReceiver#onDataAugmentRequest(Content,
+     * Consumer)} where we use the executor to populate the consumer.
+     * <p>
+     * Override this method in your {@link OnDeviceSandboxedInferenceService} implementation to
+     * provide the executor you want to use for incoming IPC.
+     *
+     * @return the {@link Executor} to use for incoming IPC from {@link OnDeviceIntelligenceManager}
+     * to {@link OnDeviceSandboxedInferenceService}.
+     */
+    @SuppressLint("OnNameExpected")
+    @NonNull
+    public Executor getCallbackExecutor() {
+        return new HandlerExecutor(Handler.createAsync(getMainLooper()));
+    }
+
+
     private RemoteCallback wrapResultReceiverAsReadOnly(
             @NonNull Consumer<Map<String, FileInputStream>> resultConsumer,
             @NonNull Executor executor) {
@@ -355,10 +380,9 @@
         });
     }
 
-    private OutcomeReceiver<Content,
-            OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> wrapResponseCallback(
+    private ProcessingOutcomeReceiver wrapResponseCallback(
             IResponseCallback callback) {
-        return new OutcomeReceiver<>() {
+        return new ProcessingOutcomeReceiver() {
             @Override
             public void onResult(@androidx.annotation.NonNull Content response) {
                 try {
@@ -378,13 +402,23 @@
                     Slog.e(TAG, "Error sending result: " + e);
                 }
             }
+
+            @Override
+            public void onDataAugmentRequest(@NonNull Content content,
+                    @NonNull Consumer<Content> contentCallback) {
+                try {
+                    callback.onDataAugmentRequest(content, wrapRemoteCallback(contentCallback));
+
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Error sending augment request: " + e);
+                }
+            }
         };
     }
 
-    private StreamingResponseReceiver<Content, Content,
-            OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> wrapStreamingResponseCallback(
+    private StreamedProcessingOutcomeReceiver wrapStreamingResponseCallback(
             IStreamingResponseCallback callback) {
-        return new StreamingResponseReceiver<>() {
+        return new StreamedProcessingOutcomeReceiver() {
             @Override
             public void onNewContent(@androidx.annotation.NonNull Content content) {
                 try {
@@ -413,17 +447,43 @@
                     Slog.e(TAG, "Error sending result: " + e);
                 }
             }
+
+            @Override
+            public void onDataAugmentRequest(@NonNull Content content,
+                    @NonNull Consumer<Content> contentCallback) {
+                try {
+                    callback.onDataAugmentRequest(content, wrapRemoteCallback(contentCallback));
+
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Error sending augment request: " + e);
+                }
+            }
         };
     }
 
-    private OutcomeReceiver<Long,
-            OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> wrapTokenCountCallback(
-            ITokenCountCallback tokenCountCallback) {
+    private RemoteCallback wrapRemoteCallback(
+            @androidx.annotation.NonNull Consumer<Content> contentCallback) {
+        return new RemoteCallback(
+                result -> {
+                    if (result != null) {
+                        getCallbackExecutor().execute(() -> contentCallback.accept(
+                                result.getParcelable(AUGMENT_REQUEST_CONTENT_BUNDLE_KEY,
+                                        Content.class)));
+                    } else {
+                        getCallbackExecutor().execute(
+                                () -> contentCallback.accept(null));
+                    }
+                });
+    }
+
+    private OutcomeReceiver<TokenInfo,
+            OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> wrapTokenInfoCallback(
+            ITokenInfoCallback tokenInfoCallback) {
         return new OutcomeReceiver<>() {
             @Override
-            public void onResult(Long tokenCount) {
+            public void onResult(TokenInfo tokenInfo) {
                 try {
-                    tokenCountCallback.onSuccess(tokenCount);
+                    tokenInfoCallback.onSuccess(tokenInfo);
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Error sending result: " + e);
                 }
@@ -433,7 +493,7 @@
             public void onError(
                     OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException exception) {
                 try {
-                    tokenCountCallback.onFailure(exception.getErrorCode(), exception.getMessage(),
+                    tokenInfoCallback.onFailure(exception.getErrorCode(), exception.getMessage(),
                             exception.getErrorParams());
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Error sending failure: " + e);
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 67a3978..0ae3e59 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -1775,9 +1775,9 @@
     @CriticalNative
     private static native void nativeOffsetLocation(long nativePtr, float deltaX, float deltaY);
     @CriticalNative
-    private static native float nativeGetXOffset(long nativePtr);
+    private static native float nativeGetRawXOffset(long nativePtr);
     @CriticalNative
-    private static native float nativeGetYOffset(long nativePtr);
+    private static native float nativeGetRawYOffset(long nativePtr);
     @CriticalNative
     private static native float nativeGetXPrecision(long nativePtr);
     @CriticalNative
@@ -3745,7 +3745,7 @@
                     nativeGetAction(mNativePtr), nativeGetFlags(mNativePtr),
                     nativeGetEdgeFlags(mNativePtr), nativeGetMetaState(mNativePtr),
                     nativeGetButtonState(mNativePtr), nativeGetClassification(mNativePtr),
-                    nativeGetXOffset(mNativePtr), nativeGetYOffset(mNativePtr),
+                    nativeGetRawXOffset(mNativePtr), nativeGetRawYOffset(mNativePtr),
                     nativeGetXPrecision(mNativePtr), nativeGetYPrecision(mNativePtr),
                     nativeGetDownTimeNanos(mNativePtr),
                     nativeGetEventTimeNanos(mNativePtr, HISTORY_CURRENT),
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index d29963c..a6f380d 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -33760,7 +33760,7 @@
     @FlaggedApi(FLAG_VIEW_VELOCITY_API)
     public float getFrameContentVelocity() {
         if (viewVelocityApi()) {
-            return mFrameContentVelocity;
+            return (mFrameContentVelocity < 0f) ? 0f : mFrameContentVelocity;
         }
         return 0;
     }
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index 131fca7..1efd375 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -315,7 +315,8 @@
     /**
      * Add to this view's child count.  This increases the current child count by
      * <var>num</var> children beyond what was last set by {@link #setChildCount}
-     * or {@link #addChildCount}.  The index at which the new child starts in the child
+     * or {@link #addChildCount}.  The index at which the new
+     * child starts in the child
      * array is returned.
      *
      * @param num The number of new children to add.
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index fe4ac4e..a2d8d80 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -4299,7 +4299,7 @@
         }
 
         if (mode == MODE_NORMAL) {
-            mApplication = ApplicationInfo.CREATOR.createFromParcel(parcel);
+            mApplication = parcel.readTypedObject(ApplicationInfo.CREATOR);
             mIdealSize = parcel.readInt() == 0 ? null : SizeF.CREATOR.createFromParcel(parcel);
             mLayoutId = parcel.readInt();
             mViewId = parcel.readInt();
@@ -6805,7 +6805,7 @@
                 mBitmapCache.writeBitmapsToParcel(dest, flags);
                 mCollectionCache.writeToParcel(dest, flags, intentsToIgnore);
             }
-            mApplication.writeToParcel(dest, flags);
+            dest.writeTypedObject(mApplication, flags);
             if (mIsRoot || mIdealSize == null) {
                 dest.writeInt(0);
             } else {
@@ -6893,7 +6893,8 @@
      * @hide
      */
     public boolean hasSameAppInfo(ApplicationInfo info) {
-        return mApplication.packageName.equals(info.packageName) && mApplication.uid == info.uid;
+        return mApplication == null || mApplication.packageName.equals(info.packageName)
+                && mApplication.uid == info.uid;
     }
 
     /**
@@ -7672,8 +7673,7 @@
             byte[] instruction;
             final List<byte[]> instructions = new ArrayList<>(size);
             for (int i = 0; i < size; i++) {
-                instruction = new byte[in.readInt()];
-                in.readByteArray(instruction);
+                instruction = in.readBlob();
                 instructions.add(instruction);
             }
             return new DrawInstructions(instructions);
@@ -7688,8 +7688,7 @@
             final List<byte[]> instructions = drawInstructions.mInstructions;
             dest.writeInt(instructions.size());
             for (byte[] instruction : instructions) {
-                dest.writeInt(instruction.length);
-                dest.writeByteArray(instruction);
+                dest.writeBlob(instruction);
             }
         }
 
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 7dcbbea..78f06b6 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -2655,7 +2655,8 @@
 
     private boolean privateSpaceEnabled() {
         return mIsIntentPicker && android.os.Flags.allowPrivateProfile()
-                && android.multiuser.Flags.allowResolverSheetForPrivateSpace();
+                && android.multiuser.Flags.allowResolverSheetForPrivateSpace()
+                && android.multiuser.Flags.enablePrivateSpaceFeatures();
     }
 
     /**
diff --git a/core/java/com/android/internal/app/SetScreenLockDialogActivity.java b/core/java/com/android/internal/app/SetScreenLockDialogActivity.java
index 93fe37c..360fcaf 100644
--- a/core/java/com/android/internal/app/SetScreenLockDialogActivity.java
+++ b/core/java/com/android/internal/app/SetScreenLockDialogActivity.java
@@ -75,7 +75,8 @@
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         if (!(android.os.Flags.allowPrivateProfile()
-                && android.multiuser.Flags.showSetScreenLockDialog())) {
+                && android.multiuser.Flags.showSetScreenLockDialog()
+                && android.multiuser.Flags.enablePrivateSpaceFeatures())) {
             finish();
             return;
         }
diff --git a/core/java/com/android/internal/app/UnlaunchableAppActivity.java b/core/java/com/android/internal/app/UnlaunchableAppActivity.java
index 4ef0a1b..97f8084 100644
--- a/core/java/com/android/internal/app/UnlaunchableAppActivity.java
+++ b/core/java/com/android/internal/app/UnlaunchableAppActivity.java
@@ -77,6 +77,7 @@
         }
 
         if (android.os.Flags.allowPrivateProfile()
+                && android.multiuser.Flags.enablePrivateSpaceFeatures()
                 && !userManager.isManagedProfile(mUserId)) {
             Log.e(TAG, "Unlaunchable activity for target package " + targetPackageName
                     + " called for a non-managed-profile " + mUserId);
diff --git a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
index 85bdbb9..e55cdef 100644
--- a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
+++ b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
@@ -80,11 +80,10 @@
             in Bundle extras, in IntentSender resultIntent);
     boolean isRequestPinAppWidgetSupported();
     oneway void noteAppWidgetTapped(in String callingPackage, in int appWidgetId);
-    void setWidgetPreview(in ComponentName providerComponent, in int widgetCategories,
+    boolean setWidgetPreview(in ComponentName providerComponent, in int widgetCategories,
             in RemoteViews preview);
     @nullable RemoteViews getWidgetPreview(in String callingPackage,
             in ComponentName providerComponent, in int profileId, in int widgetCategory);
     void removeWidgetPreview(in ComponentName providerComponent, in int widgetCategories);
-
 }
 
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index bd806bf..91678c7 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -560,6 +560,19 @@
      */
     public static final String CURSOR_HOVER_STATES_ENABLED = "cursor_hover_states_enabled";
 
+
+    /*
+     * (long) The reset interval for generated preview API calls.
+     */
+    public static final String GENERATED_PREVIEW_API_RESET_INTERVAL_MS =
+            "generated_preview_api_reset_interval_ms";
+
+    /*
+     * (int) The max number of generated preview API calls per reset interval.
+     */
+    public static final String GENERATED_PREVIEW_API_MAX_CALLS_PER_INTERVAL =
+            "generated_preview_api_max_calls_per_interval";
+
     private SystemUiDeviceConfigFlags() {
     }
 }
diff --git a/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java b/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
index 30de546..2096ba4 100644
--- a/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
@@ -55,37 +55,38 @@
  * A service for the ProtoLog logging system.
  */
 public class LegacyProtoLogImpl implements IProtoLog {
-    private final TreeMap<String, IProtoLogGroup> mLogGroups = new TreeMap<>();
-
     private static final int BUFFER_CAPACITY = 1024 * 1024;
     private static final int PER_CHUNK_SIZE = 1024;
     private static final String TAG = "ProtoLog";
     private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
     static final String PROTOLOG_VERSION = "2.0.0";
-    private static final int DEFAULT_PER_CHUNK_SIZE = 0;
 
     private final File mLogFile;
     private final String mLegacyViewerConfigFilename;
     private final TraceBuffer mBuffer;
     private final LegacyProtoLogViewerConfigReader mViewerConfig;
+    private final TreeMap<String, IProtoLogGroup> mLogGroups;
     private final int mPerChunkSize;
 
     private boolean mProtoLogEnabled;
     private boolean mProtoLogEnabledLockFree;
     private final Object mProtoLogEnabledLock = new Object();
 
-    public LegacyProtoLogImpl(String outputFile, String viewerConfigFilename) {
+    public LegacyProtoLogImpl(String outputFile, String viewerConfigFilename,
+            TreeMap<String, IProtoLogGroup> logGroups) {
         this(new File(outputFile), viewerConfigFilename, BUFFER_CAPACITY,
-                new LegacyProtoLogViewerConfigReader(), PER_CHUNK_SIZE);
+                new LegacyProtoLogViewerConfigReader(), PER_CHUNK_SIZE, logGroups);
     }
 
     public LegacyProtoLogImpl(File file, String viewerConfigFilename, int bufferCapacity,
-            LegacyProtoLogViewerConfigReader viewerConfig, int perChunkSize) {
+            LegacyProtoLogViewerConfigReader viewerConfig, int perChunkSize,
+            TreeMap<String, IProtoLogGroup> logGroups) {
         mLogFile = file;
         mBuffer = new TraceBuffer(bufferCapacity);
         mLegacyViewerConfigFilename = viewerConfigFilename;
         mViewerConfig = viewerConfig;
         mPerChunkSize = perChunkSize;
+        this.mLogGroups = logGroups;
     }
 
     /**
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index 53062d8..4ead82f 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -22,9 +22,9 @@
 import static perfetto.protos.PerfettoTrace.InternedString.STR;
 import static perfetto.protos.PerfettoTrace.ProtoLogMessage.BOOLEAN_PARAMS;
 import static perfetto.protos.PerfettoTrace.ProtoLogMessage.DOUBLE_PARAMS;
-import static perfetto.protos.PerfettoTrace.ProtoLogMessage.STACKTRACE_IID;
 import static perfetto.protos.PerfettoTrace.ProtoLogMessage.MESSAGE_ID;
 import static perfetto.protos.PerfettoTrace.ProtoLogMessage.SINT64_PARAMS;
+import static perfetto.protos.PerfettoTrace.ProtoLogMessage.STACKTRACE_IID;
 import static perfetto.protos.PerfettoTrace.ProtoLogMessage.STR_PARAM_IIDS;
 import static perfetto.protos.PerfettoTrace.ProtoLogViewerConfig.GROUPS;
 import static perfetto.protos.PerfettoTrace.ProtoLogViewerConfig.Group.ID;
@@ -70,6 +70,8 @@
 import java.util.ArrayList;
 import java.util.Map;
 import java.util.TreeMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import perfetto.protos.PerfettoTrace.ProtoLogViewerConfig.MessageData;
@@ -78,7 +80,6 @@
  * A service for the ProtoLog logging system.
  */
 public class PerfettoProtoLogImpl implements IProtoLog {
-    private final TreeMap<String, IProtoLogGroup> mLogGroups = new TreeMap<>();
     private static final String LOG_TAG = "ProtoLog";
     private final AtomicInteger mTracingInstances = new AtomicInteger();
 
@@ -89,8 +90,12 @@
     );
     private final ProtoLogViewerConfigReader mViewerConfigReader;
     private final ViewerConfigInputStreamProvider mViewerConfigInputStreamProvider;
+    private final TreeMap<String, IProtoLogGroup> mLogGroups;
 
-    public PerfettoProtoLogImpl(String viewerConfigFilePath) {
+    private final ExecutorService mBackgroundLoggingService = Executors.newCachedThreadPool();
+
+    public PerfettoProtoLogImpl(String viewerConfigFilePath,
+            TreeMap<String, IProtoLogGroup> logGroups) {
         this(() -> {
             try {
                 return new ProtoInputStream(new FileInputStream(viewerConfigFilePath));
@@ -98,23 +103,28 @@
                 Slog.w(LOG_TAG, "Failed to load viewer config file " + viewerConfigFilePath, e);
                 return null;
             }
-        });
+        }, logGroups);
     }
 
-    public PerfettoProtoLogImpl(ViewerConfigInputStreamProvider viewerConfigInputStreamProvider) {
+    public PerfettoProtoLogImpl(
+            ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
+            TreeMap<String, IProtoLogGroup> logGroups
+    ) {
         this(viewerConfigInputStreamProvider,
-                new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider));
+                new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider), logGroups);
     }
 
     @VisibleForTesting
     public PerfettoProtoLogImpl(
             ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
-            ProtoLogViewerConfigReader viewerConfigReader
+            ProtoLogViewerConfigReader viewerConfigReader,
+            TreeMap<String, IProtoLogGroup> logGroups
     ) {
         Producer.init(InitArguments.DEFAULTS);
         mDataSource.register(DataSourceParams.DEFAULTS);
         this.mViewerConfigInputStreamProvider = viewerConfigInputStreamProvider;
         this.mViewerConfigReader = viewerConfigReader;
+        this.mLogGroups = logGroups;
     }
 
     /**
@@ -128,7 +138,8 @@
 
         long tsNanos = SystemClock.elapsedRealtimeNanos();
         try {
-            logToProto(level, group.name(), messageHash, paramsMask, args, tsNanos);
+            mBackgroundLoggingService.submit(() ->
+                    logToProto(level, group.name(), messageHash, paramsMask, args, tsNanos));
             if (group.isLogToLogcat()) {
                 logToLogcat(group.getTag(), level, messageHash, messageString, args);
             }
@@ -456,40 +467,6 @@
     }
 
     /**
-     * Responds to a shell command.
-     */
-    public int onShellCommand(ShellCommand shell) {
-        PrintWriter pw = shell.getOutPrintWriter();
-        String cmd = shell.getNextArg();
-        if (cmd == null) {
-            return unknownCommand(pw);
-        }
-        ArrayList<String> args = new ArrayList<>();
-        String arg;
-        while ((arg = shell.getNextArg()) != null) {
-            args.add(arg);
-        }
-        final ILogger logger = (msg) -> logAndPrintln(pw, msg);
-        String[] groups = args.toArray(new String[args.size()]);
-        switch (cmd) {
-            case "enable-text":
-                return this.startLoggingToLogcat(groups, logger);
-            case "disable-text":
-                return this.stopLoggingToLogcat(groups, logger);
-            default:
-                return unknownCommand(pw);
-        }
-    }
-
-    private int unknownCommand(PrintWriter pw) {
-        pw.println("Unknown command");
-        pw.println("Window manager logging options:");
-        pw.println("  enable-text [group...]: Enable logcat logging for given groups");
-        pw.println("  disable-text [group...]: Disable logcat logging for given groups");
-        return -1;
-    }
-
-    /**
      * Returns {@code true} iff logging to proto is enabled.
      */
     public boolean isProtoEnabled() {
@@ -548,6 +525,49 @@
         return 0;
     }
 
+    /**
+     * Responds to a shell command.
+     */
+    public int onShellCommand(ShellCommand shell) {
+        PrintWriter pw = shell.getOutPrintWriter();
+        String cmd = shell.getNextArg();
+        if (cmd == null) {
+            return unknownCommand(pw);
+        }
+        ArrayList<String> args = new ArrayList<>();
+        String arg;
+        while ((arg = shell.getNextArg()) != null) {
+            args.add(arg);
+        }
+        final ILogger logger = (msg) -> logAndPrintln(pw, msg);
+        String[] groups = args.toArray(new String[0]);
+        switch (cmd) {
+            case "start", "stop" -> {
+                pw.println("Command not supported. "
+                        + "Please start and stop ProtoLog tracing with Perfetto.");
+                return -1;
+            }
+            case "enable-text" -> {
+                mViewerConfigReader.loadViewerConfig(logger);
+                return setTextLogging(true, logger, groups);
+            }
+            case "disable-text" -> {
+                return setTextLogging(false, logger, groups);
+            }
+            default -> {
+                return unknownCommand(pw);
+            }
+        }
+    }
+
+    private int unknownCommand(PrintWriter pw) {
+        pw.println("Unknown command");
+        pw.println("Window manager logging options:");
+        pw.println("  enable-text [group...]: Enable logcat logging for given groups");
+        pw.println("  disable-text [group...]: Disable logcat logging for given groups");
+        return -1;
+    }
+
     static void logAndPrintln(@Nullable PrintWriter pw, String msg) {
         Slog.i(LOG_TAG, msg);
         if (pw != null) {
diff --git a/core/java/com/android/internal/protolog/ProtoLogImpl.java b/core/java/com/android/internal/protolog/ProtoLogImpl.java
index 8965385..487ae814 100644
--- a/core/java/com/android/internal/protolog/ProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/ProtoLogImpl.java
@@ -18,6 +18,7 @@
 
 import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.LEGACY_OUTPUT_FILE_PATH;
 import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.LEGACY_VIEWER_CONFIG_PATH;
+import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.LOG_GROUPS;
 import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.VIEWER_CONFIG_PATH;
 
 import android.annotation.Nullable;
@@ -28,6 +29,8 @@
 import com.android.internal.protolog.common.LogLevel;
 import com.android.internal.protolog.common.ProtoLogToolInjected;
 
+import java.util.TreeMap;
+
 /**
  * A service for the ProtoLog logging system.
  */
@@ -43,6 +46,9 @@
     @ProtoLogToolInjected(LEGACY_OUTPUT_FILE_PATH)
     private static String sLegacyOutputFilePath;
 
+    @ProtoLogToolInjected(LOG_GROUPS)
+    private static TreeMap<String, IProtoLogGroup> sLogGroups;
+
     /** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
     public static void d(IProtoLogGroup group, long messageHash, int paramsMask,
             @Nullable String messageString,
@@ -99,11 +105,10 @@
     public static synchronized IProtoLog getSingleInstance() {
         if (sServiceInstance == null) {
             if (android.tracing.Flags.perfettoProtologTracing()) {
-                sServiceInstance =
-                        new PerfettoProtoLogImpl(sViewerConfigPath);
+                sServiceInstance = new PerfettoProtoLogImpl(sViewerConfigPath, sLogGroups);
             } else {
-                sServiceInstance =
-                        new LegacyProtoLogImpl(sLegacyOutputFilePath, sLegacyViewerConfigPath);
+                sServiceInstance = new LegacyProtoLogImpl(
+                        sLegacyOutputFilePath, sLegacyViewerConfigPath, sLogGroups);
             }
         }
         return sServiceInstance;
diff --git a/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
index 3c206ac..ae3d448 100644
--- a/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
+++ b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
@@ -4,6 +4,7 @@
 import static perfetto.protos.PerfettoTrace.ProtoLogViewerConfig.MessageData.MESSAGE;
 import static perfetto.protos.PerfettoTrace.ProtoLogViewerConfig.MessageData.MESSAGE_ID;
 
+import android.util.ArrayMap;
 import android.util.proto.ProtoInputStream;
 
 import com.android.internal.protolog.common.ILogger;
@@ -57,6 +58,7 @@
     }
 
     private void doLoadViewerConfig() throws IOException {
+        mLogMessageMap = new ArrayMap<>();
         final ProtoInputStream pis = mViewerConfigInputStreamProvider.getInputStream();
 
         while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
diff --git a/core/java/com/android/internal/protolog/common/ProtoLogToolInjected.java b/core/java/com/android/internal/protolog/common/ProtoLogToolInjected.java
index ffd0d76..17c82d7 100644
--- a/core/java/com/android/internal/protolog/common/ProtoLogToolInjected.java
+++ b/core/java/com/android/internal/protolog/common/ProtoLogToolInjected.java
@@ -22,7 +22,9 @@
 
 @Target({ElementType.FIELD, ElementType.PARAMETER})
 public @interface ProtoLogToolInjected {
-    enum Value { VIEWER_CONFIG_PATH, LEGACY_OUTPUT_FILE_PATH, LEGACY_VIEWER_CONFIG_PATH }
+    enum Value {
+        VIEWER_CONFIG_PATH, LEGACY_OUTPUT_FILE_PATH, LEGACY_VIEWER_CONFIG_PATH, LOG_GROUPS
+    }
 
     Value value();
 }
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index 23adb8f7..1a86363 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -411,8 +411,8 @@
             jniThrowNullPointerException(env, "pointerCoords");
             return;
         }
-        pointerCoordsToNative(env, pointerCoordsObj,
-                event->getXOffset(), event->getYOffset(), &rawPointerCoords[i]);
+        pointerCoordsToNative(env, pointerCoordsObj, event->getRawXOffset(), event->getRawYOffset(),
+                              &rawPointerCoords[i]);
         env->DeleteLocalRef(pointerCoordsObj);
     }
 
@@ -735,14 +735,14 @@
     return event->offsetLocation(deltaX, deltaY);
 }
 
-static jfloat android_view_MotionEvent_nativeGetXOffset(jlong nativePtr) {
+static jfloat android_view_MotionEvent_nativeGetRawXOffset(jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
-    return event->getXOffset();
+    return event->getRawXOffset();
 }
 
-static jfloat android_view_MotionEvent_nativeGetYOffset(jlong nativePtr) {
+static jfloat android_view_MotionEvent_nativeGetRawYOffset(jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
-    return event->getYOffset();
+    return event->getRawYOffset();
 }
 
 static jfloat android_view_MotionEvent_nativeGetXPrecision(jlong nativePtr) {
@@ -871,8 +871,8 @@
         {"nativeGetClassification", "(J)I",
          (void*)android_view_MotionEvent_nativeGetClassification},
         {"nativeOffsetLocation", "(JFF)V", (void*)android_view_MotionEvent_nativeOffsetLocation},
-        {"nativeGetXOffset", "(J)F", (void*)android_view_MotionEvent_nativeGetXOffset},
-        {"nativeGetYOffset", "(J)F", (void*)android_view_MotionEvent_nativeGetYOffset},
+        {"nativeGetRawXOffset", "(J)F", (void*)android_view_MotionEvent_nativeGetRawXOffset},
+        {"nativeGetRawYOffset", "(J)F", (void*)android_view_MotionEvent_nativeGetRawYOffset},
         {"nativeGetXPrecision", "(J)F", (void*)android_view_MotionEvent_nativeGetXPrecision},
         {"nativeGetYPrecision", "(J)F", (void*)android_view_MotionEvent_nativeGetYPrecision},
         {"nativeGetXCursorPosition", "(J)F",
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 52bad21..1acdc75 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -6772,13 +6772,6 @@
     <permission android:name="android.permission.USE_BIOMETRIC_INTERNAL"
         android:protectionLevel="signature" />
 
-    <!-- Allows privileged apps to access the background face authentication.
-        @SystemApi
-        @FlaggedApi("android.hardware.biometrics.face_background_authentication")
-        @hide -->
-    <permission android:name="android.permission.USE_BACKGROUND_FACE_AUTHENTICATION"
-        android:protectionLevel="signature|privileged" />
-
     <!-- Allows the system to control the BiometricDialog (SystemUI). Reserved for the system. @hide -->
     <permission android:name="android.permission.MANAGE_BIOMETRIC_DIALOG"
                 android:protectionLevel="signature" />
@@ -7955,12 +7948,12 @@
         android:protectionLevel="signature|privileged" />
 
 
-    <!-- @SystemApi Allows an app to bind the on-device trusted service.
+    <!-- @SystemApi Allows an app to bind the on-device sandboxed service.
              <p>Protection level: signature|privileged
              @hide
          @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence")
         -->
-    <permission android:name="android.permission.BIND_ON_DEVICE_TRUSTED_SERVICE"
+    <permission android:name="android.permission.BIND_ON_DEVICE_SANDBOXED_INFERENCE_SERVICE"
         android:protectionLevel="signature"/>
 
 
diff --git a/core/res/res/values-watch/themes_device_defaults.xml b/core/res/res/values-watch/themes_device_defaults.xml
index df8158d..6e804c0 100644
--- a/core/res/res/values-watch/themes_device_defaults.xml
+++ b/core/res/res/values-watch/themes_device_defaults.xml
@@ -146,7 +146,7 @@
         <item name="windowAnimationStyle">@style/Animation.InputMethod</item>
         <item name="imeFullscreenBackground">?colorBackground</item>
         <item name="imeExtractEnterAnimation">@anim/input_method_extract_enter</item>
-        <item name="windowSwipeToDismiss">false</item>
+        <item name="windowSwipeToDismiss">true</item>
     </style>
 
     <!-- DeviceDefault theme for dialog windows and activities. In contrast to Material, the
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 4f20fce..9d902c9 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4673,8 +4673,8 @@
     <!-- The component name for the default system on-device intelligence service, -->
     <string name="config_defaultOnDeviceIntelligenceService" translatable="false"></string>
 
-    <!-- The component name for the default system on-device trusted inference service. -->
-    <string name="config_defaultOnDeviceTrustedInferenceService" translatable="false"></string>
+    <!-- The component name for the default system on-device sandboxed inference service. -->
+    <string name="config_defaultOnDeviceSandboxedInferenceService" translatable="false"></string>
 
     <!-- Component name that accepts ACTION_SEND intents for requesting ambient context consent for
          wearable sensing. -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 59066eb..238772f 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -6434,4 +6434,6 @@
     <string name="satellite_notification_open_message">Open Messages</string>
     <!-- Invoke Satellite setting activity of Settings -->
     <string name="satellite_notification_how_it_works">How it works</string>
+    <!-- Initial/System provided label shown for an app which gets unarchived. [CHAR LIMIT=64]. -->
+    <string name="unarchival_session_app_label">Pending...</string>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 150951f..4b71654 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3916,7 +3916,7 @@
   <java-symbol type="string" name="config_ambientContextEventArrayExtraKey" />
   <java-symbol type="string" name="config_defaultWearableSensingService" />
   <java-symbol type="string" name="config_defaultOnDeviceIntelligenceService" />
-  <java-symbol type="string" name="config_defaultOnDeviceTrustedInferenceService" />
+  <java-symbol type="string" name="config_defaultOnDeviceSandboxedInferenceService" />
   <java-symbol type="string" name="config_retailDemoPackage" />
   <java-symbol type="string" name="config_retailDemoPackageSignature" />
 
@@ -5373,4 +5373,6 @@
   <java-symbol type="string" name="config_defaultContextualSearchKey" />
   <java-symbol type="string" name="config_defaultContextualSearchEnabled" />
   <java-symbol type="string" name="config_defaultContextualSearchLegacyEnabled" />
+
+  <java-symbol type="string" name="unarchival_session_app_label" />
 </resources>
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
index 15bb66b..8d9fad9 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
@@ -90,7 +90,7 @@
     private static final long TEST_HD_FREQUENCY_VALUE = 95_300;
     private static final long TEST_HD_STATION_ID_EXT_VALUE = 0x100000001L
             | (TEST_HD_FREQUENCY_VALUE << 36);
-    private static final long TEST_HD_LOCATION_VALUE = 0x89CC8E06CCB9ECL;
+    private static final long TEST_HD_LOCATION_VALUE =  0x4E647007665CF6L;
     private static final long TEST_VENDOR_ID_VALUE = 9_901;
 
     private static final ProgramSelector.Identifier TEST_DAB_SID_EXT_ID =
diff --git a/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java b/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java
index 37a499a..6cc5485 100644
--- a/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java
+++ b/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java
@@ -110,8 +110,6 @@
             Paths.get("/data/misc/wmtrace/ime_trace_service.winscope"),
             Paths.get("/data/misc/wmtrace/wm_trace.winscope"),
             Paths.get("/data/misc/wmtrace/wm_log.winscope"),
-            Paths.get("/data/misc/wmtrace/wm_transition_trace.winscope"),
-            Paths.get("/data/misc/wmtrace/shell_transition_trace.winscope"),
     };
 
     private Handler mHandler;
@@ -257,6 +255,38 @@
         assertThatAllFileContentsAreDifferent(preDumpedTraceFiles, actualTraceFiles);
     }
 
+    @LargeTest
+    @Test
+    public void noPreDumpData_then_fullWithUsePreDumpFlag_ignoresFlag() throws Exception {
+        startPreDumpedUiTraces();
+
+        mBrm.preDumpUiData();
+        waitTillDumpstateExitedOrTimeout();
+
+        // Simulate lost of pre-dumped data.
+        // For example it can happen in this scenario:
+        // 1. Pre-dump data
+        // 2. Start bugreport + "use pre-dump" flag (USE AND REMOVE THE PRE-DUMP FROM DISK)
+        // 3. Start bugreport + "use pre-dump" flag (NO PRE-DUMP AVAILABLE ON DISK)
+        removeFilesIfNeeded(UI_TRACES_PREDUMPED);
+
+        // Start bugreport with "use predump" flag. Because the pre-dumped data is not available
+        // the flag will be ignored and data will be dumped as in normal flow.
+        BugreportCallbackImpl callback = new BugreportCallbackImpl();
+        mBrm.startBugreport(mBugreportFd, null, fullWithUsePreDumpFlag(), mExecutor,
+                callback);
+        shareConsentDialog(ConsentReply.ALLOW);
+        waitTillDoneOrTimeout(callback);
+
+        stopPreDumpedUiTraces();
+
+        assertThat(callback.isDone()).isTrue();
+        assertThat(mBugreportFile.length()).isGreaterThan(0L);
+        assertFdsAreClosed(mBugreportFd);
+
+        assertThatBugreportContainsFiles(UI_TRACES_PREDUMPED);
+    }
+
     @Test
     public void simultaneousBugreportsNotAllowed() throws Exception {
         // Start bugreport #1
@@ -506,9 +536,6 @@
         InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
                 "cmd window tracing start"
         );
-        InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
-                "service call SurfaceFlinger 1025 i32 1"
-        );
     }
 
     private static void stopPreDumpedUiTraces() {
@@ -611,6 +638,14 @@
         return files;
     }
 
+    private static void removeFilesIfNeeded(Path[] paths) throws Exception {
+        for (Path path : paths) {
+            InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+                    "rm -f " + path.toString()
+            );
+        }
+    }
+
     private static ParcelFileDescriptor parcelFd(File file) throws Exception {
         return ParcelFileDescriptor.open(file,
                 ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_APPEND);
diff --git a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
index 34f5841..3a872b5 100644
--- a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
+++ b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
@@ -18,7 +18,6 @@
 
 import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE;
 import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS;
-import static android.hardware.biometrics.Flags.FLAG_FACE_BACKGROUND_AUTHENTICATION;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -36,15 +35,12 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.res.Resources;
-import android.hardware.biometrics.BiometricPrompt;
 import android.os.CancellationSignal;
 import android.os.Handler;
-import android.os.HandlerExecutor;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.test.TestLooper;
 import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsEnabled;
 
 import com.android.internal.R;
 
@@ -62,7 +58,6 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import java.util.concurrent.Executor;
 
 @Presubmit
 @RunWith(MockitoJUnitRunner.class)
@@ -83,8 +78,6 @@
     @Mock
     private FaceManager.AuthenticationCallback mAuthCallback;
     @Mock
-    private BiometricPrompt.AuthenticationCallback mBioAuthCallback;
-    @Mock
     private FaceManager.EnrollmentCallback mEnrollmentCallback;
     @Mock
     private FaceManager.FaceDetectionCallback mFaceDetectionCallback;
@@ -98,16 +91,13 @@
     private TestLooper mLooper;
     private Handler mHandler;
     private FaceManager mFaceManager;
-    private Executor mExecutor;
 
     @Before
     public void setUp() throws Exception {
         mLooper = new TestLooper();
         mHandler = new Handler(mLooper.getLooper());
-        mExecutor = new HandlerExecutor(mHandler);
 
         when(mContext.getMainLooper()).thenReturn(mLooper.getLooper());
-        when(mContext.getMainExecutor()).thenReturn(mExecutor);
         when(mContext.getOpPackageName()).thenReturn(PACKAGE_NAME);
         when(mContext.getAttributionTag()).thenReturn(ATTRIBUTION_TAG);
         when(mContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
@@ -169,19 +159,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(FLAG_FACE_BACKGROUND_AUTHENTICATION)
-    public void authenticateInBackground_errorWhenUnavailable() throws Exception {
-        when(mService.authenticateInBackground(any(), anyLong(), any(), any()))
-                .thenThrow(new RemoteException());
-
-        mFaceManager.authenticateInBackground(mExecutor, null, new CancellationSignal(),
-                mBioAuthCallback);
-        mLooper.dispatchAll();
-
-        verify(mBioAuthCallback).onAuthenticationError(eq(FACE_ERROR_HW_UNAVAILABLE), any());
-    }
-
-    @Test
     public void enrollment_errorWhenFaceEnrollmentExists() throws RemoteException {
         when(mResources.getInteger(R.integer.config_faceMaxTemplatesPerUser)).thenReturn(1);
         when(mService.getEnrolledFaces(anyInt(), anyInt(), anyString()))
diff --git a/core/tests/coretests/src/android/view/ViewVelocityTest.java b/core/tests/coretests/src/android/view/ViewVelocityTest.java
index 128c54b..d437f7b 100644
--- a/core/tests/coretests/src/android/view/ViewVelocityTest.java
+++ b/core/tests/coretests/src/android/view/ViewVelocityTest.java
@@ -16,6 +16,8 @@
 
 package android.view;
 
+import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
+
 import static junit.framework.Assert.assertEquals;
 
 import static org.junit.Assert.assertFalse;
@@ -23,6 +25,7 @@
 
 import android.app.Activity;
 import android.os.SystemClock;
+import android.platform.test.annotations.RequiresFlagsEnabled;
 
 import androidx.test.annotation.UiThreadTest;
 import androidx.test.filters.SmallTest;
@@ -64,6 +67,7 @@
 
     @UiThreadTest
     @Test
+    @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API)
     public void frameRateChangesWhenContentMoves() {
         mMovingView.offsetLeftAndRight(100);
         float frameRate = mViewRoot.getPreferredFrameRate();
@@ -72,11 +76,13 @@
 
     @UiThreadTest
     @Test
+    @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API)
     public void firstFrameNoMovement() {
         assertEquals(0f, mViewRoot.getPreferredFrameRate(), 0f);
     }
 
     @Test
+    @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API)
     public void touchBoostDisable() throws Throwable {
         mActivityRule.runOnUiThread(() -> {
             long now = SystemClock.uptimeMillis();
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
index b209c7c..cb8754a 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
@@ -1162,7 +1162,8 @@
     @Test
     public void testTriggerFromPrivateProfile_withoutWorkProfile() throws RemoteException {
         mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
-                android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE);
+                android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         markPrivateProfileUserAvailable();
         Intent sendIntent = createSendImageIntent();
         List<ResolvedComponentInfo> privateResolvedComponentInfos =
@@ -1183,7 +1184,8 @@
     @Test
     public void testTriggerFromPrivateProfile_withWorkProfilePresent(){
         mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
-                android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE);
+                android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         ResolverActivity.ENABLE_TABBED_VIEW = false;
         markPrivateProfileUserAvailable();
         markWorkProfileUserAvailable();
@@ -1205,7 +1207,8 @@
     @Test
     public void testPrivateProfile_triggerFromPrimaryUser_withWorkProfilePresent(){
         mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
-                android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE);
+                android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         markPrivateProfileUserAvailable();
         markWorkProfileUserAvailable();
         Intent sendIntent = createSendImageIntent();
@@ -1228,7 +1231,8 @@
     @Test
     public void testPrivateProfile_triggerFromWorkProfile(){
         mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
-                android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE);
+                android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         markPrivateProfileUserAvailable();
         markWorkProfileUserAvailable();
         Intent sendIntent = createSendImageIntent();
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 051e73f..0f12438 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -455,7 +455,7 @@
         <permission name="android.permission.USE_BIOMETRIC" />
         <permission name="android.permission.TEST_BIOMETRIC" />
         <permission name="android.permission.SET_BIOMETRIC_DIALOG_LOGO" />
-        <permission name="android.permission.USE_BACKGROUND_FACE_AUTHENTICATION" />
+        <permission name="android.permission.MANAGE_BIOMETRIC_DIALOG" />
         <!-- Permissions required for CTS test - CtsContactsProviderTestCases -->
         <permission name="android.contacts.permission.MANAGE_SIM_ACCOUNTS" />
         <!-- Permissions required for CTS test - CtsHdmiCecHostTestCases -->
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index b6ce9b6..f9ac02a 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -789,10 +789,8 @@
      * @param m The 4x4 matrix to preconcatenate with the current matrix
      */
     @FlaggedApi(Flags.FLAG_MATRIX_44)
-    public void concat44(@Nullable Matrix44 m) {
-        if (m != null) {
-            nConcat(mNativeCanvasWrapper, m.mBackingArray);
-        }
+    public void concat(@Nullable Matrix44 m) {
+        if (m != null) nConcat(mNativeCanvasWrapper, m.mBackingArray);
     }
 
     /**
diff --git a/graphics/java/android/graphics/Matrix44.java b/graphics/java/android/graphics/Matrix44.java
index 7cc0eb7..a99e201 100644
--- a/graphics/java/android/graphics/Matrix44.java
+++ b/graphics/java/android/graphics/Matrix44.java
@@ -17,6 +17,7 @@
 package android.graphics;
 
 import android.annotation.FlaggedApi;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 
 import com.android.graphics.hwui.flags.Flags;
@@ -98,11 +99,11 @@
     /**
      * Gets the value at the matrix's row and column.
      *
-     * @param row An integer from 0 to 4 indicating the row of the value to get
-     * @param col An integer from 0 to 4 indicating the column of the value to get
+     * @param row An integer from 0 to 3 indicating the row of the value to get
+     * @param col An integer from 0 to 3 indicating the column of the value to get
      */
     @FlaggedApi(Flags.FLAG_MATRIX_44)
-    public float get(int row, int col) {
+    public float get(@IntRange(from = 0, to = 3) int row, @IntRange(from = 0, to = 3) int col) {
         if (row >= 0 && row < 4 && col >= 0 && col < 4) {
             return mBackingArray[row * 4 + col];
         }
@@ -112,12 +113,13 @@
     /**
      * Sets the value at the matrix's row and column to the provided value.
      *
-     * @param row An integer from 0 to 4 indicating the row of the value to change
-     * @param col An integer from 0 to 4 indicating the column of the value to change
+     * @param row An integer from 0 to 3 indicating the row of the value to change
+     * @param col An integer from 0 to 3 indicating the column of the value to change
      * @param val The value the element at the specified index will be set to
      */
     @FlaggedApi(Flags.FLAG_MATRIX_44)
-    public void set(int row, int col, float val) {
+    public void set(@IntRange(from = 0, to = 3) int row, @IntRange(from = 0, to = 3) int col,
+            float val) {
         if (row >= 0 && row < 4 && col >= 0 && col < 4) {
             mBackingArray[row * 4 + col] = val;
         } else {
diff --git a/libs/WindowManager/Shell/res/drawable/circular_progress.xml b/libs/WindowManager/Shell/res/drawable/circular_progress.xml
index 9482645..294b1f0 100644
--- a/libs/WindowManager/Shell/res/drawable/circular_progress.xml
+++ b/libs/WindowManager/Shell/res/drawable/circular_progress.xml
@@ -25,7 +25,7 @@
             <shape
                 android:shape="ring"
                 android:thickness="3dp"
-                android:innerRadius="17dp"
+                android:innerRadius="14dp"
                 android:useLevel="true">
             </shape>
         </rotate>
diff --git a/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml
index 02b7075..e5fe1b54 100644
--- a/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml
+++ b/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml
@@ -15,12 +15,12 @@
   ~ limitations under the License.
   -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="48dp"
-    android:height="48dp"
+    android:width="24dp"
+    android:height="24dp"
     android:tint="?attr/colorControlNormal"
     android:viewportHeight="960"
     android:viewportWidth="960">
     <path
-        android:fillColor="@android:color/white"
-        android:pathData="M180,840Q156,840 138,822Q120,804 120,780L120,180Q120,156 138,138Q156,120 180,120L780,120Q804,120 822,138Q840,156 840,180L840,780Q840,804 822,822Q804,840 780,840L180,840ZM180,780L780,780Q780,780 780,780Q780,780 780,780L780,277L180,277L180,780Q180,780 180,780Q180,780 180,780Z" />
+        android:fillColor="@android:color/black"
+        android:pathData="M160,800Q127,800 103.5,776.5Q80,753 80,720L80,240Q80,207 103.5,183.5Q127,160 160,160L800,160Q833,160 856.5,183.5Q880,207 880,240L880,720Q880,753 856.5,776.5Q833,800 800,800L160,800ZM160,720L800,720Q800,720 800,720Q800,720 800,720L800,320L160,320L160,720Q160,720 160,720Q160,720 160,720Z"/>
 </vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_select.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_select.xml
deleted file mode 100644
index 7c4f499..0000000
--- a/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_select.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2023 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:width="20dp"
-    android:height="20dp"
-    android:viewportWidth="20"
-    android:viewportHeight="20">
-  <path
-      android:pathData="M15.701,14.583L18.567,17.5L17.425,18.733L14.525,15.833L12.442,17.917V12.5H17.917L15.701,14.583ZM15.833,5.833H17.5V7.5H15.833V5.833ZM17.5,4.167H15.833V2.567C16.75,2.567 17.5,3.333 17.5,4.167ZM12.5,2.5H14.167V4.167H12.5V2.5ZM15.833,9.167H17.5V10.833H15.833V9.167ZM7.5,17.5H5.833V15.833H7.5V17.5ZM4.167,7.5H2.5V5.833H4.167V7.5ZM4.167,2.567V4.167H2.5C2.5,3.333 3.333,2.567 4.167,2.567ZM4.167,14.167H2.5V12.5H4.167V14.167ZM7.5,4.167H5.833V2.5H7.5V4.167ZM10.833,4.167H9.167V2.5H10.833V4.167ZM10.833,17.5H9.167V15.833H10.833V17.5ZM4.167,10.833H2.5V9.167H4.167V10.833ZM4.167,17.567C3.25,17.567 2.5,16.667 2.5,15.833H4.167V17.567Z"
-      android:fillColor="#1C1C14"/>
-</vector>
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
index a5605a7..fa18e2b 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
@@ -80,11 +80,14 @@
 
     <com.android.wm.shell.windowdecor.MaximizeButtonView
         android:id="@+id/maximize_button_view"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
+        android:layout_width="44dp"
+        android:layout_height="40dp"
         android:layout_gravity="end"
+        android:layout_marginHorizontal="8dp"
+        android:paddingHorizontal="5dp"
+        android:paddingVertical="3dp"
         android:clickable="true"
-        android:focusable="true" />
+        android:focusable="true"/>
 
     <ImageButton
         android:id="@+id/close_window"
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
index c6f85a0..fca2fe4 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
@@ -134,15 +134,6 @@
             android:drawableStart="@drawable/desktop_mode_ic_handle_menu_screenshot"
             android:drawableTint="?androidprv:attr/materialColorOnSurface"
             style="@style/DesktopModeHandleMenuActionButton"/>
-
-        <Button
-            android:id="@+id/select_button"
-            android:contentDescription="@string/select_text"
-            android:text="@string/select_text"
-            android:drawableStart="@drawable/desktop_mode_ic_handle_menu_select"
-            android:drawableTint="?androidprv:attr/materialColorOnSurface"
-            style="@style/DesktopModeHandleMenuActionButton"/>
-
     </LinearLayout>
 </LinearLayout>
 
diff --git a/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml b/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml
index e0057fe..296c8956 100644
--- a/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml
+++ b/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml
@@ -20,16 +20,16 @@
         android:id="@+id/progress_bar"
         style="?android:attr/progressBarStyleHorizontal"
         android:progressDrawable="@drawable/circular_progress"
-        android:layout_width="40dp"
-        android:layout_height="40dp"
+        android:layout_width="34dp"
+        android:layout_height="34dp"
         android:indeterminate="false"
         android:visibility="invisible"/>
 
     <ImageButton
         android:id="@+id/maximize_window"
-        android:layout_width="40dp"
-        android:layout_height="40dp"
-        android:padding="9dp"
+        android:layout_width="34dp"
+        android:layout_height="34dp"
+        android:padding="5dp"
         android:contentDescription="@string/maximize_button_text"
         android:tint="?androidprv:attr/materialColorOnSurface"
         android:background="?android:selectableItemBackgroundBorderless"
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 4d47ca9..139cde2 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
@@ -983,7 +983,13 @@
         // cache current min/max size
         Point minSize = mPipBoundsState.getMinSize();
         Point maxSize = mPipBoundsState.getMaxSize();
-        mPipBoundsState.updateMinMaxSize(pictureInPictureParams.getAspectRatioFloat());
+        final float aspectRatioFloat;
+        if (pictureInPictureParams.hasSetAspectRatio()) {
+            aspectRatioFloat = pictureInPictureParams.getAspectRatioFloat();
+        } else {
+            aspectRatioFloat = mPipBoundsAlgorithm.getDefaultAspectRatio();
+        }
+        mPipBoundsState.updateMinMaxSize(aspectRatioFloat);
         final Rect entryBounds = mPipTaskOrganizer.startSwipePipToHome(componentName, activityInfo,
                 pictureInPictureParams);
         // restore min/max size, as this is referenced later in OnDisplayChangingListener and needs
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index f4ccd68..500e6f4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -442,12 +442,6 @@
                 mDesktopTasksController.requestSplit(decoration.mTaskInfo);
             } else if (id == R.id.collapse_menu_button) {
                 decoration.closeHandleMenu();
-            } else if (id == R.id.select_button) {
-                if (DesktopModeStatus.IS_DISPLAY_CHANGE_ENABLED) {
-                    // TODO(b/278084491): dev option to enable display switching
-                    //  remove when select is implemented
-                    mDesktopTasksController.moveToNextDisplay(mTaskId);
-                }
             } else if (id == R.id.maximize_window) {
                 final RunningTaskInfo taskInfo = decoration.mTaskInfo;
                 decoration.closeHandleMenu();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
index b37dd0d..3d0dd31 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
@@ -35,7 +35,6 @@
 import android.view.MotionEvent;
 import android.view.SurfaceControl;
 import android.view.View;
-import android.widget.Button;
 import android.widget.ImageButton;
 import android.widget.ImageView;
 import android.widget.TextView;
@@ -53,6 +52,7 @@
  */
 class HandleMenu {
     private static final String TAG = "HandleMenu";
+    private static final boolean SHOULD_SHOW_MORE_ACTIONS_PILL = false;
     private final Context mContext;
     private final WindowDecoration mParentDecor;
     private WindowDecoration.AdditionalWindow mHandleMenuWindow;
@@ -185,11 +185,9 @@
      * Set up interactive elements & height of handle menu's more actions pill
      */
     private void setupMoreActionsPill(View handleMenu) {
-        final Button selectBtn = handleMenu.findViewById(R.id.select_button);
-        selectBtn.setOnClickListener(mOnClickListener);
-        final Button screenshotBtn = handleMenu.findViewById(R.id.screenshot_button);
-        // TODO: Remove once implemented.
-        screenshotBtn.setVisibility(View.GONE);
+        if (!SHOULD_SHOW_MORE_ACTIONS_PILL) {
+            handleMenu.findViewById(R.id.more_actions_pill).setVisibility(View.GONE);
+        }
     }
 
     /**
@@ -305,12 +303,15 @@
      * Determines handle menu height based on if windowing pill should be shown.
      */
     private int getHandleMenuHeight(Resources resources) {
-        int menuHeight = loadDimensionPixelSize(resources,
-                R.dimen.desktop_mode_handle_menu_height);
+        int menuHeight = loadDimensionPixelSize(resources, R.dimen.desktop_mode_handle_menu_height);
         if (!mShouldShowWindowingPill) {
             menuHeight -= loadDimensionPixelSize(resources,
                     R.dimen.desktop_mode_handle_menu_windowing_pill_height);
         }
+        if (!SHOULD_SHOW_MORE_ACTIONS_PILL) {
+            menuHeight -= loadDimensionPixelSize(resources,
+                    R.dimen.desktop_mode_handle_menu_more_actions_pill_height);
+        }
         return menuHeight;
     }
 
diff --git a/libs/androidfw/fuzz/resxmlparser_fuzzer/Android.bp b/libs/androidfw/fuzz/resxmlparser_fuzzer/Android.bp
new file mode 100644
index 0000000..4b008a7
--- /dev/null
+++ b/libs/androidfw/fuzz/resxmlparser_fuzzer/Android.bp
@@ -0,0 +1,51 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_libs_androidfw_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_libs_androidfw_license"],
+}
+
+cc_fuzz {
+    name: "resxmlparser_fuzzer",
+    srcs: [
+        "resxmlparser_fuzzer.cpp",
+    ],
+    host_supported: true,
+
+    static_libs: ["libgmock"],
+    target: {
+        android: {
+            shared_libs: [
+                "libandroidfw",
+                "libbase",
+                "libbinder",
+                "libcutils",
+                "liblog",
+                "libutils",
+            ],
+        },
+        host: {
+            static_libs: [
+                "libandroidfw",
+                "libbase",
+                "libbinder",
+                "libcutils",
+                "liblog",
+                "libutils",
+            ],
+        },
+        darwin: {
+            // libbinder is not supported on mac
+            enabled: false,
+        },
+    },
+
+    include_dirs: [
+        "system/incremental_delivery/incfs/util/include/",
+    ],
+
+    corpus: ["testdata/*"],
+    dictionary: "xmlparser_fuzzer.dict",
+}
diff --git a/libs/androidfw/fuzz/resxmlparser_fuzzer/resxmlparser_fuzzer.cpp b/libs/androidfw/fuzz/resxmlparser_fuzzer/resxmlparser_fuzzer.cpp
new file mode 100644
index 0000000..829a396
--- /dev/null
+++ b/libs/androidfw/fuzz/resxmlparser_fuzzer/resxmlparser_fuzzer.cpp
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+#include <memory>
+#include <cstdint>
+#include <cstddef>
+#include <fuzzer/FuzzedDataProvider.h>
+#include "androidfw/ResourceTypes.h"
+
+static void populateDynamicRefTableWithFuzzedData(
+    android::DynamicRefTable& table,
+    FuzzedDataProvider& fuzzedDataProvider) {
+
+    const size_t numMappings = fuzzedDataProvider.ConsumeIntegralInRange<size_t>(1, 5);
+    for (size_t i = 0; i < numMappings; ++i) {
+        const uint8_t packageId = fuzzedDataProvider.ConsumeIntegralInRange<uint8_t>(0x02, 0x7F);
+
+        // Generate a package name
+        std::string packageName;
+        size_t packageNameLength = fuzzedDataProvider.ConsumeIntegralInRange<size_t>(1, 128);
+        for (size_t j = 0; j < packageNameLength; ++j) {
+            // Consume characters only in the ASCII range (0x20 to 0x7E) to ensure valid UTF-8
+            char ch = fuzzedDataProvider.ConsumeIntegralInRange<char>(0x20, 0x7E);
+            packageName.push_back(ch);
+        }
+
+        // Convert std::string to String16 for compatibility
+        android::String16 androidPackageName(packageName.c_str(), packageName.length());
+
+        // Add the mapping to the table
+        table.addMapping(androidPackageName, packageId);
+    }
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+    FuzzedDataProvider fuzzedDataProvider(data, size);
+
+    auto dynamic_ref_table = std::make_shared<android::DynamicRefTable>();
+
+    // Populate the DynamicRefTable with fuzzed data
+    populateDynamicRefTableWithFuzzedData(*dynamic_ref_table, fuzzedDataProvider);
+
+    auto tree = android::ResXMLTree(std::move(dynamic_ref_table));
+
+    std::vector<uint8_t> xmlData = fuzzedDataProvider.ConsumeRemainingBytes<uint8_t>();
+    if (tree.setTo(xmlData.data(), xmlData.size()) != android::NO_ERROR) {
+        return 0; // Exit early if unable to parse XML data
+    }
+
+    tree.restart();
+
+    size_t len = 0;
+    auto code = tree.next();
+    if (code == android::ResXMLParser::START_TAG) {
+        // Access element name
+        auto name = tree.getElementName(&len);
+
+        // Access attributes of the current element
+        for (size_t i = 0; i < tree.getAttributeCount(); i++) {
+            // Access attribute name
+            auto attrName = tree.getAttributeName(i, &len);
+        }
+    } else if (code == android::ResXMLParser::TEXT) {
+        const auto text = tree.getText(&len);
+    }
+    return 0; // Non-zero return values are reserved for future use.
+}
diff --git a/libs/androidfw/fuzz/resxmlparser_fuzzer/testdata/attributes.xml b/libs/androidfw/fuzz/resxmlparser_fuzzer/testdata/attributes.xml
new file mode 100644
index 0000000..417fec7
--- /dev/null
+++ b/libs/androidfw/fuzz/resxmlparser_fuzzer/testdata/attributes.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<root>
+    <child id="1">
+        <subchild type="A">Content A</subchild>
+        <subchild type="B">Content B</subchild>
+    </child>
+    <child id="2" extra="data">
+        <subchild type="C">Content C</subchild>
+    </child>
+</root>
diff --git a/libs/androidfw/fuzz/resxmlparser_fuzzer/testdata/basic.xml b/libs/androidfw/fuzz/resxmlparser_fuzzer/testdata/basic.xml
new file mode 100644
index 0000000..7e13db5
--- /dev/null
+++ b/libs/androidfw/fuzz/resxmlparser_fuzzer/testdata/basic.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<root>
+    <child1>Value 1</child1>
+    <child2>Value 2</child2>
+</root>
diff --git a/libs/androidfw/fuzz/resxmlparser_fuzzer/testdata/cdata.xml b/libs/androidfw/fuzz/resxmlparser_fuzzer/testdata/cdata.xml
new file mode 100644
index 0000000..90cdf35
--- /dev/null
+++ b/libs/androidfw/fuzz/resxmlparser_fuzzer/testdata/cdata.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<root>
+    <!-- Example with special characters and CDATA -->
+    <data><![CDATA[Some <encoded> data & other "special" characters]]></data>
+    <message>Hello &amp; Welcome!</message>
+</root>
diff --git a/libs/androidfw/fuzz/resxmlparser_fuzzer/xmlparser_fuzzer.dict b/libs/androidfw/fuzz/resxmlparser_fuzzer/xmlparser_fuzzer.dict
new file mode 100644
index 0000000..745ded4
--- /dev/null
+++ b/libs/androidfw/fuzz/resxmlparser_fuzzer/xmlparser_fuzzer.dict
@@ -0,0 +1,11 @@
+root_tag=<root>
+child_tag=<child>
+end_child_tag=</child>
+id_attr=id="
+type_attr=type="
+cdata_start=<![CDATA[
+cdata_end=]]>
+ampersand_entity=&amp;
+xml_header=<?xml version="1.0" encoding="UTF-8"?>
+comment_start=<!--
+comment_end= -->
diff --git a/location/java/android/location/provider/ForwardGeocodeRequest.java b/location/java/android/location/provider/ForwardGeocodeRequest.java
index 8f227b1..89d14fb 100644
--- a/location/java/android/location/provider/ForwardGeocodeRequest.java
+++ b/location/java/android/location/provider/ForwardGeocodeRequest.java
@@ -260,7 +260,11 @@
             mCallingAttributionTag = null;
         }
 
-        /** Sets the attribution tag. */
+        /**
+         * Sets the attribution tag.
+         *
+         * @param attributionTag The attribution tag to associate with the request.
+         */
         @NonNull
         public Builder setCallingAttributionTag(@NonNull String attributionTag) {
             mCallingAttributionTag = attributionTag;
diff --git a/location/java/android/location/provider/ReverseGeocodeRequest.java b/location/java/android/location/provider/ReverseGeocodeRequest.java
index 57c9047..2107707 100644
--- a/location/java/android/location/provider/ReverseGeocodeRequest.java
+++ b/location/java/android/location/provider/ReverseGeocodeRequest.java
@@ -207,7 +207,11 @@
             mCallingAttributionTag = null;
         }
 
-        /** Sets the attribution tag. */
+        /**
+         * Sets the attribution tag.
+         *
+         * @param attributionTag The attribution tag to associate with the request.
+         */
         @NonNull
         public Builder setCallingAttributionTag(@NonNull String attributionTag) {
             mCallingAttributionTag = attributionTag;
diff --git a/native/android/input.cpp b/native/android/input.cpp
index 53699bc..0a22314 100644
--- a/native/android/input.cpp
+++ b/native/android/input.cpp
@@ -124,11 +124,11 @@
 }
 
 float AMotionEvent_getXOffset(const AInputEvent* motion_event) {
-    return static_cast<const MotionEvent*>(motion_event)->getXOffset();
+    return static_cast<const MotionEvent*>(motion_event)->getRawXOffset();
 }
 
 float AMotionEvent_getYOffset(const AInputEvent* motion_event) {
-    return static_cast<const MotionEvent*>(motion_event)->getYOffset();
+    return static_cast<const MotionEvent*>(motion_event)->getRawYOffset();
 }
 
 float AMotionEvent_getXPrecision(const AInputEvent* motion_event) {
diff --git a/nfc/Android.bp b/nfc/Android.bp
index b6bc40d..7dd16ba 100644
--- a/nfc/Android.bp
+++ b/nfc/Android.bp
@@ -38,6 +38,7 @@
     name: "framework-nfc",
     libs: [
         "unsupportedappusage", // for android.compat.annotation.UnsupportedAppUsage
+        "framework-permission-s",
     ],
     static_libs: [
         "android.nfc.flags-aconfig-java",
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index c0db089..54f1421 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -194,13 +194,13 @@
 package android.nfc.cardemulation {
 
   public final class CardEmulation {
-    method public boolean categoryAllowsForegroundPreference(String);
+    method @Deprecated public boolean categoryAllowsForegroundPreference(String);
     method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public java.util.List<java.lang.String> getAidsForPreferredPaymentService();
     method public java.util.List<java.lang.String> getAidsForService(android.content.ComponentName, String);
-    method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public CharSequence getDescriptionForPreferredPaymentService();
+    method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public CharSequence getDescriptionForPreferredPaymentService();
     method public static android.nfc.cardemulation.CardEmulation getInstance(android.nfc.NfcAdapter);
-    method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public String getRouteDestinationForPreferredPaymentService();
-    method public int getSelectionModeForCategory(String);
+    method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public String getRouteDestinationForPreferredPaymentService();
+    method @Deprecated public int getSelectionModeForCategory(String);
     method public boolean isDefaultServiceForAid(android.content.ComponentName, String);
     method public boolean isDefaultServiceForCategory(android.content.ComponentName, String);
     method public boolean registerAidsForService(android.content.ComponentName, String, java.util.List<java.lang.String>);
diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java
index ea58504..e55f540 100644
--- a/nfc/java/android/nfc/cardemulation/CardEmulation.java
+++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java
@@ -27,6 +27,7 @@
 import android.annotation.UserHandleAware;
 import android.annotation.UserIdInt;
 import android.app.Activity;
+import android.app.role.RoleManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -278,13 +279,22 @@
      * @param category The category, e.g. {@link #CATEGORY_PAYMENT}
      * @return whether AIDs in the category can be handled by a service
      *         specified by the foreground app.
+     *
+     * @deprecated see {@link android.app.role.RoleManager#ROLE_WALLET}. The definition of the
+     * Preferred Payment service is no longer valid. All routings will be done in a AID
+     * category agnostic manner.
      */
     @SuppressWarnings("NonUserGetterCalled")
+    @Deprecated
     public boolean categoryAllowsForegroundPreference(String category) {
+        Context contextAsUser = mContext.createContextAsUser(
+                UserHandle.of(UserHandle.myUserId()), 0);
+        RoleManager roleManager = contextAsUser.getSystemService(RoleManager.class);
+        if (roleManager.isRoleAvailable(RoleManager.ROLE_WALLET)) {
+            return true;
+        }
         if (CATEGORY_PAYMENT.equals(category)) {
             boolean preferForeground = false;
-            Context contextAsUser = mContext.createContextAsUser(
-                    UserHandle.of(UserHandle.myUserId()), 0);
             try {
                 preferForeground = Settings.Secure.getInt(
                         contextAsUser.getContentResolver(),
@@ -309,7 +319,12 @@
      *    to pick a service if there is a conflict.
      * @param category The category, for example {@link #CATEGORY_PAYMENT}
      * @return the selection mode for the passed in category
+     *
+     * @deprecated see {@link android.app.role.RoleManager#ROLE_WALLET}. The definition of the
+     * Preferred Payment service is no longer valid. All routings will be done in a AID
+     * category agnostic manner.
      */
+    @Deprecated
     public int getSelectionModeForCategory(String category) {
         if (CATEGORY_PAYMENT.equals(category)) {
             boolean paymentRegistered = false;
@@ -792,8 +807,15 @@
      *                                               (e.g. eSE/eSE1, eSE2, etc.).
      *                          2. "OffHost" if the payment service does not specify secure element
      *                             name.
+     *
+     * @deprecated see {@link android.app.role.RoleManager#ROLE_WALLET}. The definition of the
+     * Preferred Payment service is no longer valid. All routings will go to the Wallet Holder app.
+     * A payment service will be selected automatically based on registered AIDs. In the case of
+     * multiple services that register for the same payment AID, the selection will be done on
+     * an alphabetical order based on the component names.
      */
     @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO)
+    @Deprecated
     @Nullable
     public String getRouteDestinationForPreferredPaymentService() {
         try {
@@ -836,8 +858,15 @@
      * Returns a user-visible description of the preferred payment service.
      *
      * @return the preferred payment service description
+     *
+     * @deprecated see {@link android.app.role.RoleManager#ROLE_WALLET}. The definition of the
+     * Preferred Payment service is no longer valid. All routings will go to the Wallet Holder app.
+     * A payment service will be selected automatically based on registered AIDs. In the case of
+     * multiple services that register for the same payment AID, the selection will be done on
+     * an alphabetical order based on the component names.
      */
     @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO)
+    @Deprecated
     @Nullable
     public CharSequence getDescriptionForPreferredPaymentService() {
         try {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index 4e1f4ee..d7a2e36 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -21,14 +21,13 @@
 import android.content.Context
 import android.credentials.CredentialManager
 import android.credentials.GetCredentialRequest
-import android.credentials.GetCredentialResponse
-import android.credentials.GetCredentialException
 import android.credentials.GetCandidateCredentialsResponse
 import android.credentials.GetCandidateCredentialsException
 import android.credentials.CredentialOption
 import android.credentials.selection.Entry
 import android.credentials.selection.GetCredentialProviderData
 import android.credentials.selection.ProviderData
+import android.graphics.BlendMode
 import android.graphics.drawable.Icon
 import android.os.Bundle
 import android.os.CancellationSignal
@@ -123,13 +122,10 @@
         // TODO(b/324635774): Use callback for validating. If the request is coming
         // directly from the view, there should be a corresponding callback, otherwise
         // we should fail fast,
-        val getCredCallback = getCredManCallback(structure)
         if (getCredRequest == null) {
             Log.i(TAG, "No credential manager request found")
             callback.onFailure("No credential manager request found")
             return
-        } else if (getCredCallback == null) {
-            Log.i(TAG, "No credential manager callback found")
         }
         val credentialManager: CredentialManager =
                 getSystemService(Context.CREDENTIAL_SERVICE) as CredentialManager
@@ -353,6 +349,7 @@
         val sliceBuilder = InlineSuggestionUi
                 .newContentBuilder(pendingIntent)
                 .setTitle(displayName)
+        icon.setTintBlendMode(BlendMode.DST)
         sliceBuilder.setStartIcon(icon)
         if (primaryEntry.credentialType ==
                 CredentialType.PASSKEY && duplicateDisplayNameForPasskeys[displayName] == true) {
@@ -526,42 +523,6 @@
         TODO("Not yet implemented")
     }
 
-    private fun getCredManCallback(structure: AssistStructure): OutcomeReceiver<
-            GetCredentialResponse, GetCredentialException>? {
-        return traverseStructureForCallback(structure)
-    }
-
-    private fun traverseStructureForCallback(
-            structure: AssistStructure
-    ): OutcomeReceiver<GetCredentialResponse, GetCredentialException>? {
-        val windowNodes: List<AssistStructure.WindowNode> =
-                structure.run {
-                    (0 until windowNodeCount).map { getWindowNodeAt(it) }
-                }
-
-        windowNodes.forEach { windowNode: AssistStructure.WindowNode ->
-            return traverseNodeForCallback(windowNode.rootViewNode)
-        }
-        return null
-    }
-
-    private fun traverseNodeForCallback(
-            viewNode: AssistStructure.ViewNode
-    ): OutcomeReceiver<GetCredentialResponse, GetCredentialException>? {
-        val children: List<AssistStructure.ViewNode> =
-                viewNode.run {
-                    (0 until childCount).map { getChildAt(it) }
-                }
-
-        children.forEach { childNode: AssistStructure.ViewNode ->
-            if (childNode.isFocused() && childNode.credentialManagerCallback != null) {
-                return childNode.credentialManagerCallback
-            }
-            return traverseNodeForCallback(childNode)
-        }
-        return null
-    }
-
     private fun getCredManRequest(
             structure: AssistStructure,
             sessionId: Int,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
index a7b5c36..e43b09e 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
@@ -39,7 +39,6 @@
 import com.android.credentialmanager.ui.theme.EntryShape
 import kotlinx.coroutines.launch
 
-
 /** Draws a modal bottom sheet with the same styles and effects shared by various flows. */
 @Composable
 @OptIn(ExperimentalMaterial3Api::class)
@@ -73,7 +72,7 @@
                 dragHandle = null,
                 // Never take over the full screen. We always want to leave some top scrim space
                 // for exiting and viewing the underlying app to help a user gain context.
-                modifier = Modifier.padding(top = 56.dp),
+                modifier = Modifier.padding(top = 72.dp),
         )
     } else {
         val scope = rememberCoroutineScope()
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt
index c68ae8b..006a2d9 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt
@@ -16,6 +16,7 @@
 
 package com.android.credentialmanager.common.ui
 
+import android.credentials.flags.Flags
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.ColumnScope
 import androidx.compose.foundation.layout.WindowInsets
@@ -63,7 +64,7 @@
             modifier = Modifier.padding(
                 start = 24.dp,
                 end = 24.dp,
-                bottom = 18.dp,
+                bottom = if (Flags.selectorUiImprovementsEnabled()) 8.dp else 18.dp,
                 top = if (topAppBar == null) 24.dp else 0.dp
             ).fillMaxWidth().wrapContentHeight(),
             horizontalAlignment = Alignment.CenterHorizontally,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/InlinePresentationFactory.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/InlinePresentationFactory.kt
index 3ebdd20..ff421bc 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/InlinePresentationFactory.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/InlinePresentationFactory.kt
@@ -21,63 +21,20 @@
 import android.content.Context
 import android.util.Size
 import android.widget.inline.InlinePresentationSpec
-import androidx.autofill.inline.common.TextViewStyle
-import androidx.autofill.inline.common.ViewStyle
-import androidx.autofill.inline.UiVersions
-import androidx.autofill.inline.UiVersions.Style
-import androidx.autofill.inline.v1.InlineSuggestionUi
-import androidx.core.content.ContextCompat
-import android.util.TypedValue
-import android.graphics.Typeface
-
 
 class InlinePresentationsFactory {
     companion object {
-        private const val googleSansMediumFontFamily = "google-sans-medium"
-        private const val googleSansTextFontFamily = "google-sans-text"
-        // There is no min width required for now but this is needed for the spec builder
-        private const val minInlineWidth = 5000
+        // There is no max width required for now but this is needed for the spec builder
+        private const val maxInlineWidth = 5000
 
 
         fun modifyInlinePresentationSpec(context: Context,
                                          originalSpec: InlinePresentationSpec): InlinePresentationSpec {
             return InlinePresentationSpec.Builder(Size(originalSpec.minSize.width, originalSpec
                     .minSize.height),
-                    Size(minInlineWidth, originalSpec
+                    Size(maxInlineWidth, originalSpec
                             .maxSize.height))
-                    .setStyle(UiVersions.newStylesBuilder().addStyle(getStyle(context)).build())
-                    .build()
-        }
-
-
-        fun getStyle(context: Context): Style {
-            val textColorPrimary = ContextCompat.getColor(context,
-                    com.android.credentialmanager.R.color.text_primary)
-            val textColorSecondary = ContextCompat.getColor(context,
-                    com.android.credentialmanager.R.color.text_secondary)
-            val textColorBackground = ContextCompat.getColor(context,
-                    com.android.credentialmanager.R.color.inline_background)
-            val chipHorizontalPadding = context.resources.getDimensionPixelSize(com.android
-                    .credentialmanager.R.dimen.horizontal_chip_padding)
-            val chipVerticalPadding = context.resources.getDimensionPixelSize(com.android
-                    .credentialmanager.R.dimen.vertical_chip_padding)
-            return InlineSuggestionUi.newStyleBuilder()
-                    .setChipStyle(
-                            ViewStyle.Builder().setPadding(chipHorizontalPadding,
-                                    chipVerticalPadding,
-                                    chipHorizontalPadding, chipVerticalPadding).build()
-                    )
-                    .setTitleStyle(
-                            TextViewStyle.Builder().setTextColor(textColorPrimary).setTextSize
-                            (TypedValue.COMPLEX_UNIT_DIP, 14F)
-                                    .setTypeface(googleSansMediumFontFamily,
-                                            Typeface.NORMAL).setBackgroundColor(textColorBackground)
-                                    .build()
-                    )
-                    .setSubtitleStyle(TextViewStyle.Builder().setTextColor(textColorSecondary)
-                            .setTextSize(TypedValue.COMPLEX_UNIT_DIP, 12F).setTypeface
-                            (googleSansTextFontFamily, Typeface.NORMAL).setBackgroundColor
-                            (textColorBackground).build())
+                    .setStyle(originalSpec.getStyle())
                     .build()
         }
     }
diff --git a/packages/PackageInstaller/Android.bp b/packages/PackageInstaller/Android.bp
index 98a5a67..79c810c 100644
--- a/packages/PackageInstaller/Android.bp
+++ b/packages/PackageInstaller/Android.bp
@@ -54,6 +54,7 @@
         "androidx.lifecycle_lifecycle-extensions",
         "android.content.pm.flags-aconfig-java",
         "android.os.flags-aconfig-java",
+        "android.multiuser.flags-aconfig-java",
     ],
 
     lint: {
@@ -85,6 +86,7 @@
         "androidx.lifecycle_lifecycle-extensions",
         "android.content.pm.flags-aconfig-java",
         "android.os.flags-aconfig-java",
+        "android.multiuser.flags-aconfig-java",
     ],
     aaptflags: ["--product tablet"],
 
@@ -118,6 +120,7 @@
         "androidx.lifecycle_lifecycle-extensions",
         "android.content.pm.flags-aconfig-java",
         "android.os.flags-aconfig-java",
+        "android.multiuser.flags-aconfig-java",
     ],
     aaptflags: ["--product tv"],
 
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
index 221ca4f..8f5d07c 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
@@ -166,6 +166,7 @@
                     messageBuilder.append(getString(
                             R.string.uninstall_application_text_current_user_clone_profile));
                 } else if (Flags.allowPrivateProfile()
+                        && android.multiuser.Flags.enablePrivateSpaceFeatures()
                         && customUserManager.isPrivateProfile()
                         && customUserManager.isSameProfileGroup(dialogInfo.user, myUserHandle)) {
                     messageBuilder.append(
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt
index 0fc1845..c6b6d36 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt
@@ -235,7 +235,9 @@
                     messageString = context.getString(
                             R.string.uninstall_application_text_current_user_clone_profile
                     )
-                } else if (Flags.allowPrivateProfile() && customUserManager!!.isPrivateProfile()) {
+                } else if (Flags.allowPrivateProfile()
+                        && android.multiuser.Flags.enablePrivateSpaceFeatures()
+                        && customUserManager!!.isPrivateProfile()) {
                     // TODO(b/324244123): Get these Strings from a User Property API.
                     messageString = context.getString(
                             R.string.uninstall_application_text_current_user_private_profile
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index 33086f98..2889ce2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -1703,6 +1703,7 @@
 
         public boolean isPrivateProfile() {
             return android.os.Flags.allowPrivateProfile()
+                    && android.multiuser.Flags.enablePrivateSpaceFeatures()
                     && UserManager.USER_TYPE_PROFILE_PRIVATE.equals(mProfileType);
         }
 
@@ -1918,16 +1919,6 @@
         }
     };
 
-    public static final AppFilter FILTER_PERSONAL_OR_PRIVATE = new AppFilter() {
-        @Override
-        public void init() {}
-
-        @Override
-        public boolean filterApp(AppEntry entry) {
-            return entry.showInPersonalTab || entry.isPrivateProfile();
-        }
-    };
-
     /**
      * Displays a combined list with "downloaded" and "visible in launcher" apps only.
      */
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt
index da1fd55..0c7d6f0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt
@@ -311,7 +311,7 @@
         }
 
         builder.apply {
-            setSourceDevice(device, sourceAddrType)
+            setSourceDevice(device, addrType)
             setSourceAdvertisingSid(sourceAdvertiserSid)
             setBroadcastId(broadcastId)
             setBroadcastName(broadcastName)
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 5f026c4..e34c50e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -44,7 +44,6 @@
 import static com.android.settingslib.media.LocalMediaManager.MediaDeviceState.STATE_SELECTED;
 
 import android.annotation.TargetApi;
-import android.app.Notification;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.content.ComponentName;
@@ -67,6 +66,7 @@
 import com.android.settingslib.media.flags.Flags;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.LinkedHashSet;
@@ -81,11 +81,43 @@
 
 /** InfoMediaManager provide interface to get InfoMediaDevice list. */
 @RequiresApi(Build.VERSION_CODES.R)
-public abstract class InfoMediaManager extends MediaManager {
+public abstract class InfoMediaManager {
+    /** Callback for notifying device is added, removed and attributes changed. */
+    public interface MediaDeviceCallback {
 
-    private static final String TAG = "InfoMediaManager";
-    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-    protected final List<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>();
+        /**
+         * Callback for notifying MediaDevice list is added.
+         *
+         * @param devices the MediaDevice list
+         */
+        void onDeviceListAdded(@NonNull List<MediaDevice> devices);
+
+        /**
+         * Callback for notifying MediaDevice list is removed.
+         *
+         * @param devices the MediaDevice list
+         */
+        void onDeviceListRemoved(@NonNull List<MediaDevice> devices);
+
+        /**
+         * Callback for notifying connected MediaDevice is changed.
+         *
+         * @param id the id of MediaDevice
+         */
+        void onConnectedDeviceChanged(@Nullable String id);
+
+        /**
+         * Callback for notifying that transferring is failed.
+         *
+         * @param reason the reason that the request has failed. Can be one of followings: {@link
+         *     android.media.MediaRoute2ProviderService#REASON_UNKNOWN_ERROR}, {@link
+         *     android.media.MediaRoute2ProviderService#REASON_REJECTED}, {@link
+         *     android.media.MediaRoute2ProviderService#REASON_NETWORK_ERROR}, {@link
+         *     android.media.MediaRoute2ProviderService#REASON_ROUTE_NOT_AVAILABLE}, {@link
+         *     android.media.MediaRoute2ProviderService#REASON_INVALID_COMMAND},
+         */
+        void onRequestFailed(int reason);
+    }
 
     /** Checked exception that signals the specified package is not present in the system. */
     public static class PackageNotAvailableException extends Exception {
@@ -94,19 +126,22 @@
         }
     }
 
+    private static final String TAG = "InfoMediaManager";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+    protected final List<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>();
+    @NonNull protected final Context mContext;
     @NonNull protected final String mPackageName;
+    private final Collection<MediaDeviceCallback> mCallbacks = new CopyOnWriteArrayList<>();
     private MediaDevice mCurrentConnectedDevice;
     private final LocalBluetoothManager mBluetoothManager;
     private final Map<String, RouteListingPreference.Item> mPreferenceItemMap =
             new ConcurrentHashMap<>();
 
     /* package */ InfoMediaManager(
-            Context context,
+            @NonNull Context context,
             @NonNull String packageName,
-            Notification notification,
-            LocalBluetoothManager localBluetoothManager) {
-        super(context, notification);
-
+            @NonNull LocalBluetoothManager localBluetoothManager) {
+        mContext = context;
         mBluetoothManager = localBluetoothManager;
         mPackageName = packageName;
     }
@@ -115,7 +150,6 @@
     public static InfoMediaManager createInstance(
             Context context,
             @Nullable String packageName,
-            Notification notification,
             LocalBluetoothManager localBluetoothManager) {
 
         // The caller is only interested in system routes (headsets, built-in speakers, etc), and is
@@ -127,17 +161,14 @@
 
         if (Flags.useMediaRouter2ForInfoMediaManager()) {
             try {
-                return new RouterInfoMediaManager(
-                        context, packageName, notification, localBluetoothManager);
+                return new RouterInfoMediaManager(context, packageName, localBluetoothManager);
             } catch (PackageNotAvailableException ex) {
                 // TODO: b/293578081 - Propagate this exception to callers for proper handling.
                 Log.w(TAG, "Returning a no-op InfoMediaManager for package " + packageName);
-                return new NoOpInfoMediaManager(
-                        context, packageName, notification, localBluetoothManager);
+                return new NoOpInfoMediaManager(context, packageName, localBluetoothManager);
             }
         } else {
-            return new ManagerInfoMediaManager(
-                    context, packageName, notification, localBluetoothManager);
+            return new ManagerInfoMediaManager(context, packageName, localBluetoothManager);
         }
     }
 
@@ -239,6 +270,38 @@
         return null;
     }
 
+    protected final void registerCallback(MediaDeviceCallback callback) {
+        if (!mCallbacks.contains(callback)) {
+            mCallbacks.add(callback);
+        }
+    }
+
+    protected final void unregisterCallback(MediaDeviceCallback callback) {
+        mCallbacks.remove(callback);
+    }
+
+    private void dispatchDeviceListAdded(@NonNull List<MediaDevice> devices) {
+        for (MediaDeviceCallback callback : getCallbacks()) {
+            callback.onDeviceListAdded(new ArrayList<>(devices));
+        }
+    }
+
+    private void dispatchConnectedDeviceChanged(String id) {
+        for (MediaDeviceCallback callback : getCallbacks()) {
+            callback.onConnectedDeviceChanged(id);
+        }
+    }
+
+    protected void dispatchOnRequestFailed(int reason) {
+        for (MediaDeviceCallback callback : getCallbacks()) {
+            callback.onRequestFailed(reason);
+        }
+    }
+
+    private Collection<MediaDeviceCallback> getCallbacks() {
+        return new CopyOnWriteArrayList<>(mCallbacks);
+    }
+
     /**
      * Get current device that played media.
      * @return MediaDevice
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 5925492..63056b6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -138,8 +138,7 @@
         }
 
         mInfoMediaManager =
-                InfoMediaManager.createInstance(
-                        context, packageName, notification, mLocalBluetoothManager);
+                InfoMediaManager.createInstance(context, packageName, mLocalBluetoothManager);
     }
 
     /**
@@ -505,9 +504,9 @@
         return new CopyOnWriteArrayList<>(mCallbacks);
     }
 
-    class MediaDeviceCallback implements MediaManager.MediaDeviceCallback {
+    class MediaDeviceCallback implements InfoMediaManager.MediaDeviceCallback {
         @Override
-        public void onDeviceListAdded(List<MediaDevice> devices) {
+        public void onDeviceListAdded(@NonNull List<MediaDevice> devices) {
             synchronized (mMediaDevicesLock) {
                 mMediaDevices.clear();
                 mMediaDevices.addAll(devices);
@@ -637,7 +636,7 @@
         }
 
         @Override
-        public void onDeviceListRemoved(List<MediaDevice> devices) {
+        public void onDeviceListRemoved(@NonNull List<MediaDevice> devices) {
             synchronized (mMediaDevicesLock) {
                 mMediaDevices.removeAll(devices);
             }
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
index 453e807..c4fac35 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
@@ -16,7 +16,6 @@
 
 package com.android.settingslib.media;
 
-import android.app.Notification;
 import android.content.Context;
 import android.media.MediaRoute2Info;
 import android.media.MediaRouter2Manager;
@@ -54,9 +53,8 @@
     /* package */ ManagerInfoMediaManager(
             Context context,
             @NonNull String packageName,
-            Notification notification,
             LocalBluetoothManager localBluetoothManager) {
-        super(context, packageName, notification, localBluetoothManager);
+        super(context, packageName, localBluetoothManager);
 
         mRouterManager = MediaRouter2Manager.getInstance(context);
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java
deleted file mode 100644
index d562c8a..0000000
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.settingslib.media;
-
-import android.annotation.NonNull;
-import android.app.Notification;
-import android.content.Context;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
-
-/**
- * MediaManager provide interface to get MediaDevice list.
- */
-public abstract class MediaManager {
-
-    protected final Collection<MediaDeviceCallback> mCallbacks = new CopyOnWriteArrayList<>();
-
-    protected Context mContext;
-    protected Notification mNotification;
-
-    MediaManager(Context context, Notification notification) {
-        mContext = context;
-        mNotification = notification;
-    }
-
-    protected void registerCallback(MediaDeviceCallback callback) {
-        if (!mCallbacks.contains(callback)) {
-            mCallbacks.add(callback);
-        }
-    }
-
-    protected void unregisterCallback(MediaDeviceCallback callback) {
-        if (mCallbacks.contains(callback)) {
-            mCallbacks.remove(callback);
-        }
-    }
-
-    protected void dispatchDeviceListAdded(@NonNull List<MediaDevice> devices) {
-        for (MediaDeviceCallback callback : getCallbacks()) {
-            callback.onDeviceListAdded(new ArrayList<>(devices));
-        }
-    }
-
-    protected void dispatchDeviceListRemoved(List<MediaDevice> devices) {
-        for (MediaDeviceCallback callback : getCallbacks()) {
-            callback.onDeviceListRemoved(devices);
-        }
-    }
-
-    protected void dispatchConnectedDeviceChanged(String id) {
-        for (MediaDeviceCallback callback : getCallbacks()) {
-            callback.onConnectedDeviceChanged(id);
-        }
-    }
-
-    protected void dispatchOnRequestFailed(int reason) {
-        for (MediaDeviceCallback callback : getCallbacks()) {
-            callback.onRequestFailed(reason);
-        }
-    }
-
-    private Collection<MediaDeviceCallback> getCallbacks() {
-        return new CopyOnWriteArrayList<>(mCallbacks);
-    }
-
-    /**
-     * Callback for notifying device is added, removed and attributes changed.
-     */
-    public interface MediaDeviceCallback {
-
-        /**
-         * Callback for notifying MediaDevice list is added.
-         *
-         * @param devices the MediaDevice list
-         */
-        void onDeviceListAdded(List<MediaDevice> devices);
-
-        /**
-         * Callback for notifying MediaDevice list is removed.
-         *
-         * @param devices the MediaDevice list
-         */
-        void onDeviceListRemoved(List<MediaDevice> devices);
-
-        /**
-         * Callback for notifying connected MediaDevice is changed.
-         *
-         * @param id the id of MediaDevice
-         */
-        void onConnectedDeviceChanged(String id);
-
-        /**
-         * Callback for notifying that transferring is failed.
-         *
-         * @param reason the reason that the request has failed. Can be one of followings:
-         * {@link android.media.MediaRoute2ProviderService#REASON_UNKNOWN_ERROR},
-         * {@link android.media.MediaRoute2ProviderService#REASON_REJECTED},
-         * {@link android.media.MediaRoute2ProviderService#REASON_NETWORK_ERROR},
-         * {@link android.media.MediaRoute2ProviderService#REASON_ROUTE_NOT_AVAILABLE},
-         * {@link android.media.MediaRoute2ProviderService#REASON_INVALID_COMMAND},
-         */
-        void onRequestFailed(int reason);
-    }
-}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
index ea4de39..ff4d4dd 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
@@ -16,7 +16,6 @@
 
 package com.android.settingslib.media;
 
-import android.app.Notification;
 import android.content.Context;
 import android.media.MediaRoute2Info;
 import android.media.RouteListingPreference;
@@ -42,9 +41,8 @@
     NoOpInfoMediaManager(
             Context context,
             @NonNull String packageName,
-            Notification notification,
             LocalBluetoothManager localBluetoothManager) {
-        super(context, packageName, notification, localBluetoothManager);
+        super(context, packageName, localBluetoothManager);
     }
 
     @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
index df03167..9c82cb1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
@@ -17,7 +17,6 @@
 package com.android.settingslib.media;
 
 import android.annotation.SuppressLint;
-import android.app.Notification;
 import android.content.Context;
 import android.media.MediaRoute2Info;
 import android.media.MediaRouter2;
@@ -71,10 +70,9 @@
     /* package */ RouterInfoMediaManager(
             Context context,
             @NonNull String packageName,
-            Notification notification,
             LocalBluetoothManager localBluetoothManager)
             throws PackageNotAvailableException {
-        super(context, packageName, notification, localBluetoothManager);
+        super(context, packageName, localBluetoothManager);
 
         MediaRouter2 router = null;
 
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
index 1ad7d49..fe83ffb 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
@@ -319,7 +319,8 @@
 
     @Test
     public void testPrivateProfileFilterDisplaysCorrectApps() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
 
         mEntry.showInPersonalTab = true;
         mEntry.mProfileType = UserManager.USER_TYPE_FULL_SYSTEM;
@@ -334,7 +335,8 @@
 
     @Test
     public void testPrivateProfileFilterDisplaysCorrectAppsWhenFlagDisabled() {
-        mSetFlagsRule.disableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.disableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
 
         mEntry.showInPersonalTab = false;
         mEntry.mProfileType = UserManager.USER_TYPE_PROFILE_PRIVATE;
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java
index c647cbb..f0185b9 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java
@@ -64,22 +64,21 @@
     @RequiresFlagsEnabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
     public void createInstance_withMR2FlagOn_returnsRouterInfoMediaManager() {
         InfoMediaManager manager =
-                InfoMediaManager.createInstance(mContext, mContext.getPackageName(), null, null);
+                InfoMediaManager.createInstance(mContext, mContext.getPackageName(), null);
         assertThat(manager).isInstanceOf(RouterInfoMediaManager.class);
     }
 
     @Test
     @RequiresFlagsEnabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
     public void createInstance_withMR2FlagOn_withFakePackage_returnsNoOpInfoMediaManager() {
-        InfoMediaManager manager =
-                InfoMediaManager.createInstance(mContext, FAKE_PACKAGE, null, null);
+        InfoMediaManager manager = InfoMediaManager.createInstance(mContext, FAKE_PACKAGE, null);
         assertThat(manager).isInstanceOf(NoOpInfoMediaManager.class);
     }
 
     @Test
     @RequiresFlagsEnabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
     public void createInstance_withMR2FlagOn_withNullPackage_returnsRouterInfoMediaManager() {
-        InfoMediaManager manager = InfoMediaManager.createInstance(mContext, null, null, null);
+        InfoMediaManager manager = InfoMediaManager.createInstance(mContext, null, null);
         assertThat(manager).isInstanceOf(RouterInfoMediaManager.class);
     }
 
@@ -87,7 +86,7 @@
     @RequiresFlagsDisabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
     public void createInstance_withMR2FlagOff_returnsManagerInfoMediaManager() {
         InfoMediaManager manager =
-                InfoMediaManager.createInstance(mContext, mContext.getPackageName(), null, null);
+                InfoMediaManager.createInstance(mContext, mContext.getPackageName(), null);
         assertThat(manager).isInstanceOf(ManagerInfoMediaManager.class);
     }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index c159d5e..d85d253 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -91,7 +91,7 @@
     @Mock
     private LocalBluetoothManager mLocalBluetoothManager;
     @Mock
-    private MediaManager.MediaDeviceCallback mCallback;
+    private InfoMediaManager.MediaDeviceCallback mCallback;
     @Mock
     private MediaSessionManager mMediaSessionManager;
     @Mock
@@ -109,8 +109,7 @@
         doReturn(mMediaSessionManager).when(mContext).getSystemService(
                 Context.MEDIA_SESSION_SERVICE);
         mInfoMediaManager =
-                new ManagerInfoMediaManager(
-                        mContext, TEST_PACKAGE_NAME, null, mLocalBluetoothManager);
+                new ManagerInfoMediaManager(mContext, TEST_PACKAGE_NAME, mLocalBluetoothManager);
         mShadowRouter2Manager = ShadowRouter2Manager.getShadow();
         mInfoMediaManager.mRouterManager = MediaRouter2Manager.getInstance(mContext);
     }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
index 9a7d4f1..693b7d0 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
@@ -26,6 +26,7 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.withSettings;
 
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
@@ -35,7 +36,6 @@
 import android.media.AudioManager;
 import android.media.AudioSystem;
 import android.media.MediaRoute2Info;
-import android.media.MediaRouter2Manager;
 import android.media.RoutingSessionInfo;
 
 import com.android.settingslib.bluetooth.A2dpProfile;
@@ -75,8 +75,6 @@
     private static final String TEST_ADDRESS = "00:01:02:03:04:05";
 
     @Mock
-    private InfoMediaManager mInfoMediaManager;
-    @Mock
     private LocalBluetoothManager mLocalBluetoothManager;
     @Mock
     private LocalMediaManager.DeviceCallback mCallback;
@@ -87,8 +85,6 @@
     @Mock
     private LocalBluetoothProfileManager mLocalProfileManager;
     @Mock
-    private MediaRouter2Manager mMediaRouter2Manager;
-    @Mock
     private MediaRoute2Info mRouteInfo1;
     @Mock
     private MediaRoute2Info mRouteInfo2;
@@ -97,6 +93,7 @@
 
     private Context mContext;
     private LocalMediaManager mLocalMediaManager;
+    private InfoMediaManager mInfoMediaManager;
     private ShadowBluetoothAdapter mShadowBluetoothAdapter;
     private InfoMediaDevice mInfoMediaDevice1;
     private InfoMediaDevice mInfoMediaDevice2;
@@ -116,10 +113,16 @@
         when(mLocalProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile);
         when(mLocalProfileManager.getHearingAidProfile()).thenReturn(mHapProfile);
 
+        // Need to call constructor to initialize final fields.
+        mInfoMediaManager = mock(
+                InfoMediaManager.class,
+                withSettings().useConstructor(mContext, TEST_PACKAGE_NAME, mLocalBluetoothManager));
+
         mInfoMediaDevice1 = spy(new InfoMediaDevice(mContext, mRouteInfo1));
         mInfoMediaDevice2 = new InfoMediaDevice(mContext, mRouteInfo2);
-        mLocalMediaManager = new LocalMediaManager(mContext, mLocalBluetoothManager,
-                mInfoMediaManager, "com.test.packagename");
+        mLocalMediaManager =
+                new LocalMediaManager(
+                        mContext, mLocalBluetoothManager, mInfoMediaManager, TEST_PACKAGE_NAME);
         mLocalMediaManager.mAudioManager = mAudioManager;
     }
 
@@ -146,7 +149,6 @@
 
         mLocalMediaManager.registerCallback(mCallback);
         assertThat(mLocalMediaManager.connectDevice(device)).isTrue();
-
         verify(mInfoMediaManager).connectToDevice(device);
     }
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaManagerTest.java
deleted file mode 100644
index c3237f0..0000000
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaManagerTest.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.media;
-
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-
-import java.util.Collections;
-
-@RunWith(RobolectricTestRunner.class)
-public class MediaManagerTest {
-
-    private static final String TEST_ID = "test_id";
-
-    @Mock
-    private MediaManager.MediaDeviceCallback mCallback;
-    @Mock
-    private MediaDevice mDevice;
-
-    private MediaManager mMediaManager;
-    private Context mContext;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mContext = RuntimeEnvironment.application;
-
-        when(mDevice.getId()).thenReturn(TEST_ID);
-
-        mMediaManager = new MediaManager(mContext, null) {};
-    }
-
-    @Test
-    public void dispatchDeviceListAdded_registerCallback_shouldDispatchCallback() {
-        mMediaManager.registerCallback(mCallback);
-
-        mMediaManager.dispatchDeviceListAdded(Collections.emptyList());
-
-        verify(mCallback).onDeviceListAdded(any());
-    }
-
-    @Test
-    public void dispatchDeviceListRemoved_registerCallback_shouldDispatchCallback() {
-        mMediaManager.registerCallback(mCallback);
-
-        mMediaManager.dispatchDeviceListRemoved(Collections.emptyList());
-
-        verify(mCallback).onDeviceListRemoved(Collections.emptyList());
-    }
-
-    @Test
-    public void dispatchActiveDeviceChanged_registerCallback_shouldDispatchCallback() {
-        mMediaManager.registerCallback(mCallback);
-
-        mMediaManager.dispatchConnectedDeviceChanged(TEST_ID);
-
-        verify(mCallback).onConnectedDeviceChanged(TEST_ID);
-    }
-
-    @Test
-    public void dispatchOnRequestFailed_registerCallback_shouldDispatchCallback() {
-        mMediaManager.registerCallback(mCallback);
-
-        mMediaManager.dispatchOnRequestFailed(1);
-
-        verify(mCallback).onRequestFailed(1);
-    }
-
-}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index 3043d54..8f8445d 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -115,6 +115,7 @@
         Settings.Global.Wearable.SCREEN_UNLOCK_SOUND_ENABLED,
         Settings.Global.Wearable.CHARGING_SOUNDS_ENABLED,
         Settings.Global.Wearable.WRIST_DETECTION_AUTO_LOCKING_ENABLED,
+        Settings.Global.Wearable.AUTO_BEDTIME_MODE,
         Settings.Global.FORCE_ENABLE_PSS_PROFILING,
         Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_ENABLED,
         Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_TYPE,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index c0a0760..6def40b 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -452,6 +452,7 @@
         VALIDATORS.put(Global.Wearable.WRIST_DETECTION_AUTO_LOCKING_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(
                 Global.Wearable.CONSISTENT_NOTIFICATION_BLOCKING_ENABLED, ANY_INTEGER_VALIDATOR);
+        VALIDATORS.put(Global.Wearable.AUTO_BEDTIME_MODE, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.FORCE_ENABLE_PSS_PROFILING, BOOLEAN_VALIDATOR);
     }
 }
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 0c02f56..eb2d13d 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -566,9 +566,6 @@
     <!-- Permission required for CTS test - android.server.biometrics -->
     <uses-permission android:name="android.permission.SET_BIOMETRIC_DIALOG_LOGO" />
 
-    <!-- Permission required for CTS test - android.server.biometrics -->
-    <uses-permission android:name="android.permission.USE_BACKGROUND_FACE_AUTHENTICATION" />
-
     <!-- Permissions required for CTS test - NotificationManagerTest -->
     <uses-permission android:name="android.permission.MANAGE_NOTIFICATION_LISTENERS" />
 
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index baa1397..d05d40d 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -25,6 +25,16 @@
 }
 
 flag {
+   name: "notification_view_flipper_pausing"
+   namespace: "systemui"
+   description: "Pause ViewFlippers inside Notification custom layouts when the shade is closed."
+   bug: "309146176"
+   metadata {
+        purpose: PURPOSE_BUGFIX
+   }
+}
+
+flag {
     name: "notification_async_group_header_inflation"
     namespace: "systemui"
     description: "Inflates the notification group summary header views from the background thread."
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
index 8dc7495..b124025 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
@@ -27,6 +27,7 @@
 import android.text.Layout
 import android.util.LruCache
 import kotlin.math.roundToInt
+import android.util.Log
 
 private const val DEFAULT_ANIMATION_DURATION: Long = 300
 private const val TYPEFACE_CACHE_MAX_ENTRIES = 5
@@ -140,7 +141,6 @@
     }
 
     sealed class PositionedGlyph {
-
         /** Mutable X coordinate of the glyph position relative from drawing offset. */
         var x: Float = 0f
 
@@ -269,41 +269,53 @@
         duration: Long = -1L,
         interpolator: TimeInterpolator? = null,
         delay: Long = 0,
-        onAnimationEnd: Runnable? = null
+        onAnimationEnd: Runnable? = null,
+    ) = setTextStyleInternal(fvar, textSize, color, strokeWidth, animate, duration,
+        interpolator, delay, onAnimationEnd, updateLayoutOnFailure = true)
+
+    private fun setTextStyleInternal(
+        fvar: String?,
+        textSize: Float,
+        color: Int?,
+        strokeWidth: Float,
+        animate: Boolean,
+        duration: Long,
+        interpolator: TimeInterpolator?,
+        delay: Long,
+        onAnimationEnd: Runnable?,
+        updateLayoutOnFailure: Boolean,
     ) {
-        if (animate) {
-            animator.cancel()
-            textInterpolator.rebase()
-        }
+        try {
+            if (animate) {
+                animator.cancel()
+                textInterpolator.rebase()
+            }
 
-        if (textSize >= 0) {
-            textInterpolator.targetPaint.textSize = textSize
-        }
+            if (textSize >= 0) {
+                textInterpolator.targetPaint.textSize = textSize
+            }
+            if (!fvar.isNullOrBlank()) {
+                textInterpolator.targetPaint.typeface = typefaceCache.getTypefaceForVariant(fvar)
+            }
+            if (color != null) {
+                textInterpolator.targetPaint.color = color
+            }
+            if (strokeWidth >= 0F) {
+                textInterpolator.targetPaint.strokeWidth = strokeWidth
+            }
+            textInterpolator.onTargetPaintModified()
 
-        if (!fvar.isNullOrBlank()) {
-            textInterpolator.targetPaint.typeface = typefaceCache.getTypefaceForVariant(fvar)
-        }
-
-        if (color != null) {
-            textInterpolator.targetPaint.color = color
-        }
-        if (strokeWidth >= 0F) {
-            textInterpolator.targetPaint.strokeWidth = strokeWidth
-        }
-        textInterpolator.onTargetPaintModified()
-
-        if (animate) {
-            animator.startDelay = delay
-            animator.duration =
-                if (duration == -1L) {
-                    DEFAULT_ANIMATION_DURATION
-                } else {
-                    duration
-                }
-            interpolator?.let { animator.interpolator = it }
-            if (onAnimationEnd != null) {
-                val listener =
-                    object : AnimatorListenerAdapter() {
+            if (animate) {
+                animator.startDelay = delay
+                animator.duration =
+                    if (duration == -1L) {
+                        DEFAULT_ANIMATION_DURATION
+                    } else {
+                        duration
+                    }
+                interpolator?.let { animator.interpolator = it }
+                if (onAnimationEnd != null) {
+                    val listener = object : AnimatorListenerAdapter() {
                         override fun onAnimationEnd(animation: Animator) {
                             onAnimationEnd.run()
                             animator.removeListener(this)
@@ -312,14 +324,25 @@
                             animator.removeListener(this)
                         }
                     }
-                animator.addListener(listener)
+                    animator.addListener(listener)
+                }
+                animator.start()
+            } else {
+                // No animation is requested, thus set base and target state to the same state.
+                textInterpolator.progress = 1f
+                textInterpolator.rebase()
+                invalidateCallback()
             }
-            animator.start()
-        } else {
-            // No animation is requested, thus set base and target state to the same state.
-            textInterpolator.progress = 1f
-            textInterpolator.rebase()
-            invalidateCallback()
+        } catch (ex: IllegalArgumentException) {
+            if (updateLayoutOnFailure) {
+                Log.e(TAG, "setTextStyleInternal: Exception caught but retrying. This is usually" +
+                    " due to the layout having changed unexpectedly without being notified.", ex)
+                updateLayout(textInterpolator.layout)
+                setTextStyleInternal(fvar, textSize, color, strokeWidth, animate, duration,
+                    interpolator, delay, onAnimationEnd, updateLayoutOnFailure = false)
+            } else {
+                throw ex
+            }
         }
     }
 
@@ -355,15 +378,13 @@
         interpolator: TimeInterpolator? = null,
         delay: Long = 0,
         onAnimationEnd: Runnable? = null
-    ) {
-        val fvar = fontVariationUtils.updateFontVariation(
-            weight = weight,
-            width = width,
-            opticalSize = opticalSize,
-            roundness = roundness,
-        )
-        setTextStyle(
-            fvar = fvar,
+    ) = setTextStyleInternal(
+            fvar = fontVariationUtils.updateFontVariation(
+                weight = weight,
+                width = width,
+                opticalSize = opticalSize,
+                roundness = roundness,
+            ),
             textSize = textSize,
             color = color,
             strokeWidth = strokeWidth,
@@ -372,6 +393,10 @@
             interpolator = interpolator,
             delay = delay,
             onAnimationEnd = onAnimationEnd,
+            updateLayoutOnFailure = true,
         )
+
+    companion object {
+        private val TAG = TextAnimator::class.simpleName!!
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 515c816..078da1c86 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -31,8 +31,8 @@
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
 import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.ColumnScope
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
@@ -93,7 +93,6 @@
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.res.dimensionResource
-import androidx.compose.ui.res.painterResource
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.semantics.testTagsAsResourceId
@@ -119,7 +118,6 @@
 import com.android.systemui.communal.ui.compose.extensions.observeTapsWithoutConsuming
 import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
 import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
-import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 import com.android.systemui.communal.widgets.WidgetConfigurator
 import com.android.systemui.res.R
 import kotlinx.coroutines.launch
@@ -199,37 +197,26 @@
                     }
                 },
     ) {
-        Column(Modifier.align(Alignment.TopStart)) {
-            CommunalHubLazyGrid(
-                communalContent = communalContent,
-                viewModel = viewModel,
-                contentPadding = contentPadding,
-                contentOffset = contentOffset,
-                setGridCoordinates = { gridCoordinates = it },
-                updateDragPositionForRemove = { offset ->
-                    isDraggingToRemove =
-                        isPointerWithinCoordinates(
-                            offset = gridCoordinates?.let { it.positionInWindow() + offset },
-                            containerToCheck = removeButtonCoordinates
-                        )
-                    isDraggingToRemove
-                },
-                onOpenWidgetPicker = onOpenWidgetPicker,
-                gridState = gridState,
-                contentListState = contentListState,
-                selectedKey = selectedKey,
-                widgetConfigurator = widgetConfigurator,
-            )
-            // TODO(b/326060686): Remove this once keyguard indication area can persist over hub
-            if (viewModel is CommunalViewModel) {
-                val isUnlocked by viewModel.deviceUnlocked.collectAsState(initial = false)
-                Spacer(Modifier.height(24.dp))
-                LockStateIcon(
-                    isUnlocked = isUnlocked,
-                    modifier = Modifier.align(Alignment.CenterHorizontally),
-                )
-            }
-        }
+        CommunalHubLazyGrid(
+            communalContent = communalContent,
+            viewModel = viewModel,
+            contentPadding = contentPadding,
+            contentOffset = contentOffset,
+            setGridCoordinates = { gridCoordinates = it },
+            updateDragPositionForRemove = { offset ->
+                isDraggingToRemove =
+                    isPointerWithinCoordinates(
+                        offset = gridCoordinates?.let { it.positionInWindow() + offset },
+                        containerToCheck = removeButtonCoordinates
+                    )
+                isDraggingToRemove
+            },
+            onOpenWidgetPicker = onOpenWidgetPicker,
+            gridState = gridState,
+            contentListState = contentListState,
+            selectedKey = selectedKey,
+            widgetConfigurator = widgetConfigurator,
+        )
 
         if (viewModel.isEditMode && onOpenWidgetPicker != null && onEditDone != null) {
             Toolbar(
@@ -281,7 +268,7 @@
 
 @OptIn(ExperimentalFoundationApi::class)
 @Composable
-private fun ColumnScope.CommunalHubLazyGrid(
+private fun BoxScope.CommunalHubLazyGrid(
     communalContent: List<CommunalContentModel>,
     viewModel: BaseCommunalViewModel,
     contentPadding: PaddingValues,
@@ -295,7 +282,7 @@
     widgetConfigurator: WidgetConfigurator?,
 ) {
     var gridModifier =
-        Modifier.align(Alignment.Start).onGloballyPositioned { setGridCoordinates(it) }
+        Modifier.align(Alignment.TopStart).onGloballyPositioned { setGridCoordinates(it) }
     var list = communalContent
     var dragDropState: GridDragDropState? = null
     if (viewModel.isEditMode && viewModel is CommunalEditModeViewModel) {
@@ -377,26 +364,6 @@
     }
 }
 
-@Composable
-private fun LockStateIcon(
-    isUnlocked: Boolean,
-    modifier: Modifier = Modifier,
-) {
-    val colors = LocalAndroidColorScheme.current
-    val resource =
-        if (isUnlocked) {
-            R.drawable.ic_unlocked
-        } else {
-            R.drawable.ic_lock
-        }
-    Icon(
-        painter = painterResource(id = resource),
-        contentDescription = null,
-        tint = colors.onPrimaryContainer,
-        modifier = modifier.size(52.dp)
-    )
-}
-
 /**
  * Toolbar that contains action buttons to
  * 1) open the widget picker
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 8f802b8..563aad1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -39,7 +39,6 @@
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel.Companion.POPUP_AUTO_HIDE_TIMEOUT_MS
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
@@ -114,7 +113,6 @@
                 kosmos.communalInteractor,
                 kosmos.communalTutorialInteractor,
                 kosmos.shadeInteractor,
-                kosmos.deviceEntryInteractor,
                 mediaHost,
                 logcatLogBuffer("CommunalViewModelTest"),
             )
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/doman/interactor/BatterySaverTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/doman/interactor/BatterySaverTileDataInteractorTest.kt
new file mode 100644
index 0000000..c80eb13
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/doman/interactor/BatterySaverTileDataInteractorTest.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2024 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.qs.tiles.impl.battery.doman.interactor
+
+import android.os.UserHandle
+import android.platform.test.annotations.EnabledOnRavenwood
+import android.testing.LeakCheck
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.impl.battery.domain.interactor.BatterySaverTileDataInteractor
+import com.android.systemui.utils.leaks.FakeBatteryController
+import com.google.common.truth.Truth
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.toCollection
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@EnabledOnRavenwood
+@RunWith(AndroidJUnit4::class)
+class BatterySaverTileDataInteractorTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val testScope = kosmos.testScope
+    private val batteryController = FakeBatteryController(LeakCheck())
+    private val testUser = UserHandle.of(1)
+    private val underTest =
+        BatterySaverTileDataInteractor(testScope.testScheduler, batteryController)
+
+    @Test
+    fun availability_isTrue() =
+        testScope.runTest {
+            val availability = underTest.availability(testUser).toCollection(mutableListOf())
+
+            Truth.assertThat(availability).hasSize(1)
+            Truth.assertThat(availability.last()).isTrue()
+        }
+
+    @Test
+    fun tileData_matchesBatteryControllerPowerSaving() =
+        testScope.runTest {
+            val data by
+                collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+
+            runCurrent()
+            Truth.assertThat(data!!.isPowerSaving).isFalse()
+
+            batteryController.setPowerSaveMode(true)
+            runCurrent()
+            Truth.assertThat(data!!.isPowerSaving).isTrue()
+
+            batteryController.setPowerSaveMode(false)
+            runCurrent()
+            Truth.assertThat(data!!.isPowerSaving).isFalse()
+        }
+
+    @Test
+    fun tileData_matchesBatteryControllerIsPluggedIn() =
+        testScope.runTest {
+            val data by
+                collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+
+            runCurrent()
+            Truth.assertThat(data!!.isPluggedIn).isFalse()
+
+            batteryController.isPluggedIn = true
+            runCurrent()
+            Truth.assertThat(data!!.isPluggedIn).isTrue()
+
+            batteryController.isPluggedIn = false
+            runCurrent()
+            Truth.assertThat(data!!.isPluggedIn).isFalse()
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/doman/interactor/BatterySaverTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/doman/interactor/BatterySaverTileUserActionInteractorTest.kt
new file mode 100644
index 0000000..62c51e6
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/doman/interactor/BatterySaverTileUserActionInteractorTest.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 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.qs.tiles.impl.battery.doman.interactor
+
+import android.platform.test.annotations.EnabledOnRavenwood
+import android.provider.Settings
+import android.testing.LeakCheck
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
+import com.android.systemui.qs.tiles.impl.battery.domain.interactor.BatterySaverTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.battery.domain.model.BatterySaverTileModel
+import com.android.systemui.utils.leaks.FakeBatteryController
+import com.google.common.truth.Truth
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@EnabledOnRavenwood
+@RunWith(AndroidJUnit4::class)
+class BatterySaverTileUserActionInteractorTest : SysuiTestCase() {
+    private val inputHandler = FakeQSTileIntentUserInputHandler()
+    private val controller = FakeBatteryController(LeakCheck())
+    private val underTest = BatterySaverTileUserActionInteractor(inputHandler, controller)
+
+    @Test
+    fun handleClickWhenNotPluggedIn_flipsPowerSaverMode() = runTest {
+        val originalPowerSaveMode = controller.isPowerSave
+        controller.isPluggedIn = false
+
+        underTest.handleInput(
+            QSTileInputTestKtx.click(BatterySaverTileModel.Standard(false, originalPowerSaveMode))
+        )
+
+        Truth.assertThat(controller.isPowerSave).isNotEqualTo(originalPowerSaveMode)
+    }
+
+    @Test
+    fun handleClickWhenPluggedIn_doesNotTurnOnPowerSaverMode() = runTest {
+        controller.setPowerSaveMode(false)
+        val originalPowerSaveMode = controller.isPowerSave
+        controller.isPluggedIn = true
+
+        underTest.handleInput(
+            QSTileInputTestKtx.click(
+                BatterySaverTileModel.Standard(controller.isPluggedIn, originalPowerSaveMode)
+            )
+        )
+
+        Truth.assertThat(controller.isPowerSave).isEqualTo(originalPowerSaveMode)
+    }
+
+    @Test
+    fun handleLongClick() = runTest {
+        underTest.handleInput(
+            QSTileInputTestKtx.longClick(BatterySaverTileModel.Standard(false, false))
+        )
+
+        QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+            Truth.assertThat(it.intent.action).isEqualTo(Settings.ACTION_BATTERY_SAVER_SETTINGS)
+        }
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt
new file mode 100644
index 0000000..6e9db2c
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2024 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.qs.tiles.impl.battery.ui
+
+import android.graphics.drawable.TestStubDrawable
+import android.widget.Switch
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.impl.battery.domain.model.BatterySaverTileModel
+import com.android.systemui.qs.tiles.impl.battery.qsBatterySaverTileConfig
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BatterySaverTileMapperTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val batterySaverTileConfig = kosmos.qsBatterySaverTileConfig
+    private lateinit var mapper: BatterySaverTileMapper
+
+    @Before
+    fun setup() {
+        mapper =
+            BatterySaverTileMapper(
+                context.orCreateTestableResources
+                    .apply {
+                        addOverride(R.drawable.qs_battery_saver_icon_off, TestStubDrawable())
+                        addOverride(R.drawable.qs_battery_saver_icon_on, TestStubDrawable())
+                    }
+                    .resources,
+                context.theme,
+            )
+    }
+
+    @Test
+    fun map_standard_notPluggedInNotPowerSaving() {
+        val inputModel = BatterySaverTileModel.Standard(false, false)
+
+        val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+        val expectedState =
+            createBatterySaverTileState(
+                QSTileState.ActivationState.INACTIVE,
+                "",
+                R.drawable.qs_battery_saver_icon_off,
+                null,
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun map_standard_notPluggedInPowerSaving() {
+        val inputModel = BatterySaverTileModel.Standard(false, true)
+
+        val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+        val expectedState =
+            createBatterySaverTileState(
+                QSTileState.ActivationState.ACTIVE,
+                "",
+                R.drawable.qs_battery_saver_icon_on,
+                null,
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun map_standard_pluggedInPowerSaving() {
+        val inputModel = BatterySaverTileModel.Standard(true, true)
+
+        val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+        val expectedState =
+            createBatterySaverTileState(
+                QSTileState.ActivationState.UNAVAILABLE,
+                "",
+                R.drawable.qs_battery_saver_icon_on,
+                null,
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun map_standard_pluggedInNotPowerSaving() {
+        val inputModel = BatterySaverTileModel.Standard(true, false)
+
+        val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+        val expectedState =
+            createBatterySaverTileState(
+                QSTileState.ActivationState.UNAVAILABLE,
+                "",
+                R.drawable.qs_battery_saver_icon_off,
+                null,
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun map_extremeSaverDisabledNotPluggedInNotPowerSaving() {
+        val inputModel = BatterySaverTileModel.Extreme(false, false, false)
+
+        val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+        val expectedState =
+            createBatterySaverTileState(
+                QSTileState.ActivationState.INACTIVE,
+                "",
+                R.drawable.qs_battery_saver_icon_off,
+                null,
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun map_extremeSaverDisabledNotPluggedInPowerSaving() {
+        val inputModel = BatterySaverTileModel.Extreme(false, true, false)
+
+        val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+        val expectedState =
+            createBatterySaverTileState(
+                QSTileState.ActivationState.ACTIVE,
+                context.getString(R.string.standard_battery_saver_text),
+                R.drawable.qs_battery_saver_icon_on,
+                context.getString(R.string.standard_battery_saver_text),
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun map_extremeSaverDisabledPluggedInPowerSaving() {
+        val inputModel = BatterySaverTileModel.Extreme(true, true, false)
+
+        val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+        val expectedState =
+            createBatterySaverTileState(
+                QSTileState.ActivationState.UNAVAILABLE,
+                "",
+                R.drawable.qs_battery_saver_icon_on,
+                null,
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun map_extremeSaverDisabledPluggedInNotPowerSaving() {
+        val inputModel = BatterySaverTileModel.Extreme(true, false, false)
+
+        val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+        val expectedState =
+            createBatterySaverTileState(
+                QSTileState.ActivationState.UNAVAILABLE,
+                "",
+                R.drawable.qs_battery_saver_icon_off,
+                null,
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun map_extremeSaverEnabledNotPluggedInNotPowerSaving() {
+        val inputModel = BatterySaverTileModel.Extreme(false, false, true)
+
+        val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+        val expectedState =
+            createBatterySaverTileState(
+                QSTileState.ActivationState.INACTIVE,
+                "",
+                R.drawable.qs_battery_saver_icon_off,
+                null,
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun map_extremeSaverEnabledNotPluggedInPowerSaving() {
+        val inputModel = BatterySaverTileModel.Extreme(false, true, true)
+
+        val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+        val expectedState =
+            createBatterySaverTileState(
+                QSTileState.ActivationState.ACTIVE,
+                context.getString(R.string.extreme_battery_saver_text),
+                R.drawable.qs_battery_saver_icon_on,
+                context.getString(R.string.extreme_battery_saver_text),
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun map_extremeSaverEnabledPluggedInPowerSaving() {
+        val inputModel = BatterySaverTileModel.Extreme(true, true, true)
+
+        val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+        val expectedState =
+            createBatterySaverTileState(
+                QSTileState.ActivationState.UNAVAILABLE,
+                "",
+                R.drawable.qs_battery_saver_icon_on,
+                null,
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun map_extremeSaverEnabledPluggedInNotPowerSaving() {
+        val inputModel = BatterySaverTileModel.Extreme(true, false, true)
+
+        val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+        val expectedState =
+            createBatterySaverTileState(
+                QSTileState.ActivationState.UNAVAILABLE,
+                "",
+                R.drawable.qs_battery_saver_icon_off,
+                null,
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    private fun createBatterySaverTileState(
+        activationState: QSTileState.ActivationState,
+        secondaryLabel: String,
+        iconRes: Int,
+        stateDescription: CharSequence?,
+    ): QSTileState {
+        val label = context.getString(R.string.battery_detail_switch_title)
+        return QSTileState(
+            { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+            label,
+            activationState,
+            secondaryLabel,
+            if (activationState == QSTileState.ActivationState.UNAVAILABLE)
+                setOf(QSTileState.UserAction.LONG_CLICK)
+            else setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
+            label,
+            stateDescription,
+            QSTileState.SideViewIcon.None,
+            QSTileState.EnabledState.ENABLED,
+            Switch::class.qualifiedName
+        )
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt
new file mode 100644
index 0000000..39755bf
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2024 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.qs.tiles.impl.internet.domain
+
+import android.graphics.drawable.TestStubDrawable
+import android.widget.Switch
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel
+import com.android.systemui.qs.tiles.impl.internet.qsInternetTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class InternetTileMapperTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val internetTileConfig = kosmos.qsInternetTileConfig
+    private val mapper by lazy {
+        InternetTileMapper(
+            context.orCreateTestableResources
+                .apply {
+                    addOverride(R.drawable.ic_qs_no_internet_unavailable, TestStubDrawable())
+                    addOverride(wifiRes, TestStubDrawable())
+                }
+                .resources,
+            context.theme,
+            context
+        )
+    }
+
+    @Test
+    fun withActiveModel_mappedStateMatchesDataModel() {
+        val inputModel =
+            InternetTileModel.Active(
+                secondaryLabel = Text.Resource(R.string.quick_settings_networks_available),
+                iconId = wifiRes,
+                stateDescription = null,
+                contentDescription =
+                    ContentDescription.Resource(R.string.quick_settings_internet_label),
+            )
+
+        val outputState = mapper.map(internetTileConfig, inputModel)
+
+        val expectedState =
+            createInternetTileState(
+                QSTileState.ActivationState.ACTIVE,
+                context.getString(R.string.quick_settings_networks_available),
+                Icon.Loaded(context.getDrawable(wifiRes)!!, contentDescription = null),
+                context.getString(R.string.quick_settings_internet_label)
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun withInactiveModel_mappedStateMatchesDataModel() {
+        val inputModel =
+            InternetTileModel.Inactive(
+                secondaryLabel = Text.Resource(R.string.quick_settings_networks_unavailable),
+                iconId = R.drawable.ic_qs_no_internet_unavailable,
+                stateDescription = null,
+                contentDescription =
+                    ContentDescription.Resource(R.string.quick_settings_networks_unavailable),
+            )
+
+        val outputState = mapper.map(internetTileConfig, inputModel)
+
+        val expectedState =
+            createInternetTileState(
+                QSTileState.ActivationState.INACTIVE,
+                context.getString(R.string.quick_settings_networks_unavailable),
+                Icon.Loaded(
+                    context.getDrawable(R.drawable.ic_qs_no_internet_unavailable)!!,
+                    contentDescription = null
+                ),
+                context.getString(R.string.quick_settings_networks_unavailable)
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    private fun createInternetTileState(
+        activationState: QSTileState.ActivationState,
+        secondaryLabel: String,
+        icon: Icon,
+        contentDescription: String,
+    ): QSTileState {
+        val label = context.getString(R.string.quick_settings_internet_label)
+        return QSTileState(
+            { icon },
+            label,
+            activationState,
+            secondaryLabel,
+            setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
+            contentDescription,
+            null,
+            QSTileState.SideViewIcon.Chevron,
+            QSTileState.EnabledState.ENABLED,
+            Switch::class.qualifiedName
+        )
+    }
+
+    private companion object {
+        val wifiRes = WIFI_FULL_ICONS[4]
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt
new file mode 100644
index 0000000..37ef6ad
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt
@@ -0,0 +1,548 @@
+/*
+ * Copyright (C) 2024 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.qs.tiles.impl.internet.domain.interactor
+
+import android.graphics.drawable.TestStubDrawable
+import android.os.UserHandle
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.AccessibilityContentDescriptions
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.common.shared.model.Text.Companion.loadText
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.connectivity.WifiIcons
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.ethernet.domain.EthernetInteractor
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorImpl
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
+import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository
+import com.android.systemui.util.CarrierConfigTracker
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@TestableLooper.RunWithLooper
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class InternetTileDataInteractorTest : SysuiTestCase() {
+    private val testUser = UserHandle.of(1)
+    private val kosmos = Kosmos()
+    private val testScope = kosmos.testScope
+
+    private lateinit var underTest: InternetTileDataInteractor
+    private lateinit var mobileIconsInteractor: MobileIconsInteractor
+
+    private val airplaneModeRepository = FakeAirplaneModeRepository()
+    private val connectivityRepository = FakeConnectivityRepository()
+    private val ethernetInteractor = EthernetInteractor(connectivityRepository)
+    private val wifiRepository = FakeWifiRepository()
+    private val userSetupRepo = FakeUserSetupRepository()
+    private val wifiInteractor =
+        WifiInteractorImpl(connectivityRepository, wifiRepository, testScope.backgroundScope)
+
+    private val tableLogBuffer: TableLogBuffer = mock()
+    private val carrierConfigTracker: CarrierConfigTracker = mock()
+
+    private val mobileConnectionsRepository =
+        FakeMobileConnectionsRepository(FakeMobileMappingsProxy(), tableLogBuffer)
+    private val mobileConnectionRepository =
+        FakeMobileConnectionRepository(SUB_1_ID, tableLogBuffer)
+
+    private val flags =
+        FakeFeatureFlagsClassic().also {
+            it.set(Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS, true)
+        }
+
+    private val internet = context.getString(R.string.quick_settings_internet_label)
+
+    @Before
+    fun setUp() {
+        mobileConnectionRepository.apply {
+            setNetworkTypeKey(mobileConnectionsRepository.GSM_KEY)
+            isInService.value = true
+            dataConnectionState.value = DataConnectionState.Connected
+            dataEnabled.value = true
+        }
+
+        mobileConnectionsRepository.apply {
+            activeMobileDataRepository.value = mobileConnectionRepository
+            activeMobileDataSubscriptionId.value = SUB_1_ID
+            setMobileConnectionRepositoryMap(mapOf(SUB_1_ID to mobileConnectionRepository))
+        }
+
+        mobileIconsInteractor =
+            MobileIconsInteractorImpl(
+                mobileConnectionsRepository,
+                carrierConfigTracker,
+                tableLogBuffer,
+                connectivityRepository,
+                userSetupRepo,
+                testScope.backgroundScope,
+                context,
+                flags,
+            )
+
+        context.orCreateTestableResources.apply {
+            addOverride(com.android.internal.R.drawable.ic_signal_cellular, TestStubDrawable())
+            addOverride(
+                com.android.settingslib.R.drawable.ic_no_internet_wifi_signal_0,
+                TestStubDrawable()
+            )
+            addOverride(
+                com.android.settingslib.R.drawable.ic_no_internet_wifi_signal_1,
+                TestStubDrawable()
+            )
+            addOverride(
+                com.android.settingslib.R.drawable.ic_no_internet_wifi_signal_2,
+                TestStubDrawable()
+            )
+            addOverride(
+                com.android.settingslib.R.drawable.ic_no_internet_wifi_signal_3,
+                TestStubDrawable()
+            )
+            addOverride(
+                com.android.settingslib.R.drawable.ic_no_internet_wifi_signal_4,
+                TestStubDrawable()
+            )
+            addOverride(com.android.settingslib.R.drawable.ic_hotspot_phone, TestStubDrawable())
+            addOverride(com.android.settingslib.R.drawable.ic_hotspot_laptop, TestStubDrawable())
+            addOverride(com.android.settingslib.R.drawable.ic_hotspot_tablet, TestStubDrawable())
+            addOverride(com.android.settingslib.R.drawable.ic_hotspot_watch, TestStubDrawable())
+            addOverride(com.android.settingslib.R.drawable.ic_hotspot_auto, TestStubDrawable())
+        }
+
+        underTest =
+            InternetTileDataInteractor(
+                context,
+                testScope.backgroundScope,
+                airplaneModeRepository,
+                connectivityRepository,
+                ethernetInteractor,
+                mobileIconsInteractor,
+                wifiInteractor,
+            )
+    }
+
+    @Test
+    fun noDefault_noNetworksAvailable() =
+        testScope.runTest {
+            val latest by
+                collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+
+            connectivityRepository.defaultConnections.value = DefaultConnectionModel()
+
+            assertThat(latest?.secondaryLabel)
+                .isEqualTo(Text.Resource(R.string.quick_settings_networks_unavailable))
+            assertThat(latest?.iconId).isEqualTo(R.drawable.ic_qs_no_internet_unavailable)
+        }
+
+    @Test
+    fun noDefault_networksAvailable() =
+        testScope.runTest {
+            // TODO(b/328419203): support [WifiInteractor.areNetworksAvailable]
+        }
+
+    @Test
+    fun wifiDefaultAndActive() =
+        testScope.runTest {
+            val latest by
+                collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+
+            val networkModel =
+                WifiNetworkModel.Active(
+                    networkId = 1,
+                    level = 4,
+                    ssid = "test ssid",
+                )
+            val wifiIcon =
+                WifiIcon.fromModel(model = networkModel, context = context, showHotspotInfo = true)
+                    as WifiIcon.Visible
+
+            connectivityRepository.setWifiConnected()
+            wifiRepository.setIsWifiDefault(true)
+            wifiRepository.setWifiNetwork(networkModel)
+
+            assertThat(latest?.secondaryTitle).isEqualTo("test ssid")
+            assertThat(latest?.secondaryLabel).isNull()
+            val expectedIcon =
+                Icon.Loaded(context.getDrawable(WifiIcons.WIFI_NO_INTERNET_ICONS[4])!!, null)
+
+            val actualIcon = latest?.icon
+            assertThat(actualIcon).isEqualTo(expectedIcon)
+            assertThat(latest?.iconId).isNull()
+            assertThat(latest?.contentDescription.loadContentDescription(context))
+                .isEqualTo("$internet,test ssid")
+            val expectedSd = wifiIcon.contentDescription
+            assertThat(latest?.stateDescription).isEqualTo(expectedSd)
+        }
+
+    @Test
+    fun wifiDefaultAndActive_hotspotNone() =
+        testScope.runTest {
+            val latest by
+                collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+
+            val networkModel =
+                WifiNetworkModel.Active(
+                    networkId = 1,
+                    level = 4,
+                    ssid = "test ssid",
+                    hotspotDeviceType = WifiNetworkModel.HotspotDeviceType.NONE,
+                )
+
+            connectivityRepository.setWifiConnected()
+            wifiRepository.setIsWifiDefault(true)
+            wifiRepository.setWifiNetwork(networkModel)
+
+            val expectedIcon =
+                Icon.Loaded(context.getDrawable(WifiIcons.WIFI_NO_INTERNET_ICONS[4])!!, null)
+            assertThat(latest?.icon).isEqualTo(expectedIcon)
+            assertThat(latest?.stateDescription.loadContentDescription(context))
+                .doesNotContain(
+                    context.getString(AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION)
+                )
+        }
+
+    @Test
+    fun wifiDefaultAndActive_hotspotTablet() =
+        testScope.runTest {
+            val latest by
+                collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+
+            setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.TABLET)
+
+            val expectedIcon =
+                Icon.Loaded(
+                    context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_tablet)!!,
+                    null
+                )
+            assertThat(latest?.icon).isEqualTo(expectedIcon)
+            assertThat(latest?.stateDescription.loadContentDescription(context))
+                .isEqualTo(
+                    context.getString(AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION)
+                )
+        }
+
+    @Test
+    fun wifiDefaultAndActive_hotspotLaptop() =
+        testScope.runTest {
+            val latest by
+                collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+
+            setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.LAPTOP)
+
+            val expectedIcon =
+                Icon.Loaded(
+                    context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_laptop)!!,
+                    null
+                )
+            assertThat(latest?.icon).isEqualTo(expectedIcon)
+            assertThat(latest?.stateDescription.loadContentDescription(context))
+                .isEqualTo(
+                    context.getString(AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION)
+                )
+        }
+
+    @Test
+    fun wifiDefaultAndActive_hotspotWatch() =
+        testScope.runTest {
+            val latest by
+                collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+
+            setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.WATCH)
+
+            val expectedIcon =
+                Icon.Loaded(
+                    context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_watch)!!,
+                    null
+                )
+            assertThat(latest?.icon).isEqualTo(expectedIcon)
+            assertThat(latest?.stateDescription.loadContentDescription(context))
+                .isEqualTo(
+                    context.getString(AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION)
+                )
+        }
+
+    @Test
+    fun wifiDefaultAndActive_hotspotAuto() =
+        testScope.runTest {
+            val latest by
+                collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+
+            setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.AUTO)
+
+            val expectedIcon =
+                Icon.Loaded(
+                    context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_auto)!!,
+                    null
+                )
+            assertThat(latest?.icon).isEqualTo(expectedIcon)
+            assertThat(latest?.stateDescription.loadContentDescription(context))
+                .isEqualTo(
+                    context.getString(AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION)
+                )
+        }
+
+    @Test
+    fun wifiDefaultAndActive_hotspotPhone() =
+        testScope.runTest {
+            val latest by
+                collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+            setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.PHONE)
+
+            val expectedIcon =
+                Icon.Loaded(
+                    context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_phone)!!,
+                    null
+                )
+            assertThat(latest?.icon).isEqualTo(expectedIcon)
+            assertThat(latest?.stateDescription.loadContentDescription(context))
+                .isEqualTo(
+                    context.getString(AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION)
+                )
+        }
+
+    @Test
+    fun wifiDefaultAndActive_hotspotUnknown() =
+        testScope.runTest {
+            val latest by
+                collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+
+            setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.UNKNOWN)
+
+            val expectedIcon =
+                Icon.Loaded(
+                    context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_phone)!!,
+                    null
+                )
+            assertThat(latest?.icon).isEqualTo(expectedIcon)
+            assertThat(latest?.stateDescription.loadContentDescription(context))
+                .isEqualTo(
+                    context.getString(AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION)
+                )
+        }
+
+    @Test
+    fun wifiDefaultAndActive_hotspotInvalid() =
+        testScope.runTest {
+            val latest by
+                collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+
+            setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.INVALID)
+
+            val expectedIcon =
+                Icon.Loaded(
+                    context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_phone)!!,
+                    null
+                )
+            assertThat(latest?.icon).isEqualTo(expectedIcon)
+            assertThat(latest?.stateDescription.loadContentDescription(context))
+                .isEqualTo(
+                    context.getString(AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION)
+                )
+        }
+
+    @Test
+    fun wifiDefaultAndNotActive_noNetworksAvailable() =
+        testScope.runTest {
+            val latest by
+                collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+            val networkModel = WifiNetworkModel.Inactive
+
+            connectivityRepository.setWifiConnected(validated = false)
+            wifiRepository.setIsWifiDefault(true)
+            wifiRepository.setWifiNetwork(networkModel)
+            wifiRepository.wifiScanResults.value = emptyList()
+
+            assertThat(latest).isEqualTo(NOT_CONNECTED_NETWORKS_UNAVAILABLE)
+        }
+
+    @Test
+    fun wifiDefaultAndNotActive_networksAvailable() =
+        testScope.runTest {
+            val latest by
+                collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+
+            val networkModel = WifiNetworkModel.Inactive
+
+            connectivityRepository.setWifiConnected(validated = false)
+            wifiRepository.setIsWifiDefault(true)
+            wifiRepository.setWifiNetwork(networkModel)
+            wifiRepository.wifiScanResults.value = listOf(WifiScanEntry("test 1"))
+
+            assertThat(latest?.secondaryLabel).isNull()
+            assertThat(latest?.secondaryTitle)
+                .isEqualTo(context.getString(R.string.quick_settings_networks_available))
+            assertThat(latest?.icon).isNull()
+            assertThat(latest?.iconId).isEqualTo(R.drawable.ic_qs_no_internet_available)
+            assertThat(latest?.stateDescription).isNull()
+            val expectedCd =
+                "$internet,${context.getString(R.string.quick_settings_networks_available)}"
+            assertThat(latest?.contentDescription.loadContentDescription(context))
+                .isEqualTo(expectedCd)
+        }
+
+    @Test
+    fun mobileDefault_usesNetworkNameAndIcon() =
+        testScope.runTest {
+            val latest by
+                collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+
+            connectivityRepository.setMobileConnected()
+            mobileConnectionsRepository.mobileIsDefault.value = true
+            mobileConnectionRepository.apply {
+                setAllLevels(3)
+                setAllRoaming(false)
+                networkName.value = NetworkNameModel.Default("test network")
+            }
+
+            assertThat(latest).isNotNull()
+            assertThat(latest?.secondaryTitle).isNotNull()
+            assertThat(latest?.secondaryTitle.toString()).contains("test network")
+            assertThat(latest?.secondaryLabel).isNull()
+            assertThat(latest?.icon).isInstanceOf(Icon.Loaded::class.java)
+            assertThat(latest?.iconId).isNull()
+            assertThat(latest?.stateDescription.loadContentDescription(context))
+                .isEqualTo(latest?.secondaryTitle.toString())
+            assertThat(latest?.contentDescription.loadContentDescription(context))
+                .isEqualTo(internet)
+        }
+
+    @Test
+    fun ethernetDefault_validated_matchesInteractor() =
+        testScope.runTest {
+            val latest by
+                collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+            val ethernetIcon by collectLastValue(ethernetInteractor.icon)
+
+            connectivityRepository.setEthernetConnected(default = true, validated = true)
+
+            assertThat(latest?.secondaryLabel.loadText(context))
+                .isEqualTo(ethernetIcon!!.contentDescription.loadContentDescription(context))
+            assertThat(latest?.secondaryTitle).isNull()
+            assertThat(latest?.iconId).isEqualTo(R.drawable.stat_sys_ethernet_fully)
+            assertThat(latest?.icon).isNull()
+            assertThat(latest?.stateDescription).isNull()
+            assertThat(latest?.contentDescription.loadContentDescription(context))
+                .isEqualTo(latest?.secondaryLabel.loadText(context))
+        }
+
+    @Test
+    fun ethernetDefault_notValidated_matchesInteractor() =
+        testScope.runTest {
+            val latest by
+                collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+            val ethernetIcon by collectLastValue(ethernetInteractor.icon)
+
+            connectivityRepository.setEthernetConnected(default = true, validated = false)
+
+            assertThat(latest?.secondaryLabel.loadText(context))
+                .isEqualTo(ethernetIcon!!.contentDescription.loadContentDescription(context))
+            assertThat(latest?.secondaryTitle).isNull()
+            assertThat(latest?.iconId).isEqualTo(R.drawable.stat_sys_ethernet)
+            assertThat(latest?.icon).isNull()
+            assertThat(latest?.stateDescription).isNull()
+            assertThat(latest?.contentDescription.loadContentDescription(context))
+                .isEqualTo(latest?.secondaryLabel.loadText(context))
+        }
+
+    private fun setWifiNetworkWithHotspot(hotspot: WifiNetworkModel.HotspotDeviceType) {
+        val networkModel =
+            WifiNetworkModel.Active(
+                networkId = 1,
+                level = 4,
+                ssid = "test ssid",
+                hotspotDeviceType = hotspot,
+            )
+
+        connectivityRepository.setWifiConnected()
+        wifiRepository.setIsWifiDefault(true)
+        wifiRepository.setWifiNetwork(networkModel)
+    }
+
+    private companion object {
+        const val SUB_1_ID = 1
+
+        val NOT_CONNECTED_NETWORKS_UNAVAILABLE =
+            InternetTileModel.Inactive(
+                secondaryLabel = Text.Resource(R.string.quick_settings_networks_unavailable),
+                iconId = R.drawable.ic_qs_no_internet_unavailable,
+                stateDescription = null,
+                contentDescription =
+                    ContentDescription.Resource(R.string.quick_settings_networks_unavailable),
+            )
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt
new file mode 100644
index 0000000..e1f3d97
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2024 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.qs.tiles.impl.internet.domain.interactor
+
+import android.platform.test.annotations.EnabledOnRavenwood
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
+import com.android.systemui.qs.tiles.dialog.InternetDialogManager
+import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel
+import com.android.systemui.statusbar.connectivity.AccessPointController
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.nullable
+import com.google.common.truth.Truth
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+
+@SmallTest
+@EnabledOnRavenwood
+@RunWith(AndroidJUnit4::class)
+class InternetTileUserActionInteractorTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val inputHandler = FakeQSTileIntentUserInputHandler()
+
+    private lateinit var underTest: InternetTileUserActionInteractor
+
+    @Mock private lateinit var internetDialogManager: InternetDialogManager
+    @Mock private lateinit var controller: AccessPointController
+
+    @Before
+    fun setup() {
+        internetDialogManager = mock<InternetDialogManager>()
+        controller = mock<AccessPointController>()
+
+        underTest =
+            InternetTileUserActionInteractor(
+                kosmos.testScope.coroutineContext,
+                internetDialogManager,
+                controller,
+                inputHandler,
+            )
+    }
+
+    @Test
+    fun handleClickWhenActive() =
+        kosmos.testScope.runTest {
+            val input = InternetTileModel.Active()
+
+            underTest.handleInput(QSTileInputTestKtx.click(input))
+
+            verify(internetDialogManager).create(eq(true), anyBoolean(), anyBoolean(), nullable())
+        }
+
+    @Test
+    fun handleClickWhenInactive() =
+        kosmos.testScope.runTest {
+            val input = InternetTileModel.Inactive()
+
+            underTest.handleInput(QSTileInputTestKtx.click(input))
+
+            verify(internetDialogManager).create(eq(true), anyBoolean(), anyBoolean(), nullable())
+        }
+
+    @Test
+    fun handleLongClickWhenActive() =
+        kosmos.testScope.runTest {
+            val input = InternetTileModel.Active()
+
+            underTest.handleInput(QSTileInputTestKtx.longClick(input))
+
+            QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+                Truth.assertThat(it.intent.action).isEqualTo(Settings.ACTION_WIFI_SETTINGS)
+            }
+        }
+
+    @Test
+    fun handleLongClickWhenInactive() =
+        kosmos.testScope.runTest {
+            val input = InternetTileModel.Inactive()
+
+            underTest.handleInput(QSTileInputTestKtx.longClick(input))
+
+            QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+                Truth.assertThat(it.intent.action).isEqualTo(Settings.ACTION_WIFI_SETTINGS)
+            }
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index d505b27..7fabe33 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -25,6 +25,7 @@
 import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS;
 import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS;
 import static android.app.Flags.FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS;
+import static android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES;
 import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE;
 import static android.os.UserHandle.USER_ALL;
 import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS;
@@ -115,7 +116,8 @@
     public static List<FlagsParameterization> getParams() {
         return FlagsParameterization.allCombinationsOf(
                 FLAG_ALLOW_PRIVATE_PROFILE,
-                FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS);
+                FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS,
+                FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
     }
 
     public NotificationLockscreenUserManagerTest(FlagsParameterization flags) {
@@ -872,7 +874,7 @@
     }
 
     @Test
-    @EnableFlags(FLAG_ALLOW_PRIVATE_PROFILE)
+    @EnableFlags({FLAG_ALLOW_PRIVATE_PROFILE, FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
     public void testProfileAvailabilityIntent() {
         mLockscreenUserManager.mCurrentProfiles.clear();
         assertEquals(0, mLockscreenUserManager.mCurrentProfiles.size());
@@ -883,7 +885,7 @@
     }
 
     @Test
-    @EnableFlags(FLAG_ALLOW_PRIVATE_PROFILE)
+    @EnableFlags({FLAG_ALLOW_PRIVATE_PROFILE, FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
     public void testProfileUnAvailabilityIntent() {
         mLockscreenUserManager.mCurrentProfiles.clear();
         assertEquals(0, mLockscreenUserManager.mCurrentProfiles.size());
@@ -894,7 +896,7 @@
     }
 
     @Test
-    @DisableFlags(FLAG_ALLOW_PRIVATE_PROFILE)
+    @DisableFlags({FLAG_ALLOW_PRIVATE_PROFILE, FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
     public void testManagedProfileAvailabilityIntent() {
         mLockscreenUserManager.mCurrentProfiles.clear();
         mLockscreenUserManager.mCurrentManagedProfiles.clear();
@@ -908,7 +910,7 @@
     }
 
     @Test
-    @DisableFlags(FLAG_ALLOW_PRIVATE_PROFILE)
+    @DisableFlags({FLAG_ALLOW_PRIVATE_PROFILE, FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
     public void testManagedProfileUnAvailabilityIntent() {
         mLockscreenUserManager.mCurrentProfiles.clear();
         mLockscreenUserManager.mCurrentManagedProfiles.clear();
diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml
index 61f69c0..f6042e4 100644
--- a/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml
+++ b/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml
@@ -128,7 +128,8 @@
         android:layout_marginStart="49dp"
         android:layout_marginEnd="49dp"
         android:overScrollMode="never"
-        android:layout_marginBottom="16dp">
+        android:layout_marginBottom="16dp"
+        android:scrollbars="none">
         <LinearLayout
             android:id="@+id/keyboard_shortcuts_container"
             android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index e181d07..35f6a08 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -345,9 +345,6 @@
          the notification is not swiped enough to dismiss it. -->
     <bool name="config_showNotificationGear">true</bool>
 
-    <!-- Whether or not a background should be drawn behind a notification. -->
-    <bool name="config_drawNotificationBackground">false</bool>
-
     <!-- Whether or the notifications can be shown and dismissed with a drag. -->
     <bool name="config_enableNotificationShadeDrag">true</bool>
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index b713417..5dbdd18 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -75,6 +75,11 @@
     <!-- Battery saver notification dismiss action: Do not turn on battery saver. [CHAR LIMIT=NONE]-->
     <string name="battery_saver_dismiss_action">No thanks</string>
 
+    <!-- Secondary label for Battery Saver tile when Battery Saver is enabled. [CHAR LIMIT=20] -->
+    <string name="standard_battery_saver_text">Standard</string>
+    <!-- Secondary label for Battery Saver tile when Extreme Battery Saver is enabled. [CHAR LIMIT=20] -->
+    <string name="extreme_battery_saver_text">Extreme</string>
+
     <!-- Name of the button that links to the Settings app. [CHAR LIMIT=NONE] -->
 
     <!-- Name of the button that links to the Wifi settings screen. [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index f28d405..8a2245d 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -404,7 +404,9 @@
                             if (nextAlarmMillis > 0) nextAlarmMillis else null,
                             SysuiR.string::status_bar_alarm.name
                         )
-                        .also { data -> clock?.run { events.onAlarmDataChanged(data) } }
+                        .also { data ->
+                            mainExecutor.execute { clock?.run { events.onAlarmDataChanged(data) } }
+                        }
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
index 31698a3..01c2cc4 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
@@ -345,11 +345,25 @@
         }
     }
 
-    private TextView loadPercentView() {
+    private TextView inflatePercentView() {
         return (TextView) LayoutInflater.from(getContext())
                 .inflate(R.layout.battery_percentage_view, null);
     }
 
+    private void addPercentView(TextView inflatedPercentView) {
+        mBatteryPercentView = inflatedPercentView;
+
+        if (mPercentageStyleId != 0) { // Only set if specified as attribute
+            mBatteryPercentView.setTextAppearance(mPercentageStyleId);
+        }
+        float fontHeight = mBatteryPercentView.getPaint().getFontMetricsInt(null);
+        mBatteryPercentView.setLineHeight(TypedValue.COMPLEX_UNIT_PX, fontHeight);
+        if (mTextColor != 0) mBatteryPercentView.setTextColor(mTextColor);
+        addView(mBatteryPercentView, new LayoutParams(
+                LayoutParams.WRAP_CONTENT,
+                (int) Math.ceil(fontHeight)));
+    }
+
     /**
      * Updates percent view by removing old one and reinflating if necessary
      */
@@ -388,7 +402,9 @@
             mBatteryEstimateFetcher.fetchBatteryTimeRemainingEstimate(
                     (String estimate) -> {
                         if (mBatteryPercentView == null) {
-                            mBatteryPercentView = loadPercentView();
+                            // Similar to the legacy behavior, inflate and add the view. We will
+                            // only use it for the estimate text
+                            addPercentView(inflatePercentView());
                         }
                         if (estimate != null && mShowPercentMode == MODE_ESTIMATE) {
                             mEstimateText = estimate;
@@ -401,6 +417,10 @@
                         }
                     });
         } else {
+            if (mBatteryPercentView != null) {
+                mEstimateText = null;
+                mBatteryPercentView.setText(null);
+            }
             updateContentDescription();
         }
     }
@@ -485,22 +505,19 @@
             return;
         }
 
-        if (mUnifiedBattery == null) {
-            return;
+        if (!mShowPercentAvailable || mUnifiedBattery == null) return;
+
+        boolean shouldShow = mShowPercentMode == MODE_ON || mShowPercentMode == MODE_ESTIMATE;
+        if (!mBatteryStateUnknown && !shouldShow && (mShowPercentMode != MODE_OFF)) {
+            // Slow case: fall back to the system setting
+            // TODO(b/140051051)
+            shouldShow = 0 != whitelistIpcs(() -> Settings.System
+                    .getIntForUser(getContext().getContentResolver(),
+                    SHOW_BATTERY_PERCENT, getContext().getResources().getBoolean(
+                    com.android.internal.R.bool.config_defaultBatteryPercentageSetting)
+                    ? 1 : 0, UserHandle.USER_CURRENT));
         }
 
-        // TODO(b/140051051)
-        final boolean systemSetting = 0 != whitelistIpcs(() -> Settings.System
-                .getIntForUser(getContext().getContentResolver(),
-                SHOW_BATTERY_PERCENT, getContext().getResources().getBoolean(
-                com.android.internal.R.bool.config_defaultBatteryPercentageSetting)
-                ? 1 : 0, UserHandle.USER_CURRENT));
-
-        boolean shouldShow =
-                (mShowPercentAvailable && systemSetting && mShowPercentMode != MODE_OFF)
-                        || mShowPercentMode == MODE_ON;
-        shouldShow = shouldShow && !mBatteryStateUnknown;
-
         setBatteryDrawableState(
                 new BatteryDrawableState(
                         mUnifiedBatteryState.getLevel(),
@@ -534,17 +551,8 @@
 
         if (shouldShow) {
             if (!showing) {
-                mBatteryPercentView = loadPercentView();
-                if (mPercentageStyleId != 0) { // Only set if specified as attribute
-                    mBatteryPercentView.setTextAppearance(mPercentageStyleId);
-                }
-                float fontHeight = mBatteryPercentView.getPaint().getFontMetricsInt(null);
-                mBatteryPercentView.setLineHeight(TypedValue.COMPLEX_UNIT_PX, fontHeight);
-                if (mTextColor != 0) mBatteryPercentView.setTextColor(mTextColor);
+                addPercentView(inflatePercentView());
                 updatePercentText();
-                addView(mBatteryPercentView, new LayoutParams(
-                        LayoutParams.WRAP_CONTENT,
-                        (int) Math.ceil(fontHeight)));
             }
         } else {
             if (showing) {
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatterySaverModule.kt b/packages/SystemUI/src/com/android/systemui/battery/BatterySaverModule.kt
index 8c9ae62..10a0d95 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatterySaverModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatterySaverModule.kt
@@ -1,9 +1,21 @@
 package com.android.systemui.battery
 
+import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.qs.tiles.BatterySaverTile
+import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory
+import com.android.systemui.qs.tiles.impl.battery.domain.interactor.BatterySaverTileDataInteractor
+import com.android.systemui.qs.tiles.impl.battery.domain.interactor.BatterySaverTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.battery.domain.model.BatterySaverTileModel
+import com.android.systemui.qs.tiles.impl.battery.ui.BatterySaverTileMapper
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
+import com.android.systemui.res.R
 import dagger.Binds
 import dagger.Module
+import dagger.Provides
 import dagger.multibindings.IntoMap
 import dagger.multibindings.StringKey
 
@@ -15,4 +27,39 @@
     @IntoMap
     @StringKey(BatterySaverTile.TILE_SPEC)
     fun bindBatterySaverTile(batterySaverTile: BatterySaverTile): QSTileImpl<*>
+
+    companion object {
+        private const val BATTERY_SAVER_TILE_SPEC = "battery"
+
+        @Provides
+        @IntoMap
+        @StringKey(BATTERY_SAVER_TILE_SPEC)
+        fun provideBatterySaverTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+            QSTileConfig(
+                tileSpec = TileSpec.create(BATTERY_SAVER_TILE_SPEC),
+                uiConfig =
+                    QSTileUIConfig.Resource(
+                        iconRes = R.drawable.qs_battery_saver_icon_off,
+                        labelRes = R.string.battery_detail_switch_title,
+                    ),
+                instanceId = uiEventLogger.getNewInstanceId(),
+            )
+
+        /** Inject BatterySaverTile into tileViewModelMap in QSModule */
+        @Provides
+        @IntoMap
+        @StringKey(BATTERY_SAVER_TILE_SPEC)
+        fun provideBatterySaverTileViewModel(
+            factory: QSTileViewModelFactory.Static<BatterySaverTileModel>,
+            mapper: BatterySaverTileMapper,
+            stateInteractor: BatterySaverTileDataInteractor,
+            userActionInteractor: BatterySaverTileUserActionInteractor
+        ): QSTileViewModel =
+            factory.create(
+                TileSpec.create(BATTERY_SAVER_TILE_SPEC),
+                userActionInteractor,
+                stateInteractor,
+                mapper,
+            )
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryAttributionDrawable.kt b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryAttributionDrawable.kt
index 1b8495a..f3652b8 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryAttributionDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryAttributionDrawable.kt
@@ -23,6 +23,7 @@
 import android.graphics.drawable.Drawable
 import android.graphics.drawable.DrawableWrapper
 import android.view.Gravity
+import kotlin.math.ceil
 import kotlin.math.min
 import kotlin.math.roundToInt
 
@@ -36,7 +37,7 @@
  */
 @Suppress("RtlHardcoded")
 class BatteryAttributionDrawable(dr: Drawable?) : DrawableWrapper(dr) {
-    /** One of [CENTER, LEFT]. Note that RTL is handled in the parent */
+    /** One of [CENTER, LEFT]. Note that number text does not RTL. */
     var gravity = Gravity.CENTER
         set(value) {
             field = value
@@ -67,8 +68,8 @@
             dr.setBounds(
                 bounds.left,
                 bounds.top,
-                (bounds.left + dw).roundToInt(),
-                (bounds.top + dh).roundToInt()
+                ceil(bounds.left + dw).toInt(),
+                ceil(bounds.top + dh).toInt()
             )
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryFillDrawable.kt b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryFillDrawable.kt
index 6d32067..5e34d29 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryFillDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryFillDrawable.kt
@@ -26,6 +26,7 @@
 import android.graphics.Rect
 import android.graphics.RectF
 import android.graphics.drawable.Drawable
+import android.view.View
 import com.android.systemui.battery.unified.BatteryLayersDrawable.Companion.Metrics
 import kotlin.math.floor
 import kotlin.math.roundToInt
@@ -103,6 +104,11 @@
         // saveLayer is needed here so we don't clip the other layers of our drawable
         canvas.saveLayer(null, null)
 
+        // Fill from the opposite direction in rtl mode
+        if (layoutDirection == View.LAYOUT_DIRECTION_RTL) {
+            canvas.scale(-1f, 1f, bounds.width() / 2f, bounds.height() / 2f)
+        }
+
         // We need to use 3 draw commands:
         // 1. Clip to the current level
         // 2. Clip anything outside of the path
diff --git a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryLayersDrawable.kt b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryLayersDrawable.kt
index 199dd1f..706b9ec 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryLayersDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryLayersDrawable.kt
@@ -26,7 +26,10 @@
 import android.graphics.drawable.LayerDrawable
 import android.util.PathParser
 import android.view.Gravity
+import android.view.View
 import com.android.systemui.res.R
+import kotlin.math.ceil
+import kotlin.math.floor
 import kotlin.math.roundToInt
 
 /**
@@ -69,8 +72,11 @@
 ) : LayerDrawable(arrayOf(frameBg, frame, fill, textOnly, spaceSharingText, attribution)) {
 
     private val scaleMatrix = Matrix().also { it.setScale(1f, 1f) }
-    private val scaledAttrFullCanvas = RectF(Metrics.AttrFullCanvas)
-    private val scaledAttrRightCanvas = RectF(Metrics.AttrRightCanvas)
+
+    private val attrFullCanvas = RectF()
+    private val attrRightCanvas = RectF()
+    private val scaledAttrFullCanvas = RectF()
+    private val scaledAttrRightCanvas = RectF()
 
     var batteryState = batteryState
         set(value) {
@@ -88,6 +94,12 @@
             updateColors(batteryState.showErrorState, value)
         }
 
+    init {
+        isAutoMirrored = true
+        // Initialize the canvas rects since they are not static
+        setAttrRects(layoutDirection == View.LAYOUT_DIRECTION_RTL)
+    }
+
     private fun handleUpdateState(old: BatteryDrawableState, new: BatteryDrawableState) {
         if (new.showErrorState != old.showErrorState) {
             updateColors(new.showErrorState, colors)
@@ -144,9 +156,42 @@
             bounds.height() / Metrics.ViewportHeight
         )
 
-        // Scale the attribution bounds
-        scaleMatrix.mapRect(scaledAttrFullCanvas, Metrics.AttrFullCanvas)
-        scaleMatrix.mapRect(scaledAttrRightCanvas, Metrics.AttrRightCanvas)
+        scaleAttributionBounds()
+    }
+
+    override fun onLayoutDirectionChanged(layoutDirection: Int): Boolean {
+        setAttrRects(layoutDirection == View.LAYOUT_DIRECTION_RTL)
+        scaleAttributionBounds()
+
+        return super.onLayoutDirectionChanged(layoutDirection)
+    }
+
+    private fun setAttrRects(rtl: Boolean) {
+        // Local refs make the math easier to parse
+        val full = Metrics.AttrFullCanvasInsets
+        val side = Metrics.AttrRightCanvasInsets
+        val sideRtl = Metrics.AttrRightCanvasInsetsRtl
+        val vh = Metrics.ViewportHeight
+        val vw = Metrics.ViewportWidth
+
+        attrFullCanvas.set(
+            if (rtl) full.right else full.left,
+            full.top,
+            vw - if (rtl) full.left else full.right,
+            vh - full.bottom,
+        )
+        attrRightCanvas.set(
+            if (rtl) sideRtl.left else side.left,
+            side.top,
+            vw - (if (rtl) sideRtl.right else side.right),
+            vh - side.bottom,
+        )
+    }
+
+    /** If bounds (i.e., scale), or RTL properties change, we have to recalculate the attr bounds */
+    private fun scaleAttributionBounds() {
+        scaleMatrix.mapRect(scaledAttrFullCanvas, attrFullCanvas)
+        scaleMatrix.mapRect(scaledAttrRightCanvas, attrRightCanvas)
     }
 
     override fun draw(canvas: Canvas) {
@@ -163,13 +208,14 @@
         if (batteryState.showPercent && batteryState.attribution != null) {
             // 4a. percent & attribution. Implies space-sharing
 
-            // Configure the attribute to draw in a smaller bounding box and align left
+            // Configure the attribute to draw in a smaller bounding box and align left and use
+            // floor/ceil math to make sure we get every available pixel
             attribution.gravity = Gravity.LEFT
             attribution.setBounds(
-                scaledAttrRightCanvas.left.roundToInt(),
-                scaledAttrRightCanvas.top.roundToInt(),
-                scaledAttrRightCanvas.right.roundToInt(),
-                scaledAttrRightCanvas.bottom.roundToInt(),
+                floor(scaledAttrRightCanvas.left).toInt(),
+                floor(scaledAttrRightCanvas.top).toInt(),
+                ceil(scaledAttrRightCanvas.right).toInt(),
+                ceil(scaledAttrRightCanvas.bottom).toInt(),
             )
             attribution.draw(canvas)
 
@@ -196,16 +242,44 @@
      */
     override fun setAlpha(alpha: Int) {}
 
+    /**
+     * Interface that describes relevant top-level metrics for the proper rendering of this icon.
+     * The overall canvas is defined as ViewportWidth x ViewportHeight, which is hard coded to 24x14
+     * points.
+     *
+     * The attr canvas insets are rect inset definitions. That is, they are defined as l,t,r,b
+     * points from the nearest edge. Note that for RTL, we don't actually flip the text since
+     * numbers do not reverse for RTL locales.
+     */
     interface M {
         val ViewportWidth: Float
         val ViewportHeight: Float
 
-        // Bounds, oriented in the above viewport, where we will fit-center and center-align
-        // an attribution that is the sole foreground element
-        val AttrFullCanvas: RectF
-        // Bounds, oriented in the above viewport, where we will fit-center and left-align
-        // an attribution that is sharing space with the percent text of the drawable
-        val AttrRightCanvas: RectF
+        /**
+         * Insets, oriented in the above viewport in LTR, that define the full canvas for a single
+         * foreground element. The element will be fit-center and center-aligned on this canvas
+         *
+         * 18x8 point size
+         */
+        val AttrFullCanvasInsets: RectF
+
+        /**
+         * Insets, oriented in the above viewport in LTR, that define the partial canvas for a
+         * foreground element that shares space with the percent text. The element will be
+         * fit-center and left-aligned on this canvas.
+         *
+         * 6x6 point size
+         */
+        val AttrRightCanvasInsets: RectF
+
+        /**
+         * Insets, oriented in the above viewport in RTL, that define the partial canvas for a
+         * foreground element that shares space with the percent text. The element will be
+         * fit-center and left-aligned on this canvas.
+         *
+         * 6x6 point size
+         */
+        val AttrRightCanvasInsetsRtl: RectF
     }
 
     companion object {
@@ -220,20 +294,9 @@
                 override val ViewportWidth: Float = 24f
                 override val ViewportHeight: Float = 14f
 
-                /**
-                 * Bounds, oriented in the above viewport, where we will fit-center and center-align
-                 * an attribution that is the sole foreground element
-                 *
-                 * 18x8 point size
-                 */
-                override val AttrFullCanvas: RectF = RectF(4f, 3f, 22f, 11f)
-                /**
-                 * Bounds, oriented in the above viewport, where we will fit-center and left-align
-                 * an attribution that is sharing space with the percent text of the drawable
-                 *
-                 * 6x6 point size
-                 */
-                override val AttrRightCanvas: RectF = RectF(16f, 4f, 22f, 10f)
+                override val AttrFullCanvasInsets = RectF(4f, 3f, 2f, 3f)
+                override val AttrRightCanvasInsets = RectF(16f, 4f, 2f, 4f)
+                override val AttrRightCanvasInsetsRtl = RectF(14f, 4f, 4f, 4f)
             }
 
         /**
diff --git a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryPercentTextOnlyDrawable.kt b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryPercentTextOnlyDrawable.kt
index 123d6ba..aa0e373 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryPercentTextOnlyDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryPercentTextOnlyDrawable.kt
@@ -23,6 +23,7 @@
 import android.graphics.Rect
 import android.graphics.Typeface
 import android.graphics.drawable.Drawable
+import android.view.View
 import com.android.systemui.battery.unified.BatteryLayersDrawable.Companion.Metrics
 
 /**
@@ -71,6 +72,7 @@
     }
 
     override fun draw(canvas: Canvas) {
+        val rtl = layoutDirection == View.LAYOUT_DIRECTION_RTL
         val totalAvailableHeight = CanvasHeight * vScale
 
         // Distribute the vertical whitespace around the text. This is a simplified version of
@@ -81,11 +83,12 @@
         val totalAvailableWidth = CanvasWidth * hScale
         val textWidth = textPaint.measureText(percentText)
         val offsetX = (totalAvailableWidth - textWidth) / 2
+        val startOffset = if (rtl) ViewportInsetRight else ViewportInsetLeft
 
         // Draw the text centered in the available area
         canvas.drawText(
             percentText,
-            (ViewportInsetLeft * hScale) + offsetX,
+            (startOffset * hScale) + offsetX,
             (ViewportInsetTop * vScale) + offsetY,
             textPaint
         )
diff --git a/packages/SystemUI/src/com/android/systemui/battery/unified/BatterySpaceSharingPercentTextDrawable.kt b/packages/SystemUI/src/com/android/systemui/battery/unified/BatterySpaceSharingPercentTextDrawable.kt
index 0c418b9..3b4c779 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/unified/BatterySpaceSharingPercentTextDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/battery/unified/BatterySpaceSharingPercentTextDrawable.kt
@@ -23,6 +23,7 @@
 import android.graphics.Rect
 import android.graphics.Typeface
 import android.graphics.drawable.Drawable
+import android.view.View
 import com.android.systemui.battery.unified.BatteryLayersDrawable.Companion.Metrics
 
 /**
@@ -94,6 +95,7 @@
     }
 
     override fun draw(canvas: Canvas) {
+        val rtl = layoutDirection == View.LAYOUT_DIRECTION_RTL
         val totalAvailableHeight = CanvasHeight * vScale
 
         // Distribute the vertical whitespace around the text. This is a simplified version of
@@ -107,7 +109,7 @@
 
         canvas.drawText(
             percentText,
-            (ViewportInsetLeft * hScale) + offsetX,
+            ((if (rtl) ViewportInsetLeftRtl else ViewportInsetLeft) * hScale) + offsetX,
             (ViewportInsetTop * vScale) + offsetY,
             textPaint
         )
@@ -128,6 +130,7 @@
 
     companion object {
         private const val ViewportInsetLeft = 4f
+        private const val ViewportInsetLeftRtl = 2f
         private const val ViewportInsetTop = 2f
 
         private const val CanvasWidth = 12f
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java
index 6af0fa0..66aeda6 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java
@@ -42,7 +42,7 @@
 import com.android.systemui.broadcast.BroadcastSender;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.media.controls.util.MediaDataUtils;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
+import com.android.systemui.media.dialog.MediaOutputDialogManager;
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 
@@ -69,7 +69,7 @@
 
     private final Context mContext;
     private final UiEventLogger mUiEventLogger;
-    private final MediaOutputDialogFactory mMediaOutputDialogFactory;
+    private final MediaOutputDialogManager mMediaOutputDialogManager;
     private final LocalBluetoothManager mLocalBluetoothManager;
     private final BroadcastSender mBroadcastSender;
     private final SystemUIDialog.Factory mSystemUIDialogFactory;
@@ -157,7 +157,7 @@
     @AssistedInject
     BroadcastDialogDelegate(
             Context context,
-            MediaOutputDialogFactory mediaOutputDialogFactory,
+            MediaOutputDialogManager mediaOutputDialogManager,
             @Nullable LocalBluetoothManager localBluetoothManager,
             UiEventLogger uiEventLogger,
             @Background Executor bgExecutor,
@@ -166,7 +166,7 @@
             @Assisted(CURRENT_BROADCAST_APP) String currentBroadcastApp,
             @Assisted(OUTPUT_PKG_NAME) String outputPkgName) {
         mContext = context;
-        mMediaOutputDialogFactory = mediaOutputDialogFactory;
+        mMediaOutputDialogManager = mediaOutputDialogManager;
         mLocalBluetoothManager = localBluetoothManager;
         mSystemUIDialogFactory = systemUIDialogFactory;
         mCurrentBroadcastApp = currentBroadcastApp;
@@ -218,7 +218,7 @@
                 R.string.bt_le_audio_broadcast_dialog_switch_app, switchBroadcastApp), null);
         mSwitchBroadcast.setOnClickListener((view) -> startSwitchBroadcast());
         changeOutput.setOnClickListener((view) -> {
-            mMediaOutputDialogFactory.create(mOutputPackageName, true, null);
+            mMediaOutputDialogManager.createAndShow(mOutputPackageName, true, null);
             dialog.dismiss();
         });
         cancelBtn.setOnClickListener((view) -> {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 35b27aa..fc9a7df 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -21,7 +21,6 @@
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.Logger
 import com.android.systemui.log.dagger.CommunalLog
@@ -47,7 +46,6 @@
 import kotlinx.coroutines.launch
 
 /** The default view model used for showing the communal hub. */
-@OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class CommunalViewModel
 @Inject
@@ -56,7 +54,6 @@
     private val communalInteractor: CommunalInteractor,
     tutorialInteractor: CommunalTutorialInteractor,
     shadeInteractor: ShadeInteractor,
-    deviceEntryInteractor: DeviceEntryInteractor,
     @Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
     @CommunalLog logBuffer: LogBuffer,
 ) : BaseCommunalViewModel(communalInteractor, mediaHost) {
@@ -90,8 +87,6 @@
     /** Whether touches should be disabled in communal */
     val touchesAllowed: Flow<Boolean> = not(shadeInteractor.isAnyFullyExpanded)
 
-    val deviceUnlocked: Flow<Boolean> = deviceEntryInteractor.isUnlocked
-
     init {
         // Initialize our media host for the UMO. This only needs to happen once and must be done
         // before the MediaHierarchyManager attempts to move the UMO to the hub.
diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
index c0a873a..989b0de 100644
--- a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
@@ -18,6 +18,7 @@
 import android.content.Context
 import android.os.Bundle
 import android.view.View
+import android.view.WindowInsets
 import android.widget.TextView
 import androidx.core.view.updatePadding
 import com.android.systemui.res.R
@@ -44,7 +45,10 @@
     private lateinit var mirrorButton: TextView
     private lateinit var dismissButton: TextView
     private lateinit var dualDisplayWarning: TextView
+    private lateinit var bottomSheet: View
     private var enabledPressed = false
+    private val defaultDialogBottomInset =
+        context.resources.getDimensionPixelSize(R.dimen.dialog_bottom_padding)
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
@@ -63,6 +67,8 @@
                 visibility = if (showConcurrentDisplayInfo) View.VISIBLE else View.GONE
             }
 
+        bottomSheet = requireViewById(R.id.cd_bottom_sheet)
+
         setOnDismissListener {
             if (!enabledPressed) {
                 onCancelMirroring.onClick(null)
@@ -71,15 +77,17 @@
         setupInsets()
     }
 
-    private fun setupInsets() {
+    private fun setupInsets(navbarInsets: Int = navbarBottomInsetsProvider()) {
         // This avoids overlap between dialog content and navigation bars.
-        requireViewById<View>(R.id.cd_bottom_sheet).apply {
-            val navbarInsets = navbarBottomInsetsProvider()
-            val defaultDialogBottomInset =
-                context.resources.getDimensionPixelSize(R.dimen.dialog_bottom_padding)
-            // we only care about the bottom inset as in all other configuration where navigations
-            // are in other display sides there is no overlap with the dialog.
-            updatePadding(bottom = max(navbarInsets, defaultDialogBottomInset))
+        // we only care about the bottom inset as in all other configuration where navigations
+        // are in other display sides there is no overlap with the dialog.
+        bottomSheet.updatePadding(bottom = max(navbarInsets, defaultDialogBottomInset))
+    }
+
+    override fun onInsetsChanged(changedTypes: Int, insets: WindowInsets) {
+        val navbarType = WindowInsets.Type.navigationBars()
+        if (changedTypes and navbarType != 0) {
+            setupInsets(insets.getInsets(navbarType).bottom)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
index 190062c..fbf0538 100644
--- a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
@@ -32,9 +32,12 @@
 import dagger.multibindings.ClassKey
 import dagger.multibindings.IntoMap
 import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.FlowPreview
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.debounce
 import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.launch
@@ -57,6 +60,7 @@
     private var dialog: Dialog? = null
 
     /** Starts listening for pending displays. */
+    @OptIn(FlowPreview::class)
     override fun start() {
         val pendingDisplayFlow = connectedDisplayInteractor.pendingDisplay
         val concurrentDisplaysInProgessFlow =
@@ -66,6 +70,13 @@
                 flow { emit(false) }
             }
         pendingDisplayFlow
+            // Let's debounce for 2 reasons:
+            // - prevent fast dialog flashes in case pending displays are available for just a few
+            // millis
+            // - Prevent jumps related to inset changes: when in 3 buttons navigation, device
+            // unlock triggers a change in insets that might result in a jump of the dialog (if a
+            // display was connected while on the lockscreen).
+            .debounce(200.milliseconds)
             .combine(concurrentDisplaysInProgessFlow) { pendingDisplay, concurrentDisplaysInProgress
                 ->
                 if (pendingDisplay == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderHapticPlugin.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderHapticPlugin.kt
index 931a869..ed82278 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderHapticPlugin.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderHapticPlugin.kt
@@ -163,6 +163,6 @@
     }
 
     companion object {
-        const val KEY_UP_TIMEOUT = 100L
+        const val KEY_UP_TIMEOUT = 60L
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
index 840b309..26c63f3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
@@ -109,7 +109,7 @@
 import com.android.systemui.media.controls.util.MediaFlags;
 import com.android.systemui.media.controls.util.MediaUiEventLogger;
 import com.android.systemui.media.controls.util.SmallHash;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
+import com.android.systemui.media.dialog.MediaOutputDialogManager;
 import com.android.systemui.monet.ColorScheme;
 import com.android.systemui.monet.Style;
 import com.android.systemui.plugins.ActivityStarter;
@@ -223,7 +223,7 @@
     protected int mUid = Process.INVALID_UID;
     private int mSmartspaceMediaItemsCount;
     private MediaCarouselController mMediaCarouselController;
-    private final MediaOutputDialogFactory mMediaOutputDialogFactory;
+    private final MediaOutputDialogManager mMediaOutputDialogManager;
     private final FalsingManager mFalsingManager;
     private MetadataAnimationHandler mMetadataAnimationHandler;
     private ColorSchemeTransition mColorSchemeTransition;
@@ -304,7 +304,7 @@
             MediaViewController mediaViewController,
             SeekBarViewModel seekBarViewModel,
             Lazy<MediaDataManager> lazyMediaDataManager,
-            MediaOutputDialogFactory mediaOutputDialogFactory,
+            MediaOutputDialogManager mediaOutputDialogManager,
             MediaCarouselController mediaCarouselController,
             FalsingManager falsingManager,
             SystemClock systemClock,
@@ -324,7 +324,7 @@
         mSeekBarViewModel = seekBarViewModel;
         mMediaViewController = mediaViewController;
         mMediaDataManagerLazy = lazyMediaDataManager;
-        mMediaOutputDialogFactory = mediaOutputDialogFactory;
+        mMediaOutputDialogManager = mediaOutputDialogManager;
         mMediaCarouselController = mediaCarouselController;
         mFalsingManager = falsingManager;
         mSystemClock = systemClock;
@@ -737,7 +737,7 @@
                                     mPackageName, mMediaViewHolder.getSeamlessButton());
                         } else {
                             mLogger.logOpenOutputSwitcher(mUid, mPackageName, mInstanceId);
-                            mMediaOutputDialogFactory.create(mPackageName, true,
+                            mMediaOutputDialogManager.createAndShow(mPackageName, true,
                                     mMediaViewHolder.getSeamlessButton());
                         }
                     } else {
@@ -761,7 +761,7 @@
                                 }
                             }
                         } else {
-                            mMediaOutputDialogFactory.create(mPackageName, true,
+                            mMediaOutputDialogManager.createAndShow(mPackageName, true,
                                     mMediaViewHolder.getSeamlessButton());
                         }
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt
index 5d113a9..452cb7e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt
@@ -31,7 +31,8 @@
 ) {
     /** Creates a [LocalMediaManager] for the given package. */
     fun create(packageName: String?): LocalMediaManager {
-        return InfoMediaManager.createInstance(context, packageName, null, localBluetoothManager)
-            .run { LocalMediaManager(context, localBluetoothManager, this, packageName) }
+        return InfoMediaManager.createInstance(context, packageName, localBluetoothManager).run {
+            LocalMediaManager(context, localBluetoothManager, this, packageName)
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
deleted file mode 100644
index b6e3937..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
+++ /dev/null
@@ -1,83 +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.systemui.media.dialog
-
-import android.app.KeyguardManager
-import android.content.Context
-import android.media.AudioManager
-import android.media.session.MediaSessionManager
-import android.os.PowerExemptionManager
-import android.view.View
-import com.android.internal.logging.UiEventLogger
-import com.android.settingslib.bluetooth.LocalBluetoothManager
-import com.android.systemui.animation.DialogTransitionAnimator
-import com.android.systemui.broadcast.BroadcastSender
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.media.nearby.NearbyMediaDevicesManager
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
-import javax.inject.Inject
-
-/**
- * Factory to create [MediaOutputBroadcastDialog] objects.
- */
-class MediaOutputBroadcastDialogFactory @Inject constructor(
-    private val context: Context,
-    private val mediaSessionManager: MediaSessionManager,
-    private val lbm: LocalBluetoothManager?,
-    private val starter: ActivityStarter,
-    private val broadcastSender: BroadcastSender,
-    private val notifCollection: CommonNotifCollection,
-    private val uiEventLogger: UiEventLogger,
-    private val dialogTransitionAnimator: DialogTransitionAnimator,
-    private val nearbyMediaDevicesManager: NearbyMediaDevicesManager,
-    private val audioManager: AudioManager,
-    private val powerExemptionManager: PowerExemptionManager,
-    private val keyGuardManager: KeyguardManager,
-    private val featureFlags: FeatureFlags,
-    private val userTracker: UserTracker
-) {
-    var mediaOutputBroadcastDialog: MediaOutputBroadcastDialog? = null
-
-    /** Creates a [MediaOutputBroadcastDialog] for the given package. */
-    fun create(packageName: String, aboveStatusBar: Boolean, view: View? = null) {
-        // Dismiss the previous dialog, if any.
-        mediaOutputBroadcastDialog?.dismiss()
-
-        val controller = MediaOutputController(context, packageName,
-                mediaSessionManager, lbm, starter, notifCollection,
-                dialogTransitionAnimator, nearbyMediaDevicesManager, audioManager,
-                powerExemptionManager, keyGuardManager, featureFlags, userTracker)
-        val dialog =
-                MediaOutputBroadcastDialog(context, aboveStatusBar, broadcastSender, controller)
-        mediaOutputBroadcastDialog = dialog
-
-        // Show the dialog.
-        if (view != null) {
-            dialogTransitionAnimator.showFromView(dialog, view)
-        } else {
-            dialog.show()
-        }
-    }
-
-    /** dismiss [MediaOutputBroadcastDialog] if exist. */
-    fun dismiss() {
-        mediaOutputBroadcastDialog?.dismiss()
-        mediaOutputBroadcastDialog = null
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt
new file mode 100644
index 0000000..54d175c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.dialog
+
+import android.content.Context
+import android.view.View
+import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.broadcast.BroadcastSender
+import javax.inject.Inject
+
+/** Manager to create and show a [MediaOutputBroadcastDialog]. */
+class MediaOutputBroadcastDialogManager
+@Inject
+constructor(
+    private val context: Context,
+    private val broadcastSender: BroadcastSender,
+    private val dialogTransitionAnimator: DialogTransitionAnimator,
+    private val mediaOutputControllerFactory: MediaOutputController.Factory
+) {
+    var mediaOutputBroadcastDialog: MediaOutputBroadcastDialog? = null
+
+    /** Creates a [MediaOutputBroadcastDialog] for the given package. */
+    fun createAndShow(packageName: String, aboveStatusBar: Boolean, view: View? = null) {
+        // Dismiss the previous dialog, if any.
+        mediaOutputBroadcastDialog?.dismiss()
+
+        val controller = mediaOutputControllerFactory.create(packageName)
+        val dialog =
+            MediaOutputBroadcastDialog(context, aboveStatusBar, broadcastSender, controller)
+        mediaOutputBroadcastDialog = dialog
+
+        // Show the dialog.
+        if (view != null) {
+            dialogTransitionAnimator.showFromView(dialog, view)
+        } else {
+            dialog.show()
+        }
+    }
+
+    /** dismiss [MediaOutputBroadcastDialog] if exist. */
+    fun dismiss() {
+        mediaOutputBroadcastDialog?.dismiss()
+        mediaOutputBroadcastDialog = null
+    }
+}
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 b3b7bce..adee7f2c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -64,6 +64,7 @@
 import android.view.WindowManager;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 import androidx.core.graphics.drawable.IconCompat;
 
@@ -91,6 +92,10 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
+
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -105,8 +110,6 @@
 import java.util.concurrent.Executor;
 import java.util.stream.Collectors;
 
-import javax.inject.Inject;
-
 /**
  * Controller for media output dialog
  */
@@ -170,10 +173,13 @@
         ACTION_BROADCAST_INFO_ICON
     }
 
-    @Inject
-    public MediaOutputController(@NonNull Context context, String packageName,
-            MediaSessionManager mediaSessionManager, LocalBluetoothManager
-            lbm, ActivityStarter starter,
+    @AssistedInject
+    public MediaOutputController(
+            Context context,
+            @Assisted String packageName,
+            MediaSessionManager mediaSessionManager,
+            @Nullable LocalBluetoothManager lbm,
+            ActivityStarter starter,
             CommonNotifCollection notifCollection,
             DialogTransitionAnimator dialogTransitionAnimator,
             NearbyMediaDevicesManager nearbyMediaDevicesManager,
@@ -193,7 +199,7 @@
         mKeyGuardManager = keyGuardManager;
         mFeatureFlags = featureFlags;
         mUserTracker = userTracker;
-        InfoMediaManager imm = InfoMediaManager.createInstance(mContext, packageName, null, lbm);
+        InfoMediaManager imm = InfoMediaManager.createInstance(mContext, packageName, lbm);
         mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, packageName);
         mMetricLogger = new MediaOutputMetricLogger(mContext, mPackageName);
         mDialogTransitionAnimator = dialogTransitionAnimator;
@@ -222,6 +228,12 @@
                 R.dimen.media_output_dialog_selectable_margin_end);
     }
 
+    @AssistedFactory
+    public interface Factory {
+        /** Construct a MediaOutputController */
+        MediaOutputController create(String packageName);
+    }
+
     protected void start(@NonNull Callback cb) {
         synchronized (mMediaDevicesLock) {
             mCachedMediaDevices.clear();
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt
similarity index 62%
rename from packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
rename to packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt
index 02be0c1..e7816a4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt
@@ -16,43 +16,24 @@
 
 package com.android.systemui.media.dialog
 
-import android.app.KeyguardManager
 import android.content.Context
-import android.media.AudioManager
-import android.media.session.MediaSessionManager
-import android.os.PowerExemptionManager
 import android.view.View
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.logging.UiEventLogger
-import com.android.settingslib.bluetooth.LocalBluetoothManager
 import com.android.systemui.animation.DialogCuj
 import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.broadcast.BroadcastSender
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.media.nearby.NearbyMediaDevicesManager
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
 import javax.inject.Inject
 
-/** Factory to create [MediaOutputDialog] objects. */
-open class MediaOutputDialogFactory
+/** Manager to create and show a [MediaOutputDialog]. */
+open class MediaOutputDialogManager
 @Inject
 constructor(
     private val context: Context,
-    private val mediaSessionManager: MediaSessionManager,
-    private val lbm: LocalBluetoothManager?,
-    private val starter: ActivityStarter,
     private val broadcastSender: BroadcastSender,
-    private val notifCollection: CommonNotifCollection,
     private val uiEventLogger: UiEventLogger,
     private val dialogTransitionAnimator: DialogTransitionAnimator,
-    private val nearbyMediaDevicesManager: NearbyMediaDevicesManager,
-    private val audioManager: AudioManager,
-    private val powerExemptionManager: PowerExemptionManager,
-    private val keyGuardManager: KeyguardManager,
-    private val featureFlags: FeatureFlags,
-    private val userTracker: UserTracker
+    private val mediaOutputControllerFactory: MediaOutputController.Factory,
 ) {
     companion object {
         const val INTERACTION_JANK_TAG = "media_output"
@@ -60,8 +41,8 @@
     }
 
     /** Creates a [MediaOutputDialog] for the given package. */
-    open fun create(packageName: String, aboveStatusBar: Boolean, view: View? = null) {
-        createWithController(
+    open fun createAndShow(packageName: String, aboveStatusBar: Boolean, view: View? = null) {
+        createAndShowWithController(
             packageName,
             aboveStatusBar,
             controller =
@@ -78,12 +59,12 @@
     }
 
     /** Creates a [MediaOutputDialog] for the given package. */
-    open fun createWithController(
+    open fun createAndShowWithController(
         packageName: String,
         aboveStatusBar: Boolean,
         controller: DialogTransitionAnimator.Controller?,
     ) {
-        create(
+        createAndShow(
             packageName,
             aboveStatusBar,
             dialogTransitionAnimatorController = controller,
@@ -91,8 +72,10 @@
         )
     }
 
-    open fun createDialogForSystemRouting(controller: DialogTransitionAnimator.Controller? = null) {
-        create(
+    open fun createAndShowForSystemRouting(
+        controller: DialogTransitionAnimator.Controller? = null
+    ) {
+        createAndShow(
             packageName = null,
             aboveStatusBar = false,
             dialogTransitionAnimatorController = null,
@@ -100,7 +83,7 @@
         )
     }
 
-    private fun create(
+    private fun createAndShow(
         packageName: String?,
         aboveStatusBar: Boolean,
         dialogTransitionAnimatorController: DialogTransitionAnimator.Controller?,
@@ -109,23 +92,9 @@
         // Dismiss the previous dialog, if any.
         mediaOutputDialog?.dismiss()
 
-        val controller =
-            MediaOutputController(
-                context,
-                packageName,
-                mediaSessionManager,
-                lbm,
-                starter,
-                notifCollection,
-                dialogTransitionAnimator,
-                nearbyMediaDevicesManager,
-                audioManager,
-                powerExemptionManager,
-                keyGuardManager,
-                featureFlags,
-                userTracker
-            )
-        val dialog =
+        val controller = mediaOutputControllerFactory.create(packageName)
+
+        val mediaOutputDialog =
             MediaOutputDialog(
                 context,
                 aboveStatusBar,
@@ -135,16 +104,15 @@
                 uiEventLogger,
                 includePlaybackAndAppMetadata
             )
-        mediaOutputDialog = dialog
 
         // Show the dialog.
         if (dialogTransitionAnimatorController != null) {
             dialogTransitionAnimator.show(
-                dialog,
+                mediaOutputDialog,
                 dialogTransitionAnimatorController,
             )
         } else {
-            dialog.show()
+            mediaOutputDialog.show()
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt
index 38d31ed..774792f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt
@@ -31,8 +31,8 @@
  * BroadcastReceiver for handling media output intent
  */
 class MediaOutputDialogReceiver @Inject constructor(
-    private val mediaOutputDialogFactory: MediaOutputDialogFactory,
-    private val mediaOutputBroadcastDialogFactory: MediaOutputBroadcastDialogFactory
+        private val mediaOutputDialogManager: MediaOutputDialogManager,
+        private val mediaOutputBroadcastDialogManager: MediaOutputBroadcastDialogManager
 ) : BroadcastReceiver() {
     override fun onReceive(context: Context, intent: Intent) {
         when (intent.action) {
@@ -42,7 +42,7 @@
                 launchMediaOutputDialogIfPossible(packageName)
             }
             MediaOutputConstants.ACTION_LAUNCH_SYSTEM_MEDIA_OUTPUT_DIALOG -> {
-                mediaOutputDialogFactory.createDialogForSystemRouting()
+                mediaOutputDialogManager.createAndShowForSystemRouting()
             }
             MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG -> {
                 if (!legacyLeAudioSharing()) return
@@ -55,7 +55,7 @@
 
     private fun launchMediaOutputDialogIfPossible(packageName: String?) {
         if (!packageName.isNullOrEmpty()) {
-            mediaOutputDialogFactory.create(packageName, false)
+            mediaOutputDialogManager.createAndShow(packageName, false)
         } else if (DEBUG) {
             Log.e(TAG, "Unable to launch media output dialog. Package name is empty.")
         }
@@ -63,7 +63,7 @@
 
     private fun launchMediaOutputBroadcastDialogIfPossible(packageName: String?) {
         if (!packageName.isNullOrEmpty()) {
-            mediaOutputBroadcastDialogFactory.create(
+            mediaOutputBroadcastDialogManager.createAndShow(
                     packageName, aboveStatusBar = true, view = null)
         } else if (DEBUG) {
             Log.e(TAG, "Unable to launch media output broadcast dialog. Package name is empty.")
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java
index b5b1f0f..6e7e0f2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java
@@ -34,15 +34,15 @@
     private static final String TAG = "MediaOutputSwitcherDialogUI";
 
     private final CommandQueue mCommandQueue;
-    private final MediaOutputDialogFactory mMediaOutputDialogFactory;
+    private final MediaOutputDialogManager mMediaOutputDialogManager;
 
     @Inject
     public MediaOutputSwitcherDialogUI(
             Context context,
             CommandQueue commandQueue,
-            MediaOutputDialogFactory mediaOutputDialogFactory) {
+            MediaOutputDialogManager mediaOutputDialogManager) {
         mCommandQueue = commandQueue;
-        mMediaOutputDialogFactory = mediaOutputDialogFactory;
+        mMediaOutputDialogManager = mediaOutputDialogManager;
     }
 
     @Override
@@ -54,7 +54,7 @@
     @MainThread
     public void showMediaOutputSwitcher(String packageName) {
         if (!TextUtils.isEmpty(packageName)) {
-            mMediaOutputDialogFactory.create(packageName, false, null);
+            mMediaOutputDialogManager.createAndShow(packageName, false, null);
         } else {
             Log.e(TAG, "Unable to launch media output dialog. Package name is empty.");
         }
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
index c0e688f..c7aae3c 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
@@ -23,6 +23,7 @@
 import android.app.role.RoleManager
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.notetask.NoteTaskBubblesController.NoteTaskBubblesService
 import com.android.systemui.notetask.quickaffordance.NoteTaskQuickAffordanceModule
 import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
 import com.android.systemui.notetask.shortcut.LaunchNoteTaskActivity
@@ -37,20 +38,21 @@
 interface NoteTaskModule {
 
     @[Binds IntoMap ClassKey(NoteTaskControllerUpdateService::class)]
-    fun NoteTaskControllerUpdateService.bindNoteTaskControllerUpdateService(): Service
+    fun bindNoteTaskControllerUpdateService(service: NoteTaskControllerUpdateService): Service
 
-    @[Binds IntoMap ClassKey(NoteTaskBubblesController.NoteTaskBubblesService::class)]
-    fun NoteTaskBubblesController.NoteTaskBubblesService.bindNoteTaskBubblesService(): Service
+    @[Binds IntoMap ClassKey(NoteTaskBubblesService::class)]
+    fun bindNoteTaskBubblesService(service: NoteTaskBubblesService): Service
 
     @[Binds IntoMap ClassKey(LaunchNoteTaskActivity::class)]
-    fun LaunchNoteTaskActivity.bindNoteTaskLauncherActivity(): Activity
+    fun bindNoteTaskLauncherActivity(activity: LaunchNoteTaskActivity): Activity
 
     @[Binds IntoMap ClassKey(LaunchNotesRoleSettingsTrampolineActivity::class)]
-    fun LaunchNotesRoleSettingsTrampolineActivity.bindLaunchNotesRoleSettingsTrampolineActivity():
-        Activity
+    fun bindLaunchNotesRoleSettingsTrampolineActivity(
+        activity: LaunchNotesRoleSettingsTrampolineActivity
+    ): Activity
 
     @[Binds IntoMap ClassKey(CreateNoteTaskShortcutActivity::class)]
-    fun CreateNoteTaskShortcutActivity.bindNoteTaskShortcutActivity(): Activity
+    fun bindNoteTaskShortcutActivity(activity: CreateNoteTaskShortcutActivity): Activity
 
     companion object {
 
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceModule.kt
index 2d63dbc..7d3f2a5 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceModule.kt
@@ -25,5 +25,7 @@
 interface NoteTaskQuickAffordanceModule {
 
     @[Binds IntoSet]
-    fun NoteTaskQuickAffordanceConfig.bindNoteTaskQuickAffordance(): KeyguardQuickAffordanceConfig
+    fun bindNoteTaskQuickAffordance(
+        impl: NoteTaskQuickAffordanceConfig
+    ): KeyguardQuickAffordanceConfig
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/interactor/BatterySaverTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/interactor/BatterySaverTileDataInteractor.kt
new file mode 100644
index 0000000..22bbbbb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/interactor/BatterySaverTileDataInteractor.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2024 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.qs.tiles.impl.battery.domain.interactor
+
+import android.os.UserHandle
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.battery.domain.model.BatterySaverTileModel
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.util.kotlin.combine
+import com.android.systemui.util.kotlin.getBatteryLevel
+import com.android.systemui.util.kotlin.isBatteryPowerSaveEnabled
+import com.android.systemui.util.kotlin.isDevicePluggedIn
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+
+/** Observes BatterySaver mode state changes providing the [BatterySaverTileModel.Standard]. */
+open class BatterySaverTileDataInteractor
+@Inject
+constructor(
+    @Background private val bgCoroutineContext: CoroutineContext,
+    private val batteryController: BatteryController,
+) : QSTileDataInteractor<BatterySaverTileModel> {
+
+    override fun tileData(
+        user: UserHandle,
+        triggers: Flow<DataUpdateTrigger>
+    ): Flow<BatterySaverTileModel> =
+        combine(
+            batteryController.isDevicePluggedIn().distinctUntilChanged().flowOn(bgCoroutineContext),
+            batteryController
+                .isBatteryPowerSaveEnabled()
+                .distinctUntilChanged()
+                .flowOn(bgCoroutineContext),
+            batteryController.getBatteryLevel().distinctUntilChanged().flowOn(bgCoroutineContext),
+        ) {
+            isPluggedIn: Boolean,
+            isPowerSaverEnabled: Boolean,
+            _, // we are only interested in battery level change, not the actual level
+            ->
+            BatterySaverTileModel.Standard(isPluggedIn, isPowerSaverEnabled)
+        }
+
+    override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/interactor/BatterySaverTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/interactor/BatterySaverTileUserActionInteractor.kt
new file mode 100644
index 0000000..1e4eb38
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/interactor/BatterySaverTileUserActionInteractor.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 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.qs.tiles.impl.battery.domain.interactor
+
+import android.content.Intent
+import android.provider.Settings
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.battery.domain.model.BatterySaverTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.android.systemui.statusbar.policy.BatteryController
+import javax.inject.Inject
+
+/** Handles airplane mode tile clicks and long clicks. */
+class BatterySaverTileUserActionInteractor
+@Inject
+constructor(
+    private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+    private val batteryController: BatteryController
+) : QSTileUserActionInteractor<BatterySaverTileModel> {
+
+    override suspend fun handleInput(input: QSTileInput<BatterySaverTileModel>) =
+        with(input) {
+            when (action) {
+                is QSTileUserAction.Click -> {
+                    if (!data.isPluggedIn) {
+                        batteryController.setPowerSaveMode(!data.isPowerSaving, action.view)
+                    }
+                }
+                is QSTileUserAction.LongClick -> {
+                    qsTileIntentUserActionHandler.handle(
+                        action.view,
+                        Intent(Settings.ACTION_BATTERY_SAVER_SETTINGS)
+                    )
+                }
+            }
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/model/BatterySaverTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/model/BatterySaverTileModel.kt
new file mode 100644
index 0000000..dbec50d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/model/BatterySaverTileModel.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 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.qs.tiles.impl.battery.domain.model
+
+/** BatterySaver mode tile model. */
+sealed interface BatterySaverTileModel {
+
+    val isPluggedIn: Boolean
+    val isPowerSaving: Boolean
+
+    /** For when the device does not support extreme battery saver mode. */
+    data class Standard(
+        override val isPluggedIn: Boolean,
+        override val isPowerSaving: Boolean,
+    ) : BatterySaverTileModel
+
+    /**
+     * For when device supports extreme battery saver mode. Whether or not that mode is enabled is
+     * determined through [isExtremeSaving].
+     */
+    data class Extreme(
+        override val isPluggedIn: Boolean,
+        override val isPowerSaving: Boolean,
+        val isExtremeSaving: Boolean,
+    ) : BatterySaverTileModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt
new file mode 100644
index 0000000..0c08fba
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 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.qs.tiles.impl.battery.ui
+
+import android.content.res.Resources
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.battery.domain.model.BatterySaverTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/** Maps [BatterySaverTileModel] to [QSTileState]. */
+open class BatterySaverTileMapper
+@Inject
+constructor(
+    @Main protected val resources: Resources,
+    private val theme: Resources.Theme,
+) : QSTileDataToStateMapper<BatterySaverTileModel> {
+
+    override fun map(config: QSTileConfig, data: BatterySaverTileModel): QSTileState =
+        QSTileState.build(resources, theme, config.uiConfig) {
+            label = resources.getString(R.string.battery_detail_switch_title)
+            contentDescription = label
+
+            icon = {
+                Icon.Loaded(
+                    resources.getDrawable(
+                        if (data.isPowerSaving) R.drawable.qs_battery_saver_icon_on
+                        else R.drawable.qs_battery_saver_icon_off,
+                        theme
+                    ),
+                    null
+                )
+            }
+
+            sideViewIcon = QSTileState.SideViewIcon.None
+
+            if (data.isPluggedIn) {
+                activationState = QSTileState.ActivationState.UNAVAILABLE
+                supportedActions = setOf(QSTileState.UserAction.LONG_CLICK)
+                secondaryLabel = ""
+            } else if (data.isPowerSaving) {
+                activationState = QSTileState.ActivationState.ACTIVE
+                supportedActions =
+                    setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+
+                if (data is BatterySaverTileModel.Extreme) {
+                    secondaryLabel =
+                        resources.getString(
+                            if (data.isExtremeSaving) R.string.extreme_battery_saver_text
+                            else R.string.standard_battery_saver_text
+                        )
+                    stateDescription = secondaryLabel
+                } else {
+                    secondaryLabel = ""
+                }
+            } else {
+                activationState = QSTileState.ActivationState.INACTIVE
+                supportedActions =
+                    setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+                secondaryLabel = ""
+            }
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt
new file mode 100644
index 0000000..caae4d2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2024 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.qs.tiles.impl.internet.domain
+
+import android.content.Context
+import android.content.res.Resources
+import android.widget.Switch
+import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text.Companion.loadText
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/** Maps [InternetTileModel] to [QSTileState]. */
+class InternetTileMapper
+@Inject
+constructor(
+    @Main private val resources: Resources,
+    private val theme: Resources.Theme,
+    private val context: Context,
+) : QSTileDataToStateMapper<InternetTileModel> {
+
+    override fun map(config: QSTileConfig, data: InternetTileModel): QSTileState =
+        QSTileState.build(resources, theme, config.uiConfig) {
+            label = resources.getString(R.string.quick_settings_internet_label)
+            expandedAccessibilityClass = Switch::class
+
+            if (data.secondaryLabel != null) {
+                secondaryLabel = data.secondaryLabel.loadText(context)
+            } else {
+                secondaryLabel = data.secondaryTitle
+            }
+
+            stateDescription = data.stateDescription.loadContentDescription(context)
+            contentDescription = data.contentDescription.loadContentDescription(context)
+
+            if (data.icon != null) {
+                this.icon = { data.icon }
+            } else if (data.iconId != null) {
+                val loadedIcon =
+                    Icon.Loaded(
+                        resources.getDrawable(data.iconId!!, theme),
+                        contentDescription = null
+                    )
+                this.icon = { loadedIcon }
+            }
+
+            sideViewIcon = QSTileState.SideViewIcon.Chevron
+
+            activationState =
+                if (data is InternetTileModel.Active) QSTileState.ActivationState.ACTIVE
+                else QSTileState.ActivationState.INACTIVE
+
+            supportedActions =
+                setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt
new file mode 100644
index 0000000..fdc596b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2024 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.qs.tiles.impl.internet.domain.interactor
+
+import android.annotation.StringRes
+import android.content.Context
+import android.os.UserHandle
+import android.text.Html
+import com.android.settingslib.graph.SignalDrawable
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.ethernet.domain.EthernetInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
+import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
+import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.stateIn
+
+@OptIn(ExperimentalCoroutinesApi::class)
+/** Observes internet state changes providing the [InternetTileModel]. */
+class InternetTileDataInteractor
+@Inject
+constructor(
+    private val context: Context,
+    @Application private val scope: CoroutineScope,
+    airplaneModeRepository: AirplaneModeRepository,
+    private val connectivityRepository: ConnectivityRepository,
+    ethernetInteractor: EthernetInteractor,
+    mobileIconsInteractor: MobileIconsInteractor,
+    wifiInteractor: WifiInteractor,
+) : QSTileDataInteractor<InternetTileModel> {
+    private val internetLabel: String = context.getString(R.string.quick_settings_internet_label)
+
+    // Three symmetrical Flows that can be switched upon based on the value of
+    // [DefaultConnectionModel]
+    private val wifiIconFlow: Flow<InternetTileModel> =
+        wifiInteractor.wifiNetwork.flatMapLatest {
+            val wifiIcon = WifiIcon.fromModel(it, context, showHotspotInfo = true)
+            if (it is WifiNetworkModel.Active && wifiIcon is WifiIcon.Visible) {
+                val secondary = removeDoubleQuotes(it.ssid)
+                flowOf(
+                    InternetTileModel.Active(
+                        secondaryTitle = secondary,
+                        icon = Icon.Loaded(context.getDrawable(wifiIcon.icon.res)!!, null),
+                        stateDescription = wifiIcon.contentDescription,
+                        contentDescription = ContentDescription.Loaded("$internetLabel,$secondary"),
+                    )
+                )
+            } else {
+                notConnectedFlow
+            }
+        }
+
+    private val mobileDataContentName: Flow<CharSequence?> =
+        mobileIconsInteractor.activeDataIconInteractor.flatMapLatest {
+            if (it == null) {
+                flowOf(null)
+            } else {
+                combine(it.isRoaming, it.networkTypeIconGroup) { isRoaming, networkTypeIconGroup ->
+                    val cd = loadString(networkTypeIconGroup.contentDescription)
+                    if (isRoaming) {
+                        val roaming = context.getString(R.string.data_connection_roaming)
+                        if (cd != null) {
+                            context.getString(R.string.mobile_data_text_format, roaming, cd)
+                        } else {
+                            roaming
+                        }
+                    } else {
+                        cd
+                    }
+                }
+            }
+        }
+
+    private val mobileIconFlow: Flow<InternetTileModel> =
+        mobileIconsInteractor.activeDataIconInteractor.flatMapLatest {
+            if (it == null) {
+                notConnectedFlow
+            } else {
+                combine(
+                    it.networkName,
+                    it.signalLevelIcon,
+                    mobileDataContentName,
+                ) { networkNameModel, signalIcon, dataContentDescription ->
+                    when (signalIcon) {
+                        is SignalIconModel.Cellular -> {
+                            val secondary =
+                                mobileDataContentConcat(
+                                    networkNameModel.name,
+                                    dataContentDescription
+                                )
+
+                            val stateLevel = signalIcon.level
+                            val drawable = SignalDrawable(context)
+                            drawable.setLevel(stateLevel)
+                            val loadedIcon = Icon.Loaded(drawable, null)
+
+                            InternetTileModel.Active(
+                                secondaryTitle = secondary,
+                                icon = loadedIcon,
+                                stateDescription = ContentDescription.Loaded(secondary.toString()),
+                                contentDescription = ContentDescription.Loaded(internetLabel),
+                            )
+                        }
+                        is SignalIconModel.Satellite -> {
+                            val secondary =
+                                signalIcon.icon.contentDescription.loadContentDescription(context)
+                            InternetTileModel.Active(
+                                secondaryTitle = secondary,
+                                iconId = signalIcon.icon.res,
+                                stateDescription = ContentDescription.Loaded(secondary),
+                                contentDescription = ContentDescription.Loaded(internetLabel),
+                            )
+                        }
+                    }
+                }
+            }
+        }
+
+    private fun mobileDataContentConcat(
+        networkName: String?,
+        dataContentDescription: CharSequence?
+    ): CharSequence {
+        if (dataContentDescription == null) {
+            return networkName ?: ""
+        }
+        if (networkName == null) {
+            return Html.fromHtml(dataContentDescription.toString(), 0)
+        }
+
+        return Html.fromHtml(
+            context.getString(
+                R.string.mobile_carrier_text_format,
+                networkName,
+                dataContentDescription
+            ),
+            0
+        )
+    }
+
+    private fun loadString(@StringRes resId: Int): CharSequence? =
+        if (resId != 0) {
+            context.getString(resId)
+        } else {
+            null
+        }
+
+    private val ethernetIconFlow: Flow<InternetTileModel> =
+        ethernetInteractor.icon.flatMapLatest {
+            if (it == null) {
+                notConnectedFlow
+            } else {
+                val secondary = it.contentDescription
+                flowOf(
+                    InternetTileModel.Active(
+                        secondaryLabel = secondary?.toText(),
+                        iconId = it.res,
+                        stateDescription = null,
+                        contentDescription = secondary,
+                    )
+                )
+            }
+        }
+
+    private val notConnectedFlow: StateFlow<InternetTileModel> =
+        combine(
+                wifiInteractor.areNetworksAvailable,
+                airplaneModeRepository.isAirplaneMode,
+            ) { networksAvailable, isAirplaneMode ->
+                when {
+                    isAirplaneMode -> {
+                        val secondary = context.getString(R.string.status_bar_airplane)
+                        InternetTileModel.Inactive(
+                            secondaryTitle = secondary,
+                            iconId = R.drawable.ic_qs_no_internet_unavailable,
+                            stateDescription = null,
+                            contentDescription = ContentDescription.Loaded(secondary),
+                        )
+                    }
+                    networksAvailable -> {
+                        val secondary =
+                            context.getString(R.string.quick_settings_networks_available)
+                        InternetTileModel.Inactive(
+                            secondaryTitle = secondary,
+                            iconId = R.drawable.ic_qs_no_internet_available,
+                            stateDescription = null,
+                            contentDescription =
+                                ContentDescription.Loaded("$internetLabel,$secondary")
+                        )
+                    }
+                    else -> {
+                        NOT_CONNECTED_NETWORKS_UNAVAILABLE
+                    }
+                }
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), NOT_CONNECTED_NETWORKS_UNAVAILABLE)
+
+    /**
+     * Consumable flow describing the correct state for the InternetTile.
+     *
+     * Strict ordering of which repo is sending its data to the internet tile. Swaps between each of
+     * the interim providers (wifi, mobile, ethernet, or not-connected).
+     */
+    override fun tileData(
+        user: UserHandle,
+        triggers: Flow<DataUpdateTrigger>
+    ): Flow<InternetTileModel> =
+        connectivityRepository.defaultConnections.flatMapLatest {
+            when {
+                it.ethernet.isDefault -> ethernetIconFlow
+                it.mobile.isDefault || it.carrierMerged.isDefault -> mobileIconFlow
+                it.wifi.isDefault -> wifiIconFlow
+                else -> notConnectedFlow
+            }
+        }
+
+    override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true)
+
+    private companion object {
+        val NOT_CONNECTED_NETWORKS_UNAVAILABLE =
+            InternetTileModel.Inactive(
+                secondaryLabel = Text.Resource(R.string.quick_settings_networks_unavailable),
+                iconId = R.drawable.ic_qs_no_internet_unavailable,
+                stateDescription = null,
+                contentDescription =
+                    ContentDescription.Resource(R.string.quick_settings_networks_unavailable),
+            )
+
+        fun removeDoubleQuotes(string: String?): String? {
+            if (string == null) return null
+            return if (string.firstOrNull() == '"' && string.lastOrNull() == '"') {
+                string.substring(1, string.length - 1)
+            } else string
+        }
+
+        fun ContentDescription.toText(): Text =
+            when (this) {
+                is ContentDescription.Loaded -> Text.Loaded(this.description)
+                is ContentDescription.Resource -> Text.Resource(this.res)
+            }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt
new file mode 100644
index 0000000..2620cd5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 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.qs.tiles.impl.internet.domain.interactor
+
+import android.content.Intent
+import android.provider.Settings
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.dialog.InternetDialogManager
+import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.android.systemui.statusbar.connectivity.AccessPointController
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.withContext
+
+/** Handles internet tile clicks. */
+class InternetTileUserActionInteractor
+@Inject
+constructor(
+    @Main private val mainContext: CoroutineContext,
+    private val internetDialogManager: InternetDialogManager,
+    private val accessPointController: AccessPointController,
+    private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+) : QSTileUserActionInteractor<InternetTileModel> {
+
+    override suspend fun handleInput(input: QSTileInput<InternetTileModel>): Unit =
+        with(input) {
+            when (action) {
+                is QSTileUserAction.Click -> {
+                    withContext(mainContext) {
+                        internetDialogManager.create(
+                            aboveStatusBar = true,
+                            accessPointController.canConfigMobileData(),
+                            accessPointController.canConfigWifi(),
+                            action.view,
+                        )
+                    }
+                }
+                is QSTileUserAction.LongClick -> {
+                    qsTileIntentUserActionHandler.handle(
+                        action.view,
+                        Intent(Settings.ACTION_WIFI_SETTINGS)
+                    )
+                }
+            }
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/model/InternetTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/model/InternetTileModel.kt
new file mode 100644
index 0000000..ece90461
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/model/InternetTileModel.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2024 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.qs.tiles.impl.internet.domain.model
+
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+
+/** Model describing the state that the QS Internet tile should be in. */
+sealed interface InternetTileModel {
+    val secondaryTitle: CharSequence?
+    val secondaryLabel: Text?
+    val iconId: Int?
+    val icon: Icon?
+    val stateDescription: ContentDescription?
+    val contentDescription: ContentDescription?
+
+    data class Active(
+        override val secondaryTitle: CharSequence? = null,
+        override val secondaryLabel: Text? = null,
+        override val iconId: Int? = null,
+        override val icon: Icon? = null,
+        override val stateDescription: ContentDescription? = null,
+        override val contentDescription: ContentDescription? = null,
+    ) : InternetTileModel
+
+    data class Inactive(
+        override val secondaryTitle: CharSequence? = null,
+        override val secondaryLabel: Text? = null,
+        override val iconId: Int? = null,
+        override val icon: Icon? = null,
+        override val stateDescription: ContentDescription? = null,
+        override val contentDescription: ContentDescription? = null,
+    ) : InternetTileModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
index d0585d3..20bd7c6 100644
--- a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
+++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
@@ -64,8 +64,6 @@
     private String mScrimName;
     private int mTintColor;
     private boolean mBlendWithMainColor = true;
-    private Runnable mChangeRunnable;
-    private Executor mChangeRunnableExecutor;
     private Executor mExecutor;
     private Looper mExecutorLooper;
     @Nullable
@@ -270,9 +268,6 @@
             mDrawable.invalidateSelf();
         }
 
-        if (mChangeRunnable != null) {
-            mChangeRunnableExecutor.execute(mChangeRunnable);
-        }
     }
 
     public int getTint() {
@@ -300,9 +295,6 @@
                 mViewAlpha = alpha;
 
                 mDrawable.setAlpha((int) (255 * alpha));
-                if (mChangeRunnable != null) {
-                    mChangeRunnableExecutor.execute(mChangeRunnable);
-                }
             }
         });
     }
@@ -311,14 +303,6 @@
         return mViewAlpha;
     }
 
-    /**
-     * Sets a callback that is invoked whenever the alpha, color, or tint change.
-     */
-    public void setChangeRunnable(Runnable changeRunnable, Executor changeRunnableExecutor) {
-        mChangeRunnable = changeRunnable;
-        mChangeRunnableExecutor = changeRunnableExecutor;
-    }
-
     @Override
     protected boolean canReceivePointerEvents() {
         return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index 4ee8349..81f644f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -352,10 +352,6 @@
     /** Called by the touch helper when the drag down was aborted and should be reset. */
     internal fun onDragDownReset() {
         logger.logDragDownAborted()
-        nsslController.setDimmed(
-            /* dimmed= */ true,
-            /* animate= */ true,
-        )
         nsslController.resetScrollPosition()
         nsslController.resetCheckSnoozeLeavebehind()
         setDragDownAmountAnimated(0f)
@@ -366,12 +362,7 @@
      *
      * @param above whether they dragged above it
      */
-    internal fun onCrossedThreshold(above: Boolean) {
-        nsslController.setDimmed(
-            /* dimmed= */ !above,
-            /* animate= */ true,
-        )
-    }
+    internal fun onCrossedThreshold(above: Boolean) {}
 
     /** Called by the touch helper when the drag down was started */
     internal fun onDragDownStarted(startingChild: ExpandableView?) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 1a06eec..0091bc5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -420,7 +420,7 @@
         filter.addAction(Intent.ACTION_USER_UNLOCKED);
         filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
         filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
-        if (allowPrivateProfile()){
+        if (privateSpaceFlagsEnabled()) {
             filter.addAction(Intent.ACTION_PROFILE_AVAILABLE);
             filter.addAction(Intent.ACTION_PROFILE_UNAVAILABLE);
         }
@@ -813,13 +813,17 @@
     }
 
     private boolean profileAvailabilityActions(String action){
-        return allowPrivateProfile()?
+        return privateSpaceFlagsEnabled()?
                 Objects.equals(action,Intent.ACTION_PROFILE_AVAILABLE)||
                         Objects.equals(action,Intent.ACTION_PROFILE_UNAVAILABLE):
                 Objects.equals(action,Intent.ACTION_MANAGED_PROFILE_AVAILABLE)||
                         Objects.equals(action,Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
     }
 
+    private static boolean privateSpaceFlagsEnabled() {
+        return allowPrivateProfile() && android.multiuser.Flags.enablePrivateSpaceFeatures();
+    }
+
     @Override
     public void dump(PrintWriter pw, String[] args) {
         pw.println("NotificationLockscreenUserManager state:");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
index 642eacc..c4d9cbf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
@@ -35,6 +35,10 @@
 import com.android.systemui.qs.tiles.impl.airplane.domain.interactor.AirplaneModeTileDataInteractor
 import com.android.systemui.qs.tiles.impl.airplane.domain.interactor.AirplaneModeTileUserActionInteractor
 import com.android.systemui.qs.tiles.impl.airplane.domain.model.AirplaneModeTileModel
+import com.android.systemui.qs.tiles.impl.internet.domain.InternetTileMapper
+import com.android.systemui.qs.tiles.impl.internet.domain.interactor.InternetTileDataInteractor
+import com.android.systemui.qs.tiles.impl.internet.domain.interactor.InternetTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel
 import com.android.systemui.qs.tiles.impl.saver.domain.DataSaverTileMapper
 import com.android.systemui.qs.tiles.impl.saver.domain.interactor.DataSaverTileDataInteractor
 import com.android.systemui.qs.tiles.impl.saver.domain.interactor.DataSaverTileUserActionInteractor
@@ -90,6 +94,7 @@
 
         const val AIRPLANE_MODE_TILE_SPEC = "airplane"
         const val DATA_SAVER_TILE_SPEC = "saver"
+        const val INTERNET_TILE_SPEC = "internet"
 
         /** Inject InternetTile or InternetTileNewImpl into tileMap in QSModule */
         @Provides
@@ -168,5 +173,36 @@
                 stateInteractor,
                 mapper,
             )
+
+        @Provides
+        @IntoMap
+        @StringKey(INTERNET_TILE_SPEC)
+        fun provideInternetTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+            QSTileConfig(
+                tileSpec = TileSpec.create(INTERNET_TILE_SPEC),
+                uiConfig =
+                    QSTileUIConfig.Resource(
+                        iconRes = R.drawable.ic_qs_no_internet_available,
+                        labelRes = R.string.quick_settings_internet_label,
+                    ),
+                instanceId = uiEventLogger.getNewInstanceId(),
+            )
+
+        /** Inject InternetTile into tileViewModelMap in QSModule */
+        @Provides
+        @IntoMap
+        @StringKey(INTERNET_TILE_SPEC)
+        fun provideInternetTileViewModel(
+            factory: QSTileViewModelFactory.Static<InternetTileModel>,
+            mapper: InternetTileMapper,
+            stateInteractor: InternetTileDataInteractor,
+            userActionInteractor: InternetTileUserActionInteractor
+        ): QSTileViewModel =
+            factory.create(
+                TileSpec.create(INTERNET_TILE_SPEC),
+                userActionInteractor,
+                stateInteractor,
+                mapper,
+            )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index ea9df9a..05e8717 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -275,15 +275,6 @@
         return getHeight();
     }
 
-    /**
-     * Sets the notification as dimmed. The default implementation does nothing.
-     *
-     * @param dimmed Whether the notification should be dimmed.
-     * @param fade Whether an animation should be played to change the state.
-     */
-    public void setDimmed(boolean dimmed, boolean fade) {
-    }
-
     public boolean isRemoved() {
         return false;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt
index dab89c5..b90aa10 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt
@@ -19,7 +19,9 @@
 import android.widget.flags.Flags.notifLinearlayoutOptimized
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.statusbar.notification.shared.NotificationViewFlipperPausing
 import javax.inject.Inject
+import javax.inject.Provider
 
 interface NotifRemoteViewsFactoryContainer {
     val factories: Set<NotifRemoteViewsFactory>
@@ -31,7 +33,8 @@
     featureFlags: FeatureFlags,
     precomputedTextViewFactory: PrecomputedTextViewFactory,
     bigPictureLayoutInflaterFactory: BigPictureLayoutInflaterFactory,
-    optimizedLinearLayoutFactory: NotificationOptimizedLinearLayoutFactory
+    optimizedLinearLayoutFactory: NotificationOptimizedLinearLayoutFactory,
+    notificationViewFlipperFactory: Provider<NotificationViewFlipperFactory>,
 ) : NotifRemoteViewsFactoryContainer {
     override val factories: Set<NotifRemoteViewsFactory> = buildSet {
         add(precomputedTextViewFactory)
@@ -41,5 +44,8 @@
         if (notifLinearlayoutOptimized()) {
             add(optimizedLinearLayoutFactory)
         }
+        if (NotificationViewFlipperPausing.isEnabled) {
+            add(notificationViewFlipperFactory.get())
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index c17ee39..f835cca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -42,6 +42,7 @@
 import android.view.ViewGroup;
 import android.widget.RemoteViews;
 
+import com.android.app.tracing.TraceUtils;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.widget.ImageMessageConsumer;
 import com.android.systemui.dagger.SysUISingleton;
@@ -369,49 +370,55 @@
             ExpandableNotificationRow row,
             NotifLayoutInflaterFactory.Provider notifLayoutInflaterFactoryProvider,
             NotificationContentInflaterLogger logger) {
-        InflationProgress result = new InflationProgress();
-        final NotificationEntry entryForLogging = row.getEntry();
+        return TraceUtils.trace("NotificationContentInflater.createRemoteViews", () -> {
+            InflationProgress result = new InflationProgress();
+            final NotificationEntry entryForLogging = row.getEntry();
 
-        if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) {
-            logger.logAsyncTaskProgress(entryForLogging, "creating contracted remote view");
-            result.newContentView = createContentView(builder, isLowPriority, usesIncreasedHeight);
-        }
-
-        if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0) {
-            logger.logAsyncTaskProgress(entryForLogging, "creating expanded remote view");
-            result.newExpandedView = createExpandedView(builder, isLowPriority);
-        }
-
-        if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) {
-            logger.logAsyncTaskProgress(entryForLogging, "creating heads up remote view");
-            result.newHeadsUpView = builder.createHeadsUpContentView(usesIncreasedHeadsUpHeight);
-        }
-
-        if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) {
-            logger.logAsyncTaskProgress(entryForLogging, "creating public remote view");
-            result.newPublicView = builder.makePublicContentView(isLowPriority);
-        }
-
-        if (AsyncGroupHeaderViewInflation.isEnabled()) {
-            if ((reInflateFlags & FLAG_GROUP_SUMMARY_HEADER) != 0) {
-                logger.logAsyncTaskProgress(entryForLogging,
-                        "creating group summary remote view");
-                result.mNewGroupHeaderView = builder.makeNotificationGroupHeader();
+            if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) {
+                logger.logAsyncTaskProgress(entryForLogging, "creating contracted remote view");
+                result.newContentView = createContentView(builder, isLowPriority,
+                        usesIncreasedHeight);
             }
 
-            if ((reInflateFlags & FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER) != 0) {
-                logger.logAsyncTaskProgress(entryForLogging,
-                        "creating low-priority group summary remote view");
-                result.mNewLowPriorityGroupHeaderView =
-                        builder.makeLowPriorityContentView(true /* useRegularSubtext */);
+            if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0) {
+                logger.logAsyncTaskProgress(entryForLogging, "creating expanded remote view");
+                result.newExpandedView = createExpandedView(builder, isLowPriority);
             }
-        }
-        setNotifsViewsInflaterFactory(result, row, notifLayoutInflaterFactoryProvider);
-        result.packageContext = packageContext;
-        result.headsUpStatusBarText = builder.getHeadsUpStatusBarText(false /* showingPublic */);
-        result.headsUpStatusBarTextPublic = builder.getHeadsUpStatusBarText(
-                true /* showingPublic */);
-        return result;
+
+            if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) {
+                logger.logAsyncTaskProgress(entryForLogging, "creating heads up remote view");
+                result.newHeadsUpView = builder.createHeadsUpContentView(
+                        usesIncreasedHeadsUpHeight);
+            }
+
+            if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) {
+                logger.logAsyncTaskProgress(entryForLogging, "creating public remote view");
+                result.newPublicView = builder.makePublicContentView(isLowPriority);
+            }
+
+            if (AsyncGroupHeaderViewInflation.isEnabled()) {
+                if ((reInflateFlags & FLAG_GROUP_SUMMARY_HEADER) != 0) {
+                    logger.logAsyncTaskProgress(entryForLogging,
+                            "creating group summary remote view");
+                    result.mNewGroupHeaderView = builder.makeNotificationGroupHeader();
+                }
+
+                if ((reInflateFlags & FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER) != 0) {
+                    logger.logAsyncTaskProgress(entryForLogging,
+                            "creating low-priority group summary remote view");
+                    result.mNewLowPriorityGroupHeaderView =
+                            builder.makeLowPriorityContentView(true /* useRegularSubtext */);
+                }
+            }
+            setNotifsViewsInflaterFactory(result, row, notifLayoutInflaterFactoryProvider);
+            result.packageContext = packageContext;
+            result.headsUpStatusBarText = builder.getHeadsUpStatusBarText(
+                    false /* showingPublic */);
+            result.headsUpStatusBarTextPublic = builder.getHeadsUpStatusBarText(
+                    true /* showingPublic */);
+
+            return result;
+        });
     }
 
     private static void setNotifsViewsInflaterFactory(InflationProgress result,
@@ -445,6 +452,8 @@
             RemoteViews.InteractionHandler remoteViewClickHandler,
             @Nullable InflationCallback callback,
             NotificationContentInflaterLogger logger) {
+        Trace.beginAsyncSection(APPLY_TRACE_METHOD, System.identityHashCode(row));
+
         NotificationContentView privateLayout = row.getPrivateLayout();
         NotificationContentView publicLayout = row.getPublicLayout();
         final HashMap<Integer, CancellationSignal> runningInflations = new HashMap<>();
@@ -621,6 +630,7 @@
         cancellationSignal.setOnCancelListener(
                 () -> {
                     logger.logAsyncTaskProgress(entry, "apply cancelled");
+                    Trace.endAsyncSection(APPLY_TRACE_METHOD, System.identityHashCode(row));
                     runningInflations.values().forEach(CancellationSignal::cancel);
                 });
 
@@ -769,17 +779,17 @@
         if (!requiresHeightCheck(entry)) {
             return true;
         }
-        Trace.beginSection("NotificationContentInflater#satisfiesMinHeightRequirement");
-        int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
-        int referenceWidth = resources.getDimensionPixelSize(
-                R.dimen.notification_validation_reference_width);
-        int widthSpec = View.MeasureSpec.makeMeasureSpec(referenceWidth, View.MeasureSpec.EXACTLY);
-        view.measure(widthSpec, heightSpec);
-        int minHeight = resources.getDimensionPixelSize(
-                R.dimen.notification_validation_minimum_allowed_height);
-        boolean result = view.getMeasuredHeight() >= minHeight;
-        Trace.endSection();
-        return result;
+        return TraceUtils.trace("NotificationContentInflater#satisfiesMinHeightRequirement", () -> {
+            int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+            int referenceWidth = resources.getDimensionPixelSize(
+                    R.dimen.notification_validation_reference_width);
+            int widthSpec = View.MeasureSpec.makeMeasureSpec(referenceWidth,
+                    View.MeasureSpec.EXACTLY);
+            view.measure(widthSpec, heightSpec);
+            int minHeight = resources.getDimensionPixelSize(
+                    R.dimen.notification_validation_minimum_allowed_height);
+            return view.getMeasuredHeight() >= minHeight;
+        });
     }
 
     /**
@@ -966,6 +976,7 @@
 
         entry.headsUpStatusBarText = result.headsUpStatusBarText;
         entry.headsUpStatusBarTextPublic = result.headsUpStatusBarTextPublic;
+        Trace.endAsyncSection(APPLY_TRACE_METHOD, System.identityHashCode(row));
         if (endListener != null) {
             endListener.onAsyncInflationFinished(entry);
         }
@@ -1102,83 +1113,97 @@
         }
 
         @Override
+        protected void onPreExecute() {
+            Trace.beginAsyncSection(ASYNC_TASK_TRACE_METHOD, System.identityHashCode(this));
+        }
+
+        @Override
         protected InflationProgress doInBackground(Void... params) {
-            try {
-                final StatusBarNotification sbn = mEntry.getSbn();
-                // Ensure the ApplicationInfo is updated before a builder is recovered.
-                updateApplicationInfo(sbn);
-                final Notification.Builder recoveredBuilder
-                        = Notification.Builder.recoverBuilder(mContext,
-                        sbn.getNotification());
+            return TraceUtils.trace("NotificationContentInflater.AsyncInflationTask#doInBackground",
+                    () -> {
+                        try {
+                            return doInBackgroundInternal();
+                        } catch (Exception e) {
+                            mError = e;
+                            mLogger.logAsyncTaskException(mEntry, "inflating", e);
+                            return null;
+                        }
+                    });
+        }
 
-                Context packageContext = sbn.getPackageContext(mContext);
-                if (recoveredBuilder.usesTemplate()) {
-                    // For all of our templates, we want it to be RTL
-                    packageContext = new RtlEnabledContext(packageContext);
-                }
-                boolean isConversation = mEntry.getRanking().isConversation();
-                Notification.MessagingStyle messagingStyle = null;
-                if (isConversation) {
-                    messagingStyle = mConversationProcessor.processNotification(
-                            mEntry, recoveredBuilder, mLogger);
-                }
-                InflationProgress inflationProgress = createRemoteViews(mReInflateFlags,
-                        recoveredBuilder, mIsLowPriority, mUsesIncreasedHeight,
-                        mUsesIncreasedHeadsUpHeight, packageContext, mRow,
-                        mNotifLayoutInflaterFactoryProvider, mLogger);
+        private InflationProgress doInBackgroundInternal() {
+            final StatusBarNotification sbn = mEntry.getSbn();
+            // Ensure the ApplicationInfo is updated before a builder is recovered.
+            updateApplicationInfo(sbn);
+            final Notification.Builder recoveredBuilder = Notification.Builder.recoverBuilder(
+                    mContext, sbn.getNotification());
 
-                mLogger.logAsyncTaskProgress(mEntry,
-                        "getting existing smart reply state (on wrong thread!)");
-                InflatedSmartReplyState previousSmartReplyState = mRow.getExistingSmartReplyState();
-                mLogger.logAsyncTaskProgress(mEntry, "inflating smart reply views");
-                InflationProgress result = inflateSmartReplyViews(
-                        /* result = */ inflationProgress,
-                        mReInflateFlags,
-                        mEntry,
-                        mContext,
-                        packageContext,
-                        previousSmartReplyState,
-                        mSmartRepliesInflater,
-                        mLogger);
-
-                if (AsyncHybridViewInflation.isEnabled()) {
-                    // Inflate the single-line content view's ViewModel and ViewHolder from the
-                    // background thread, the ViewHolder needs to be bind with ViewModel later from
-                    // the main thread.
-                    result.mInflatedSingleLineViewModel = SingleLineViewInflater
-                            .inflateSingleLineViewModel(
-                                    mEntry.getSbn().getNotification(),
-                                    messagingStyle,
-                                    recoveredBuilder,
-                                    mContext
-                            );
-                    result.mInflatedSingleLineViewHolder =
-                            SingleLineViewInflater.inflateSingleLineViewHolder(
-                                    isConversation,
-                                    mReInflateFlags,
-                                    mEntry,
-                                    mContext,
-                                    mLogger
-                            );
-                }
-
-                mLogger.logAsyncTaskProgress(mEntry,
-                        "getting row image resolver (on wrong thread!)");
-                final NotificationInlineImageResolver imageResolver = mRow.getImageResolver();
-                // wait for image resolver to finish preloading
-                mLogger.logAsyncTaskProgress(mEntry, "waiting for preloaded images");
-                imageResolver.waitForPreloadedImages(IMG_PRELOAD_TIMEOUT_MS);
-
-                return result;
-            } catch (Exception e) {
-                mError = e;
-                mLogger.logAsyncTaskException(mEntry, "inflating", e);
-                return null;
+            Context packageContext = sbn.getPackageContext(mContext);
+            if (recoveredBuilder.usesTemplate()) {
+                // For all of our templates, we want it to be RTL
+                packageContext = new RtlEnabledContext(packageContext);
             }
+            boolean isConversation = mEntry.getRanking().isConversation();
+            Notification.MessagingStyle messagingStyle = null;
+            if (isConversation) {
+                messagingStyle = mConversationProcessor.processNotification(
+                        mEntry, recoveredBuilder, mLogger);
+            }
+            InflationProgress inflationProgress = createRemoteViews(mReInflateFlags,
+                    recoveredBuilder, mIsLowPriority, mUsesIncreasedHeight,
+                    mUsesIncreasedHeadsUpHeight, packageContext, mRow,
+                    mNotifLayoutInflaterFactoryProvider, mLogger);
+
+            mLogger.logAsyncTaskProgress(mEntry,
+                    "getting existing smart reply state (on wrong thread!)");
+            InflatedSmartReplyState previousSmartReplyState =
+                    mRow.getExistingSmartReplyState();
+            mLogger.logAsyncTaskProgress(mEntry, "inflating smart reply views");
+            InflationProgress result = inflateSmartReplyViews(
+                    /* result = */ inflationProgress,
+                    mReInflateFlags,
+                    mEntry,
+                    mContext,
+                    packageContext,
+                    previousSmartReplyState,
+                    mSmartRepliesInflater,
+                    mLogger);
+
+            if (AsyncHybridViewInflation.isEnabled()) {
+                // Inflate the single-line content view's ViewModel and ViewHolder from the
+                // background thread, the ViewHolder needs to be bind with ViewModel later from
+                // the main thread.
+                result.mInflatedSingleLineViewModel = SingleLineViewInflater
+                        .inflateSingleLineViewModel(
+                                mEntry.getSbn().getNotification(),
+                                messagingStyle,
+                                recoveredBuilder,
+                                mContext
+                        );
+                result.mInflatedSingleLineViewHolder =
+                        SingleLineViewInflater.inflateSingleLineViewHolder(
+                                isConversation,
+                                mReInflateFlags,
+                                mEntry,
+                                mContext,
+                                mLogger
+                        );
+            }
+
+            mLogger.logAsyncTaskProgress(mEntry,
+                    "getting row image resolver (on wrong thread!)");
+            final NotificationInlineImageResolver imageResolver = mRow.getImageResolver();
+            // wait for image resolver to finish preloading
+            mLogger.logAsyncTaskProgress(mEntry, "waiting for preloaded images");
+            imageResolver.waitForPreloadedImages(IMG_PRELOAD_TIMEOUT_MS);
+
+            return result;
         }
 
         @Override
         protected void onPostExecute(InflationProgress result) {
+            Trace.endAsyncSection(ASYNC_TASK_TRACE_METHOD, System.identityHashCode(this));
+
             if (mError == null) {
                 // Logged in detail in apply.
                 mCancellationSignal = apply(
@@ -1197,6 +1222,11 @@
             }
         }
 
+        @Override
+        protected void onCancelled(InflationProgress result) {
+            Trace.endAsyncSection(ASYNC_TASK_TRACE_METHOD, System.identityHashCode(this));
+        }
+
         private void handleError(Exception e) {
             mEntry.onInflationTaskFinished();
             StatusBarNotification sbn = mEntry.getSbn();
@@ -1294,4 +1324,8 @@
         public abstract void setResultView(View v);
         public abstract RemoteViews getRemoteView();
     }
+
+    private static final String ASYNC_TASK_TRACE_METHOD =
+            "NotificationContentInflater.AsyncInflationTask";
+    private static final String APPLY_TRACE_METHOD = "NotificationContentInflater#apply";
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationViewFlipperFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationViewFlipperFactory.kt
new file mode 100644
index 0000000..0594c12
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationViewFlipperFactory.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 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.statusbar.notification.row
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import android.widget.ViewFlipper
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag
+import com.android.systemui.statusbar.notification.row.ui.viewbinder.NotificationViewFlipperBinder
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.NotificationViewFlipperViewModel
+import com.android.systemui.statusbar.notification.shared.NotificationViewFlipperPausing
+import javax.inject.Inject
+
+/**
+ * A factory which owns the construction of any ViewFlipper inside of Notifications, and binds it
+ * with a view model. This ensures that ViewFlippers are paused when the keyguard is showing.
+ */
+class NotificationViewFlipperFactory
+@Inject
+constructor(
+    private val viewModel: NotificationViewFlipperViewModel,
+) : NotifRemoteViewsFactory {
+    init {
+        /* check if */ NotificationViewFlipperPausing.isUnexpectedlyInLegacyMode()
+    }
+
+    override fun instantiate(
+        row: ExpandableNotificationRow,
+        @InflationFlag layoutType: Int,
+        parent: View?,
+        name: String,
+        context: Context,
+        attrs: AttributeSet
+    ): View? {
+        return when (name) {
+            ViewFlipper::class.java.name,
+            ViewFlipper::class.java.simpleName ->
+                ViewFlipper(context, attrs).also { viewFlipper ->
+                    NotificationViewFlipperBinder.bindWhileAttached(viewFlipper, viewModel)
+                }
+            else -> null
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/NotificationViewFlipperBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/NotificationViewFlipperBinder.kt
new file mode 100644
index 0000000..133d3e7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/NotificationViewFlipperBinder.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 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.statusbar.notification.row.ui.viewbinder
+
+import android.widget.ViewFlipper
+import androidx.lifecycle.lifecycleScope
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.NotificationViewFlipperViewModel
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
+
+/** Binds a [NotificationViewFlipper] to its [view model][NotificationViewFlipperViewModel]. */
+object NotificationViewFlipperBinder {
+    fun bindWhileAttached(
+        viewFlipper: ViewFlipper,
+        viewModel: NotificationViewFlipperViewModel,
+    ): DisposableHandle {
+        if (viewFlipper.isAutoStart) {
+            // If the ViewFlipper is not set to AutoStart, the pause binding is meaningless
+            return DisposableHandle {}
+        }
+        return viewFlipper.repeatWhenAttached {
+            lifecycleScope.launch { bind(viewFlipper, viewModel) }
+        }
+    }
+
+    suspend fun bind(
+        viewFlipper: ViewFlipper,
+        viewModel: NotificationViewFlipperViewModel,
+    ) = coroutineScope { launch { viewModel.isPaused.collect { viewFlipper.setPaused(it) } } }
+
+    private fun ViewFlipper.setPaused(paused: Boolean) {
+        if (paused) {
+            stopFlipping()
+        } else if (isAutoStart) {
+            startFlipping()
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModel.kt
new file mode 100644
index 0000000..7694e58
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModel.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 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.statusbar.notification.row.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.notification.shared.NotificationViewFlipperPausing
+import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackInteractor
+import com.android.systemui.util.kotlin.FlowDumperImpl
+import javax.inject.Inject
+
+/** A model which represents whether ViewFlippers inside notifications should be paused. */
+@SysUISingleton
+class NotificationViewFlipperViewModel
+@Inject
+constructor(
+    dumpManager: DumpManager,
+    stackInteractor: NotificationStackInteractor,
+) : FlowDumperImpl(dumpManager) {
+    init {
+        /* check if */ NotificationViewFlipperPausing.isUnexpectedlyInLegacyMode()
+    }
+
+    val isPaused = stackInteractor.isShowingOnLockscreen.dumpWhileCollecting("isPaused")
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationViewFlipperPausing.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationViewFlipperPausing.kt
new file mode 100644
index 0000000..cea6a2b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationViewFlipperPausing.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 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.statusbar.notification.shared
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the notification view flipper pausing flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object NotificationViewFlipperPausing {
+    /** The aconfig flag name */
+    const val FLAG_NAME = Flags.FLAG_NOTIFICATION_VIEW_FLIPPER_PAUSING
+
+    /** A token used for dependency declaration */
+    val token: FlagToken
+        get() = FlagToken(FLAG_NAME, isEnabled)
+
+    /** Is the refactor enabled */
+    @JvmStatic
+    inline val isEnabled
+        get() = Flags.notificationViewFlipperPausing()
+
+    /**
+     * Called to ensure code is only run when the flag is enabled. This protects users from the
+     * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+     * build to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun isUnexpectedlyInLegacyMode() =
+        RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+    /**
+     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+     * the flag is enabled to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
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 c90acee..ab2f664 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
@@ -61,7 +61,6 @@
      */
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private int mScrollY;
-    private boolean mDimmed;
     private float mOverScrollTopAmount;
     private float mOverScrollBottomAmount;
     private boolean mDozing;
@@ -344,14 +343,6 @@
         this.mScrollY = Math.max(scrollY, 0);
     }
 
-    /**
-     * @param dimmed Whether we are in a dimmed state (on the lockscreen), where the backgrounds are
-     *               translucent and everything is scaled back a bit.
-     */
-    public void setDimmed(boolean dimmed) {
-        mDimmed = dimmed;
-    }
-
     /** While dozing, we draw as little as possible, assuming a black background */
     public void setDozing(boolean dozing) {
         mDozing = dozing;
@@ -375,12 +366,6 @@
         mHideSensitive = hideSensitive;
     }
 
-    public boolean isDimmed() {
-        // While we are expanding from pulse, we want the notifications not to be dimmed, otherwise
-        // you'd see the difference to the pulsing notification
-        return mDimmed && !(isPulseExpanding() && mDozeAmount == 1.0f);
-    }
-
     public boolean isDozing() {
         return mDozing;
     }
@@ -768,7 +753,6 @@
         pw.println("mHideSensitive=" + mHideSensitive);
         pw.println("mShadeExpanded=" + mShadeExpanded);
         pw.println("mClearAllInProgress=" + mClearAllInProgress);
-        pw.println("mDimmed=" + mDimmed);
         pw.println("mStatusBarState=" + mStatusBarState);
         pw.println("mExpansionChanging=" + mExpansionChanging);
         pw.println("mPanelFullWidth=" + mIsSmallScreen);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationFilter.java
index 5343cbf..03a1082 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationFilter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationFilter.java
@@ -35,7 +35,6 @@
     boolean animateZ;
     boolean animateHeight;
     boolean animateTopInset;
-    boolean animateDimmed;
     boolean animateHideSensitive;
     boolean hasDelays;
     boolean hasGoToFullShadeEvent;
@@ -83,11 +82,6 @@
         return this;
     }
 
-    public AnimationFilter animateDimmed() {
-        animateDimmed = true;
-        return this;
-    }
-
     public AnimationFilter animateHideSensitive() {
         animateHideSensitive = true;
         return this;
@@ -128,7 +122,6 @@
         animateZ |= filter.animateZ;
         animateHeight |= filter.animateHeight;
         animateTopInset |= filter.animateTopInset;
-        animateDimmed |= filter.animateDimmed;
         animateHideSensitive |= filter.animateHideSensitive;
         hasDelays |= filter.hasDelays;
         mAnimatedProperties.addAll(filter.mAnimatedProperties);
@@ -142,7 +135,6 @@
         animateZ = false;
         animateHeight = false;
         animateTopInset = false;
-        animateDimmed = false;
         animateHideSensitive = false;
         hasDelays = false;
         hasGoToFullShadeEvent = false;
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 d0c5c82..d1e5ab0 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
@@ -88,7 +88,6 @@
             | ExpandableViewState.LOCATION_MAIN_AREA;
 
     public int height;
-    public boolean dimmed;
     public boolean hideSensitive;
     public boolean belowSpeedBump;
     public boolean inShelf;
@@ -128,7 +127,6 @@
         if (viewState instanceof ExpandableViewState) {
             ExpandableViewState svs = (ExpandableViewState) viewState;
             height = svs.height;
-            dimmed = svs.dimmed;
             hideSensitive = svs.hideSensitive;
             belowSpeedBump = svs.belowSpeedBump;
             clipTopAmount = svs.clipTopAmount;
@@ -155,9 +153,6 @@
                 expandableView.setActualHeight(newHeight, false /* notifyListeners */);
             }
 
-            // apply dimming
-            expandableView.setDimmed(this.dimmed, false /* animate */);
-
             // apply hiding sensitive
             expandableView.setHideSensitive(
                     this.hideSensitive, false /* animated */, 0 /* delay */, 0 /* duration */);
@@ -216,9 +211,6 @@
             abortAnimation(child, TAG_ANIMATOR_BOTTOM_INSET);
         }
 
-        // start dimmed animation
-        expandableView.setDimmed(this.dimmed, animationFilter.animateDimmed);
-
         // apply below the speed bump
         if (!NotificationIconContainerRefactor.isEnabled()) {
             expandableView.setBelowSpeedBump(this.belowSpeedBump);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index fa97300..28f874d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -795,7 +795,6 @@
             } else {
                 childState.setZTranslation(0);
             }
-            childState.dimmed = parentState.dimmed;
             childState.hideSensitive = parentState.hideSensitive;
             childState.belowSpeedBump = parentState.belowSpeedBump;
             childState.clipTopAmount = 0;
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 27db84f..b47b18d 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
@@ -17,6 +17,8 @@
 package com.android.systemui.statusbar.notification.stack;
 
 import static android.os.Trace.TRACE_TAG_APP;
+import static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_UP;
 
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING;
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_SHADE_CLEAR_ALL;
@@ -29,10 +31,6 @@
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.TimeAnimator;
-import android.animation.ValueAnimator;
 import android.annotation.ColorInt;
 import android.annotation.DrawableRes;
 import android.annotation.FloatRange;
@@ -79,7 +77,6 @@
 
 import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.graphics.ColorUtils;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.policy.SystemBarUtils;
 import com.android.keyguard.BouncerPanelExpansionCalculator;
@@ -163,18 +160,11 @@
      * Sentinel value for no current active pointer. Used by {@link #mActivePointerId}.
      */
     private static final int INVALID_POINTER = -1;
-    /**
-     * The distance in pixels between sections when the sections are directly adjacent (no visible
-     * gap is drawn between them). In this case we don't want to round their corners.
-     */
-    private static final int DISTANCE_BETWEEN_ADJACENT_SECTIONS_PX = 1;
     private boolean mKeyguardBypassEnabled;
 
     private final ExpandHelper mExpandHelper;
     private NotificationSwipeHelper mSwipeHelper;
     private int mCurrentStackHeight = Integer.MAX_VALUE;
-    private final Paint mBackgroundPaint = new Paint();
-    private final boolean mShouldDrawNotificationBackground;
     private boolean mHighPriorityBeforeSpeedBump;
 
     private float mExpandedHeight;
@@ -263,7 +253,6 @@
     private OnEmptySpaceClickListener mOnEmptySpaceClickListener;
     private boolean mNeedsAnimation;
     private boolean mTopPaddingNeedsAnimation;
-    private boolean mDimmedNeedsAnimation;
     private boolean mHideSensitiveNeedsAnimation;
     private boolean mActivateNeedsAnimation;
     private boolean mGoToFullShadeNeedsAnimation;
@@ -350,40 +339,15 @@
         }
     };
     private final NotificationSection[] mSections;
-    private boolean mAnimateNextBackgroundTop;
-    private boolean mAnimateNextBackgroundBottom;
-    private boolean mAnimateNextSectionBoundsChange;
-    private @ColorInt int mBgColor;
-    private float mDimAmount;
-    private ValueAnimator mDimAnimator;
     private final ArrayList<ExpandableView> mTmpSortedChildren = new ArrayList<>();
-    private final Animator.AnimatorListener mDimEndListener = new AnimatorListenerAdapter() {
-        @Override
-        public void onAnimationEnd(Animator animation) {
-            mDimAnimator = null;
-        }
-    };
-    private final ValueAnimator.AnimatorUpdateListener mDimUpdateListener
-            = new ValueAnimator.AnimatorUpdateListener() {
-
-        @Override
-        public void onAnimationUpdate(ValueAnimator animation) {
-            setDimAmount((Float) animation.getAnimatedValue());
-        }
-    };
     protected ViewGroup mQsHeader;
     // Rect of QsHeader. Kept as a field just to avoid creating a new one each time.
     private final Rect mQsHeaderBound = new Rect();
     private boolean mContinuousShadowUpdate;
-    private boolean mContinuousBackgroundUpdate;
     private final ViewTreeObserver.OnPreDrawListener mShadowUpdater = () -> {
         updateViewShadows();
         return true;
     };
-    private final ViewTreeObserver.OnPreDrawListener mBackgroundUpdater = () -> {
-        updateBackground();
-        return true;
-    };
     private final Comparator<ExpandableView> mViewPositionComparator = (view, otherView) -> {
         float endY = view.getTranslationY() + view.getActualHeight();
         float otherEndY = otherView.getTranslationY() + otherView.getActualHeight();
@@ -481,7 +445,6 @@
     private boolean mHeadsUpAnimatingAway;
     private int mStatusBarState;
     private int mUpcomingStatusBarState;
-    private int mCachedBackgroundColor;
     private boolean mHeadsUpGoingAwayAnimationsAllowed = true;
     private final Runnable mReflingAndAnimateScroll = this::animateScroll;
     private int mCornerRadius;
@@ -581,7 +544,6 @@
      */
     private boolean mDismissUsingRowTranslationX = true;
     private ExpandableNotificationRow mTopHeadsUpRow;
-    private long mNumHeadsUp;
     private NotificationStackScrollLayoutController.TouchHandler mTouchHandler;
     private final ScreenOffAnimationController mScreenOffAnimationController;
     private boolean mShouldUseSplitNotificationShade;
@@ -595,7 +557,7 @@
         mSplitShadeStateController = splitShadeStateController;
         updateSplitNotificationShade();
     }
-    private FeatureFlags mFeatureFlags;
+    private final FeatureFlags mFeatureFlags;
 
     private final ExpandableView.OnHeightChangedListener mOnChildHeightChangedListener =
             new ExpandableView.OnHeightChangedListener() {
@@ -667,8 +629,6 @@
         mSections = mSectionsManager.createSectionsForBuckets();
 
         mAmbientState = Dependency.get(AmbientState.class);
-        mBgColor = Utils.getColorAttr(mContext,
-                com.android.internal.R.attr.materialColorSurfaceContainerHigh).getDefaultColor();
         int minHeight = res.getDimensionPixelSize(R.dimen.notification_min_height);
         int maxHeight = res.getDimensionPixelSize(R.dimen.notification_max_height);
         mSplitShadeMinContentHeight = res.getDimensionPixelSize(
@@ -680,16 +640,12 @@
 
         mStackScrollAlgorithm = createStackScrollAlgorithm(context);
         mStateAnimator = new StackStateAnimator(context, this);
-        mShouldDrawNotificationBackground =
-                res.getBoolean(R.bool.config_drawNotificationBackground);
         setOutlineProvider(mOutlineProvider);
 
         // We could set this whenever we 'requestChildUpdate' much like the viewTreeObserver, but
         // that adds a bunch of complexity, and drawing nothing isn't *that* expensive.
-        boolean willDraw = SceneContainerFlag.isEnabled()
-                || mShouldDrawNotificationBackground || mDebugLines;
+        boolean willDraw = SceneContainerFlag.isEnabled() || mDebugLines;
         setWillNotDraw(!willDraw);
-        mBackgroundPaint.setAntiAlias(true);
         if (mDebugLines) {
             mDebugPaint = new Paint();
             mDebugPaint.setColor(0xffff0000);
@@ -812,9 +768,6 @@
     }
 
     void updateBgColor() {
-        mBgColor = Utils.getColorAttr(mContext,
-                com.android.internal.R.attr.materialColorSurfaceContainerHigh).getDefaultColor();
-        updateBackgroundDimming();
         for (int i = 0; i < getChildCount(); i++) {
             View child = getChildAt(i);
             if (child instanceof ActivatableNotificationView activatableView) {
@@ -835,14 +788,6 @@
 
     protected void onDraw(Canvas canvas) {
         onJustBeforeDraw();
-        if (mShouldDrawNotificationBackground
-                && (mSections[0].getCurrentBounds().top
-                < mSections[mSections.length - 1].getCurrentBounds().bottom
-                || mAmbientState.isDozing())) {
-            drawBackground(canvas);
-        } else if (mInHeadsUpPinnedMode || mHeadsUpAnimatingAway) {
-            drawHeadsUpBackground(canvas);
-        }
 
         if (mDebugLines) {
             onDrawDebug(canvas);
@@ -930,150 +875,6 @@
         return textY;
     }
 
-    private void drawBackground(Canvas canvas) {
-        int lockScreenLeft = mSidePaddings;
-        int lockScreenRight = getWidth() - mSidePaddings;
-        int lockScreenTop = mSections[0].getCurrentBounds().top;
-        int lockScreenBottom = mSections[mSections.length - 1].getCurrentBounds().bottom;
-        int hiddenLeft = getWidth() / 2;
-        int hiddenTop = mTopPadding;
-
-        float yProgress = 1 - mInterpolatedHideAmount;
-        float xProgress = mHideXInterpolator.getInterpolation(
-                (1 - mLinearHideAmount) * mBackgroundXFactor);
-
-        int left = (int) MathUtils.lerp(hiddenLeft, lockScreenLeft, xProgress);
-        int right = (int) MathUtils.lerp(hiddenLeft, lockScreenRight, xProgress);
-        int top = (int) MathUtils.lerp(hiddenTop, lockScreenTop, yProgress);
-        int bottom = (int) MathUtils.lerp(hiddenTop, lockScreenBottom, yProgress);
-        mBackgroundAnimationRect.set(
-                left,
-                top,
-                right,
-                bottom);
-
-        int backgroundTopAnimationOffset = top - lockScreenTop;
-        // TODO(kprevas): this may not be necessary any more since we don't display the shelf in AOD
-        boolean anySectionHasVisibleChild = false;
-        for (NotificationSection section : mSections) {
-            if (section.needsBackground()) {
-                anySectionHasVisibleChild = true;
-                break;
-            }
-        }
-        boolean shouldDrawBackground;
-        if (mKeyguardBypassEnabled && onKeyguard()) {
-            shouldDrawBackground = isPulseExpanding();
-        } else {
-            shouldDrawBackground = !mAmbientState.isDozing() || anySectionHasVisibleChild;
-        }
-        if (shouldDrawBackground) {
-            drawBackgroundRects(canvas, left, right, top, backgroundTopAnimationOffset);
-        }
-
-        updateClipping();
-    }
-
-    /**
-     * Draws round rects for each background section.
-     * <p>
-     * We want to draw a round rect for each background section as defined by {@link #mSections}.
-     * However, if two sections are directly adjacent with no gap between them (e.g. on the
-     * lockscreen where the shelf can appear directly below the high priority section, or while
-     * scrolling the shade so that the top of the shelf is right at the bottom of the high priority
-     * section), we don't want to round the adjacent corners.
-     * <p>
-     * Since {@link Canvas} doesn't provide a way to draw a half-rounded rect, this means that we
-     * need to coalesce the backgrounds for adjacent sections and draw them as a single round rect.
-     * This method tracks the top of each rect we need to draw, then iterates through the visible
-     * sections.  If a section is not adjacent to the previous section, we draw the previous rect
-     * behind the sections we've accumulated up to that point, then start a new rect at the top of
-     * the current section.  When we're done iterating we will always have one rect left to draw.
-     */
-    private void drawBackgroundRects(Canvas canvas, int left, int right, int top,
-                                     int animationYOffset) {
-        int backgroundRectTop = top;
-        int lastSectionBottom =
-                mSections[0].getCurrentBounds().bottom + animationYOffset;
-        int currentLeft = left;
-        int currentRight = right;
-        boolean first = true;
-        for (NotificationSection section : mSections) {
-            if (!section.needsBackground()) {
-                continue;
-            }
-            int sectionTop = section.getCurrentBounds().top + animationYOffset;
-            int ownLeft = Math.min(Math.max(left, section.getCurrentBounds().left), right);
-            int ownRight = Math.max(Math.min(right, section.getCurrentBounds().right), ownLeft);
-            // If sections are directly adjacent to each other, we don't want to draw them
-            // as separate roundrects, as the rounded corners right next to each other look
-            // bad.
-            if (sectionTop - lastSectionBottom > DISTANCE_BETWEEN_ADJACENT_SECTIONS_PX
-                    || ((currentLeft != ownLeft || currentRight != ownRight) && !first)) {
-                canvas.drawRoundRect(currentLeft,
-                        backgroundRectTop,
-                        currentRight,
-                        lastSectionBottom,
-                        mCornerRadius, mCornerRadius, mBackgroundPaint);
-                backgroundRectTop = sectionTop;
-            }
-            currentLeft = ownLeft;
-            currentRight = ownRight;
-            lastSectionBottom =
-                    section.getCurrentBounds().bottom + animationYOffset;
-            first = false;
-        }
-        canvas.drawRoundRect(currentLeft,
-                backgroundRectTop,
-                currentRight,
-                lastSectionBottom,
-                mCornerRadius, mCornerRadius, mBackgroundPaint);
-    }
-
-    private void drawHeadsUpBackground(Canvas canvas) {
-        int left = mSidePaddings;
-        int right = getWidth() - mSidePaddings;
-
-        float top = getHeight();
-        float bottom = 0;
-        int childCount = getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            View child = getChildAt(i);
-            if (child.getVisibility() != View.GONE
-                    && child instanceof ExpandableNotificationRow row) {
-                if ((row.isPinned() || row.isHeadsUpAnimatingAway()) && row.getTranslation() < 0
-                        && row.getProvider().shouldShowGutsOnSnapOpen()) {
-                    top = Math.min(top, row.getTranslationY());
-                    bottom = Math.max(bottom, row.getTranslationY() + row.getActualHeight());
-                }
-            }
-        }
-
-        if (top < bottom) {
-            canvas.drawRoundRect(
-                    left, top, right, bottom,
-                    mCornerRadius, mCornerRadius, mBackgroundPaint);
-        }
-    }
-
-    void updateBackgroundDimming() {
-        // No need to update the background color if it's not being drawn.
-        if (!mShouldDrawNotificationBackground) {
-            return;
-        }
-        // Interpolate between semi-transparent notification panel background color
-        // and white AOD separator.
-        float colorInterpolation = MathUtils.smoothStep(0.4f /* start */, 1f /* end */,
-                mLinearHideAmount);
-        int color = ColorUtils.blendARGB(mBgColor, Color.WHITE, colorInterpolation);
-
-        if (mCachedBackgroundColor != color) {
-            mCachedBackgroundColor = color;
-            mBackgroundPaint.setColor(color);
-            invalidate();
-        }
-    }
-
     private void reinitView() {
         initView(getContext(), mSwipeHelper, mNotificationStackSizeCalculator);
     }
@@ -1359,9 +1160,6 @@
 
     private void onPreDrawDuringAnimation() {
         mShelf.updateAppearance();
-        if (!mNeedsAnimation && !mChildrenUpdateRequested) {
-            updateBackground();
-        }
     }
 
     private void updateScrollStateForAddedChildren() {
@@ -2565,125 +2363,6 @@
         }
     }
 
-    private void updateBackground() {
-        // No need to update the background color if it's not being drawn.
-        if (!mShouldDrawNotificationBackground) {
-            return;
-        }
-
-        updateBackgroundBounds();
-        if (didSectionBoundsChange()) {
-            boolean animate = mAnimateNextSectionBoundsChange || mAnimateNextBackgroundTop
-                    || mAnimateNextBackgroundBottom || areSectionBoundsAnimating();
-            if (!isExpanded()) {
-                abortBackgroundAnimators();
-                animate = false;
-            }
-            if (animate) {
-                startBackgroundAnimation();
-            } else {
-                for (NotificationSection section : mSections) {
-                    section.resetCurrentBounds();
-                }
-                invalidate();
-            }
-        } else {
-            abortBackgroundAnimators();
-        }
-        mAnimateNextBackgroundTop = false;
-        mAnimateNextBackgroundBottom = false;
-        mAnimateNextSectionBoundsChange = false;
-    }
-
-    private void abortBackgroundAnimators() {
-        for (NotificationSection section : mSections) {
-            section.cancelAnimators();
-        }
-    }
-
-    private boolean didSectionBoundsChange() {
-        for (NotificationSection section : mSections) {
-            if (section.didBoundsChange()) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private boolean areSectionBoundsAnimating() {
-        for (NotificationSection section : mSections) {
-            if (section.areBoundsAnimating()) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private void startBackgroundAnimation() {
-        // TODO(kprevas): do we still need separate fields for top/bottom?
-        // or can each section manage its own animation state?
-        NotificationSection firstVisibleSection = getFirstVisibleSection();
-        NotificationSection lastVisibleSection = getLastVisibleSection();
-        for (NotificationSection section : mSections) {
-            section.startBackgroundAnimation(
-                    section == firstVisibleSection
-                            ? mAnimateNextBackgroundTop
-                            : mAnimateNextSectionBoundsChange,
-                    section == lastVisibleSection
-                            ? mAnimateNextBackgroundBottom
-                            : mAnimateNextSectionBoundsChange);
-        }
-    }
-
-    /**
-     * Update the background bounds to the new desired bounds
-     */
-    private void updateBackgroundBounds() {
-        int left = mSidePaddings;
-        int right = getWidth() - mSidePaddings;
-        for (NotificationSection section : mSections) {
-            section.getBounds().left = left;
-            section.getBounds().right = right;
-        }
-
-        if (!mIsExpanded) {
-            for (NotificationSection section : mSections) {
-                section.getBounds().top = 0;
-                section.getBounds().bottom = 0;
-            }
-            return;
-        }
-        int minTopPosition;
-        NotificationSection lastSection = getLastVisibleSection();
-        boolean onKeyguard = mStatusBarState == StatusBarState.KEYGUARD;
-        if (!onKeyguard) {
-            minTopPosition = (int) (mTopPadding + mStackTranslation);
-        } else if (lastSection == null) {
-            minTopPosition = mTopPadding;
-        } else {
-            // The first sections could be empty while there could still be elements in later
-            // sections. The position of these first few sections is determined by the position of
-            // the first visible section.
-            NotificationSection firstVisibleSection = getFirstVisibleSection();
-            firstVisibleSection.updateBounds(0 /* minTopPosition*/, 0 /* minBottomPosition */,
-                    false /* shiftPulsingWithFirst */);
-            minTopPosition = firstVisibleSection.getBounds().top;
-        }
-        boolean shiftPulsingWithFirst = mNumHeadsUp <= 1
-                && (mAmbientState.isDozing() || (mKeyguardBypassEnabled && onKeyguard));
-        for (NotificationSection section : mSections) {
-            int minBottomPosition = minTopPosition;
-            if (section == lastSection) {
-                // We need to make sure the section goes all the way to the shelf
-                minBottomPosition = (int) (ViewState.getFinalTranslationY(mShelf)
-                        + mShelf.getIntrinsicHeight());
-            }
-            minTopPosition = section.updateBounds(minTopPosition, minBottomPosition,
-                    shiftPulsingWithFirst);
-            shiftPulsingWithFirst = false;
-        }
-    }
-
     private NotificationSection getFirstVisibleSection() {
         for (NotificationSection section : mSections) {
             if (section.getFirstVisibleChild() != null) {
@@ -3184,13 +2863,7 @@
                 mSections, getChildrenWithBackground());
 
         if (mAnimationsEnabled && mIsExpanded) {
-            mAnimateNextBackgroundTop = firstChild != previousFirstChild;
-            mAnimateNextBackgroundBottom = lastChild != previousLastChild || mAnimateBottomOnLayout;
-            mAnimateNextSectionBoundsChange = sectionViewsChanged;
         } else {
-            mAnimateNextBackgroundTop = false;
-            mAnimateNextBackgroundBottom = false;
-            mAnimateNextSectionBoundsChange = false;
         }
         mAmbientState.setLastVisibleBackgroundChild(lastChild);
         mAnimateBottomOnLayout = false;
@@ -3344,7 +3017,6 @@
             setAnimationRunning(true);
             mStateAnimator.startAnimationForEvents(mAnimationEvents, mGoToFullShadeDelay);
             mAnimationEvents.clear();
-            updateBackground();
             updateViewShadows();
         } else {
             applyCurrentState();
@@ -3359,7 +3031,6 @@
         generatePositionChangeEvents();
         generateTopPaddingEvent();
         generateActivateEvent();
-        generateDimmedEvent();
         generateHideSensitiveEvent();
         generateGoToFullShadeEvent();
         generateViewResizeEvent();
@@ -3577,14 +3248,6 @@
         mEverythingNeedsAnimation = false;
     }
 
-    private void generateDimmedEvent() {
-        if (mDimmedNeedsAnimation) {
-            mAnimationEvents.add(
-                    new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DIMMED));
-        }
-        mDimmedNeedsAnimation = false;
-    }
-
     private void generateHideSensitiveEvent() {
         if (mHideSensitiveNeedsAnimation) {
             mAnimationEvents.add(
@@ -3645,7 +3308,11 @@
     @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
         if (SceneContainerFlag.isEnabled() && mIsBeingDragged) {
-            if (!mSendingTouchesToSceneFramework) {
+            int action = ev.getActionMasked();
+            boolean isUpOrCancel = action == ACTION_UP || action == ACTION_CANCEL;
+            if (mSendingTouchesToSceneFramework) {
+                mController.sendTouchToSceneFramework(ev);
+            } else if (!isUpOrCancel) {
                 // if this is the first touch being sent to the scene framework,
                 // convert it into a synthetic DOWN event.
                 mSendingTouchesToSceneFramework = true;
@@ -3653,14 +3320,9 @@
                 downEvent.setAction(MotionEvent.ACTION_DOWN);
                 mController.sendTouchToSceneFramework(downEvent);
                 downEvent.recycle();
-            } else {
-                mController.sendTouchToSceneFramework(ev);
             }
 
-            if (
-                    ev.getActionMasked() == MotionEvent.ACTION_UP
-                    || ev.getActionMasked() == MotionEvent.ACTION_CANCEL
-            ) {
+            if (isUpOrCancel) {
                 setIsBeingDragged(false);
             }
             return false;
@@ -3817,7 +3479,7 @@
                     }
                 }
                 break;
-            case MotionEvent.ACTION_UP:
+            case ACTION_UP:
                 if (mIsBeingDragged) {
                     final VelocityTracker velocityTracker = mVelocityTracker;
                     velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
@@ -3854,7 +3516,7 @@
                 }
 
                 break;
-            case MotionEvent.ACTION_CANCEL:
+            case ACTION_CANCEL:
                 if (mIsBeingDragged && getChildCount() > 0) {
                     if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
                             getScrollRange())) {
@@ -3963,7 +3625,7 @@
                     mTouchIsClick = false;
                 }
                 break;
-            case MotionEvent.ACTION_UP:
+            case ACTION_UP:
                 if (mStatusBarState != StatusBarState.KEYGUARD && mTouchIsClick &&
                         isBelowLastNotification(mInitialTouchX, mInitialTouchY)) {
                     debugShadeLog("handleEmptySpaceClick: touch event propagated further");
@@ -4104,8 +3766,8 @@
                 break;
             }
 
-            case MotionEvent.ACTION_CANCEL:
-            case MotionEvent.ACTION_UP:
+            case ACTION_CANCEL:
+            case ACTION_UP:
                 /* Release the drag */
                 setIsBeingDragged(false);
                 mActivePointerId = INVALID_POINTER;
@@ -4486,48 +4148,6 @@
         mAnimationFinishedRunnables.clear();
     }
 
-    /**
-     * See {@link AmbientState#setDimmed}.
-     */
-    void setDimmed(boolean dimmed, boolean animate) {
-        dimmed &= onKeyguard();
-        mAmbientState.setDimmed(dimmed);
-        if (animate && mAnimationsEnabled) {
-            mDimmedNeedsAnimation = true;
-            mNeedsAnimation = true;
-            animateDimmed(dimmed);
-        } else {
-            setDimAmount(dimmed ? 1.0f : 0.0f);
-        }
-        requestChildrenUpdate();
-    }
-
-    @VisibleForTesting
-    boolean isDimmed() {
-        return mAmbientState.isDimmed();
-    }
-
-    private void setDimAmount(float dimAmount) {
-        mDimAmount = dimAmount;
-        updateBackgroundDimming();
-    }
-
-    private void animateDimmed(boolean dimmed) {
-        if (mDimAnimator != null) {
-            mDimAnimator.cancel();
-        }
-        float target = dimmed ? 1.0f : 0.0f;
-        if (target == mDimAmount) {
-            return;
-        }
-        mDimAnimator = TimeAnimator.ofFloat(mDimAmount, target);
-        mDimAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED);
-        mDimAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
-        mDimAnimator.addListener(mDimEndListener);
-        mDimAnimator.addUpdateListener(mDimUpdateListener);
-        mDimAnimator.start();
-    }
-
     void updateSensitiveness(boolean animate, boolean hideSensitive) {
         if (hideSensitive != mAmbientState.isHideSensitive()) {
             int childCount = getChildCount();
@@ -4564,7 +4184,6 @@
 
         runAnimationFinishedRunnables();
         setAnimationRunning(false);
-        updateBackground();
         updateViewShadows();
     }
 
@@ -4714,7 +4333,6 @@
             invalidateOutline();
         }
         updateAlgorithmHeightAndPadding();
-        updateBackgroundDimming();
         requestChildrenUpdate();
         updateOwnTranslationZ();
     }
@@ -4747,21 +4365,6 @@
         }
     }
 
-    private int getNotGoneIndex(View child) {
-        int count = getChildCount();
-        int notGoneIndex = 0;
-        for (int i = 0; i < count; i++) {
-            View v = getChildAt(i);
-            if (child == v) {
-                return notGoneIndex;
-            }
-            if (v.getVisibility() != View.GONE) {
-                notGoneIndex++;
-            }
-        }
-        return -1;
-    }
-
     /**
      * Returns whether or not a History button is shown in the footer. If there is no footer, then
      * this will return false.
@@ -5266,13 +4869,10 @@
     void onStatePostChange(boolean fromShadeLocked) {
         boolean onKeyguard = onKeyguard();
 
-        mAmbientState.setDimmed(onKeyguard);
-
         if (mHeadsUpAppearanceController != null) {
             mHeadsUpAppearanceController.onStateChanged();
         }
 
-        setDimmed(onKeyguard, fromShadeLocked);
         setExpandingEnabled(!onKeyguard);
         if (!FooterViewRefactor.isEnabled()) {
             updateFooter();
@@ -5676,7 +5276,6 @@
      */
     public void setDozeAmount(float dozeAmount) {
         mAmbientState.setDozeAmount(dozeAmount);
-        updateContinuousBackgroundDrawing();
         updateStackPosition();
         requestChildrenUpdate();
     }
@@ -5711,7 +5310,6 @@
                 view.setTranslationY(wakeUplocation);
             }
         }
-        mDimmedNeedsAnimation = true;
     }
 
     void setAnimateBottomOnLayout(boolean animateBottomOnLayout) {
@@ -5763,7 +5361,6 @@
         updateFirstAndLastBackgroundViews();
         requestDisallowInterceptTouchEvent(true);
         updateContinuousShadowDrawing();
-        updateContinuousBackgroundDrawing();
         requestChildrenUpdate();
     }
 
@@ -5786,7 +5383,6 @@
      * @param numHeadsUp the number of active alerting notifications.
      */
     public void setNumHeadsUp(long numHeadsUp) {
-        mNumHeadsUp = numHeadsUp;
         mAmbientState.setHasHeadsUpEntries(numHeadsUp > 0);
     }
 
@@ -6160,19 +5756,6 @@
         mSpeedBumpIndexDirty = true;
     }
 
-    void updateContinuousBackgroundDrawing() {
-        boolean continuousBackground = !mAmbientState.isFullyAwake()
-                && mSwipeHelper.isSwiping();
-        if (continuousBackground != mContinuousBackgroundUpdate) {
-            mContinuousBackgroundUpdate = continuousBackground;
-            if (continuousBackground) {
-                getViewTreeObserver().addOnPreDrawListener(mBackgroundUpdater);
-            } else {
-                getViewTreeObserver().removeOnPreDrawListener(mBackgroundUpdater);
-            }
-        }
-    }
-
     private void resetAllSwipeState() {
         Trace.beginSection("NSSL.resetAllSwipeState()");
         mSwipeHelper.resetTouchState();
@@ -6259,7 +5842,6 @@
                         .animateHeight()
                         .animateTopInset()
                         .animateY()
-                        .animateDimmed()
                         .animateZ(),
 
                 // ANIMATION_TYPE_ACTIVATED_CHILD
@@ -6267,8 +5849,7 @@
                         .animateZ(),
 
                 // ANIMATION_TYPE_DIMMED
-                new AnimationFilter()
-                        .animateDimmed(),
+                new AnimationFilter(),
 
                 // ANIMATION_TYPE_CHANGE_POSITION
                 new AnimationFilter()
@@ -6283,7 +5864,6 @@
                         .animateHeight()
                         .animateTopInset()
                         .animateY()
-                        .animateDimmed()
                         .animateZ()
                         .hasDelays(),
 
@@ -6339,7 +5919,6 @@
                 // ANIMATION_TYPE_EVERYTHING
                 new AnimationFilter()
                         .animateAlpha()
-                        .animateDimmed()
                         .animateHideSensitive()
                         .animateHeight()
                         .animateTopInset()
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 7c13877..3bdd0e9 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
@@ -875,8 +875,6 @@
         mHeadsUpManager.setAnimationStateHandler(mView::setHeadsUpGoingAwayAnimationsAllowed);
         mDynamicPrivacyController.addListener(mDynamicPrivacyControllerListener);
 
-        mScrimController.setScrimBehindChangeRunnable(mView::updateBackgroundDimming);
-
         mLockscreenShadeTransitionController.setStackScroller(this);
 
         mLockscreenUserManager.addUserChangedListener(mLockscreenUserChangeListener);
@@ -1743,13 +1741,6 @@
     }
 
     /**
-     * Set the dimmed state for all of the notification views.
-     */
-    public void setDimmed(boolean dimmed, boolean animate) {
-        mView.setDimmed(dimmed, animate);
-    }
-
-    /**
      * @return the inset during the full shade transition, that needs to be added to the position
      * of the quick settings edge. This is relevant for media, that is transitioning
      * from the keyguard host to the quick settings one.
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 1ef9a8f..9b1952b 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
@@ -369,13 +369,11 @@
     /** Updates the dimmed and hiding sensitive states of the children. */
     private void updateDimmedAndHideSensitive(AmbientState ambientState,
             StackScrollAlgorithmState algorithmState) {
-        boolean dimmed = ambientState.isDimmed();
         boolean hideSensitive = ambientState.isHideSensitive();
         int childCount = algorithmState.visibleChildren.size();
         for (int i = 0; i < childCount; i++) {
             ExpandableView child = algorithmState.visibleChildren.get(i);
             ExpandableViewState childViewState = child.getViewState();
-            childViewState.dimmed = dimmed;
             childViewState.hideSensitive = hideSensitive;
         }
     }
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 d2e36b8..088f894 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -207,8 +207,6 @@
     private ScrimView mNotificationsScrim;
     private ScrimView mScrimBehind;
 
-    private Runnable mScrimBehindChangeRunnable;
-
     private final KeyguardStateController mKeyguardStateController;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final DozeParameters mDozeParameters;
@@ -415,11 +413,6 @@
         behindScrim.enableBottomEdgeConcave(mClipsQsScrim);
         mNotificationsScrim.enableRoundedCorners(true);
 
-        if (mScrimBehindChangeRunnable != null) {
-            mScrimBehind.setChangeRunnable(mScrimBehindChangeRunnable, mMainExecutor);
-            mScrimBehindChangeRunnable = null;
-        }
-
         final ScrimState[] states = ScrimState.values();
         for (int i = 0; i < states.length; i++) {
             states[i].init(mScrimInFront, mScrimBehind, mDozeParameters, mDockManager);
@@ -1542,16 +1535,6 @@
         mScrimBehind.postOnAnimationDelayed(callback, 32 /* delayMillis */);
     }
 
-    public void setScrimBehindChangeRunnable(Runnable changeRunnable) {
-        // TODO: remove this. This is necessary because of an order-of-operations limitation.
-        // The fix is to move more of these class into @SysUISingleton.
-        if (mScrimBehind == null) {
-            mScrimBehindChangeRunnable = changeRunnable;
-        } else {
-            mScrimBehind.setChangeRunnable(changeRunnable, mMainExecutor);
-        }
-    }
-
     private void updateThemeColors() {
         if (mScrimBehind == null) return;
         int background = Utils.getColorAttr(mScrimBehind.getContext(),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt
index 71e25e9..541ac48 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt
@@ -23,6 +23,9 @@
 import android.os.Bundle
 import android.view.Gravity
 import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import android.view.WindowInsets
+import android.view.WindowInsets.Type.InsetsType
+import android.view.WindowInsetsAnimation
 import android.view.WindowManager.LayoutParams.MATCH_PARENT
 import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
 import android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS
@@ -70,16 +73,43 @@
     override fun onStart() {
         super.onStart()
         configurationController?.addCallback(onConfigChanged)
+        window?.decorView?.setWindowInsetsAnimationCallback(insetsAnimationCallback)
     }
 
     override fun onStop() {
         super.onStop()
         configurationController?.removeCallback(onConfigChanged)
+        window?.decorView?.setWindowInsetsAnimationCallback(null)
     }
 
+    /** Called after any insets change. */
+    open fun onInsetsChanged(@InsetsType changedTypes: Int, insets: WindowInsets) {}
+
     /** Can be overridden by subclasses to receive config changed events. */
     open fun onConfigurationChanged() {}
 
+    private val insetsAnimationCallback =
+        object : WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
+
+            private var lastInsets: WindowInsets? = null
+
+            override fun onEnd(animation: WindowInsetsAnimation) {
+                lastInsets?.let { onInsetsChanged(animation.typeMask, it) }
+            }
+
+            override fun onProgress(
+                insets: WindowInsets,
+                animations: MutableList<WindowInsetsAnimation>,
+            ): WindowInsets {
+                lastInsets = insets
+                onInsetsChanged(changedTypes = allAnimationMasks(animations), insets)
+                return insets
+            }
+
+            private fun allAnimationMasks(animations: List<WindowInsetsAnimation>): Int =
+                animations.fold(0) { acc: Int, it -> acc or it.typeMask }
+        }
+
     private val onConfigChanged =
         object : ConfigurationListener {
             override fun onConfigChanged(newConfig: Configuration?) {
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 44c684c..b5efc44 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -379,7 +379,9 @@
                             + " was received. Deferring... Managed profile? " + isManagedProfile);
                     return;
                 }
-                if (android.os.Flags.allowPrivateProfile() && isPrivateProfile(newUserHandle)) {
+                if (android.os.Flags.allowPrivateProfile()
+                        && android.multiuser.Flags.enablePrivateSpaceFeatures()
+                        && isPrivateProfile(newUserHandle)) {
                     mDeferredThemeEvaluation = true;
                     Log.i(TAG, "Deferring theme for private profile till user setup is complete");
                     return;
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/BatteryControllerExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/BatteryControllerExt.kt
index 0128eb7..80ccd64 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/BatteryControllerExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/BatteryControllerExt.kt
@@ -22,6 +22,24 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.onStart
 
+fun BatteryController.isDevicePluggedIn(): Flow<Boolean> {
+    return conflatedCallbackFlow {
+            val batteryCallback =
+                object : BatteryController.BatteryStateChangeCallback {
+                    override fun onBatteryLevelChanged(
+                        level: Int,
+                        pluggedIn: Boolean,
+                        charging: Boolean
+                    ) {
+                        trySend(pluggedIn)
+                    }
+                }
+            addCallback(batteryCallback)
+            awaitClose { removeCallback(batteryCallback) }
+        }
+        .onStart { emit(isPluggedIn) }
+}
+
 fun BatteryController.isBatteryPowerSaveEnabled(): Flow<Boolean> {
     return conflatedCallbackFlow {
             val batteryCallback =
@@ -35,3 +53,35 @@
         }
         .onStart { emit(isPowerSave) }
 }
+
+fun BatteryController.getBatteryLevel(): Flow<Int> {
+    return conflatedCallbackFlow {
+            val batteryCallback =
+                object : BatteryController.BatteryStateChangeCallback {
+                    override fun onBatteryLevelChanged(
+                        level: Int,
+                        pluggedIn: Boolean,
+                        charging: Boolean
+                    ) {
+                        trySend(level)
+                    }
+                }
+            addCallback(batteryCallback)
+            awaitClose { removeCallback(batteryCallback) }
+        }
+        .onStart { emit(0) }
+}
+
+fun BatteryController.isExtremePowerSaverEnabled(): Flow<Boolean> {
+    return conflatedCallbackFlow {
+            val batteryCallback =
+                object : BatteryController.BatteryStateChangeCallback {
+                    override fun onExtremeBatterySaverChanged(isExtreme: Boolean) {
+                        trySend(isExtreme)
+                    }
+                }
+            addCallback(batteryCallback)
+            awaitClose { removeCallback(batteryCallback) }
+        }
+        .onStart { emit(isExtremeSaverOn) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 4045630..deec215 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -120,7 +120,7 @@
 import com.android.systemui.haptics.slider.HapticSliderViewBinder;
 import com.android.systemui.haptics.slider.SeekableSliderHapticPlugin;
 import com.android.systemui.haptics.slider.SliderHapticFeedbackConfig;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
+import com.android.systemui.media.dialog.MediaOutputDialogManager;
 import com.android.systemui.plugins.VolumeDialog;
 import com.android.systemui.plugins.VolumeDialogController;
 import com.android.systemui.plugins.VolumeDialogController.State;
@@ -265,7 +265,7 @@
     private final Object mSafetyWarningLock = new Object();
     private final Accessibility mAccessibility = new Accessibility();
     private final ConfigurationController mConfigurationController;
-    private final MediaOutputDialogFactory mMediaOutputDialogFactory;
+    private final MediaOutputDialogManager mMediaOutputDialogManager;
     private final CsdWarningDialog.Factory mCsdWarningDialogFactory;
     private final VolumePanelNavigationInteractor mVolumePanelNavigationInteractor;
     private final VolumeNavigator mVolumeNavigator;
@@ -316,7 +316,7 @@
             AccessibilityManagerWrapper accessibilityManagerWrapper,
             DeviceProvisionedController deviceProvisionedController,
             ConfigurationController configurationController,
-            MediaOutputDialogFactory mediaOutputDialogFactory,
+            MediaOutputDialogManager mediaOutputDialogManager,
             InteractionJankMonitor interactionJankMonitor,
             VolumePanelNavigationInteractor volumePanelNavigationInteractor,
             VolumeNavigator volumeNavigator,
@@ -340,7 +340,7 @@
         mAccessibilityMgr = accessibilityManagerWrapper;
         mDeviceProvisionedController = deviceProvisionedController;
         mConfigurationController = configurationController;
-        mMediaOutputDialogFactory = mediaOutputDialogFactory;
+        mMediaOutputDialogManager = mediaOutputDialogManager;
         mCsdWarningDialogFactory = csdWarningDialogFactory;
         mShowActiveStreamOnly = showActiveStreamOnly();
         mHasSeenODICaptionsTooltip =
@@ -1199,7 +1199,7 @@
             mSettingsIcon.setOnClickListener(v -> {
                 Events.writeEvent(Events.EVENT_SETTINGS_CLICK);
                 dismissH(DISMISS_REASON_SETTINGS_CLICKED);
-                mMediaOutputDialogFactory.dismiss();
+                mMediaOutputDialogManager.dismiss();
                 mVolumeNavigator.openVolumePanel(
                         mVolumePanelNavigationInteractor.getVolumePanelRoute());
             });
@@ -2082,6 +2082,9 @@
                 } else {
                     row.anim.cancel();
                     row.anim.setIntValues(progress, newProgress);
+                    // The animator can't keep up with the volume changes so haptics need to be
+                    // triggered here. This happens when the volume keys are continuously pressed.
+                    row.deliverOnProgressChangedHaptics(false, newProgress);
                 }
                 row.animTargetProgress = newProgress;
                 row.anim.setDuration(UPDATE_ANIMATION_DURATION);
@@ -2486,10 +2489,10 @@
             if (getActiveRow().equals(mRow)
                     && mRow.slider.getVisibility() == VISIBLE
                     && mRow.mHapticPlugin != null) {
-                mRow.mHapticPlugin.onProgressChanged(seekBar, progress, fromUser);
-                if (!fromUser) {
-                    // Consider a change from program as the volume key being continuously pressed
-                    mRow.mHapticPlugin.onKeyDown();
+                if (fromUser || mRow.animTargetProgress == progress) {
+                    // Deliver user-generated slider changes immediately, or when the animation
+                    // completes
+                    mRow.deliverOnProgressChangedHaptics(fromUser, progress);
                 }
             }
             if (D.BUG) Log.d(TAG, AudioSystem.streamToString(mRow.stream)
@@ -2571,11 +2574,11 @@
                 /* progressInterpolatorFactor= */ 1f,
                 /* progressBasedDragMinScale= */ 0f,
                 /* progressBasedDragMaxScale= */ 0.2f,
-                /* additionalVelocityMaxBump= */ 0.15f,
+                /* additionalVelocityMaxBump= */ 0.25f,
                 /* deltaMillisForDragInterval= */ 0f,
-                /* deltaProgressForDragThreshold= */ 0.015f,
-                /* numberOfLowTicks= */ 5,
-                /* maxVelocityToScale= */ 300f,
+                /* deltaProgressForDragThreshold= */ 0.05f,
+                /* numberOfLowTicks= */ 4,
+                /* maxVelocityToScale= */ 200,
                 /* velocityAxis= */ MotionEvent.AXIS_Y,
                 /* upperBookendScale= */ 1f,
                 /* lowerBookendScale= */ 0.05f,
@@ -2642,6 +2645,14 @@
         void removeHaptics() {
             slider.setOnTouchListener(null);
         }
+
+        void deliverOnProgressChangedHaptics(boolean fromUser, int progress) {
+            mHapticPlugin.onProgressChanged(slider, progress, fromUser);
+            if (!fromUser) {
+                // Consider a change from program as the volume key being continuously pressed
+                mHapticPlugin.onKeyDown();
+            }
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index 64a5644..1f87ec8 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -24,7 +24,7 @@
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.systemui.CoreStartable;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
+import com.android.systemui.media.dialog.MediaOutputDialogManager;
 import com.android.systemui.plugins.VolumeDialog;
 import com.android.systemui.plugins.VolumeDialogController;
 import com.android.systemui.statusbar.VibratorHelper;
@@ -100,7 +100,7 @@
             AccessibilityManagerWrapper accessibilityManagerWrapper,
             DeviceProvisionedController deviceProvisionedController,
             ConfigurationController configurationController,
-            MediaOutputDialogFactory mediaOutputDialogFactory,
+            MediaOutputDialogManager mediaOutputDialogManager,
             InteractionJankMonitor interactionJankMonitor,
             VolumePanelNavigationInteractor volumePanelNavigationInteractor,
             VolumeNavigator volumeNavigator,
@@ -116,7 +116,7 @@
                 accessibilityManagerWrapper,
                 deviceProvisionedController,
                 configurationController,
-                mediaOutputDialogFactory,
+                mediaOutputDialogManager,
                 interactionJankMonitor,
                 volumePanelNavigationInteractor,
                 volumeNavigator,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt
index 2ab5998..cb16abe 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt
@@ -20,7 +20,7 @@
 import com.android.systemui.animation.DialogCuj
 import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.animation.Expandable
-import com.android.systemui.media.dialog.MediaOutputDialogFactory
+import com.android.systemui.media.dialog.MediaOutputDialogManager
 import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSession
 import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
 import javax.inject.Inject
@@ -30,20 +30,22 @@
 class MediaOutputActionsInteractor
 @Inject
 constructor(
-    private val mediaOutputDialogFactory: MediaOutputDialogFactory,
+    private val mediaOutputDialogManager: MediaOutputDialogManager,
 ) {
 
     fun onBarClick(session: MediaDeviceSession, expandable: Expandable) {
         when (session) {
             is MediaDeviceSession.Active -> {
-                mediaOutputDialogFactory.createWithController(
+                mediaOutputDialogManager.createAndShowWithController(
                     session.packageName,
                     false,
                     expandable.dialogController()
                 )
             }
             is MediaDeviceSession.Inactive -> {
-                mediaOutputDialogFactory.createDialogForSystemRouting(expandable.dialogController())
+                mediaOutputDialogManager.createAndShowForSystemRouting(
+                    expandable.dialogController()
+                )
             }
             else -> {
                 /* do nothing */
@@ -56,7 +58,7 @@
             cuj =
                 DialogCuj(
                     InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
-                    MediaOutputDialogFactory.INTERACTION_JANK_TAG
+                    MediaOutputDialogManager.INTERACTION_JANK_TAG
                 )
         )
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt
index f07932c..e3be3822 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt
@@ -19,6 +19,7 @@
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import androidx.core.animation.doOnEnd
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.util.doOnEnd
@@ -30,6 +31,7 @@
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
 @RunWithLooper
+@FlakyTest(bugId = 302149604)
 class AnimatorTestRuleOrderTest : SysuiTestCase() {
 
     @get:Rule val animatorTestRule = AnimatorTestRule(this)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
index 043dcaa..3c073d5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
@@ -193,6 +193,34 @@
     }
 
     @Test
+    @EnableFlags(FLAG_NEW_STATUS_BAR_ICONS)
+    fun modeEstimate_batteryPercentView_isNotNull_flagOn() {
+        mBatteryMeterView.onBatteryLevelChanged(15, false)
+        mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
+        mBatteryMeterView.setBatteryEstimateFetcher(Fetcher())
+
+        mBatteryMeterView.updatePercentText()
+
+        // New battery icon only uses the percent view for the estimate text
+        assertThat(mBatteryMeterView.batteryPercentView).isNotNull()
+        // Make sure that it was added to the view hierarchy
+        assertThat(mBatteryMeterView.batteryPercentView.parent).isNotNull()
+    }
+
+    @Test
+    @EnableFlags(FLAG_NEW_STATUS_BAR_ICONS)
+    fun modePercent_batteryPercentView_isNull_flagOn() {
+        mBatteryMeterView.onBatteryLevelChanged(15, false)
+        mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ON)
+        mBatteryMeterView.setBatteryEstimateFetcher(Fetcher())
+
+        mBatteryMeterView.updatePercentText()
+
+        // New battery icon only uses the percent view for the estimate text
+        assertThat(mBatteryMeterView.batteryPercentView).isNull()
+    }
+
+    @Test
     fun contentDescription_manyUpdates_alwaysUpdated() {
         // BatteryDefender
         mBatteryMeterView.onBatteryLevelChanged(90, false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java
index 7a18477..a569cee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java
@@ -42,7 +42,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.DialogTransitionAnimator;
 import com.android.systemui.broadcast.BroadcastSender;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
+import com.android.systemui.media.dialog.MediaOutputDialogManager;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -77,7 +77,8 @@
     @Mock SysUiState mSysUiState;
     @Mock
     DialogTransitionAnimator mDialogTransitionAnimator;
-    @Mock MediaOutputDialogFactory mMediaOutputDialogFactory;
+    @Mock
+    MediaOutputDialogManager mMediaOutputDialogManager;
     private SystemUIDialog mDialog;
     private TextView mTitle;
     private TextView mSubTitle;
@@ -96,7 +97,7 @@
 
         mBroadcastDialogDelegate = new BroadcastDialogDelegate(
                 mContext,
-                mMediaOutputDialogFactory,
+                mMediaOutputDialogManager,
                 mLocalBluetoothManager,
                 new UiEventLoggerFake(),
                 mFakeExecutor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt
index b25fb6e..30519b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt
@@ -16,14 +16,17 @@
 
 package com.android.systemui.display.ui.view
 
+import android.graphics.Insets
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.View
+import android.view.WindowInsets
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.res.R
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
@@ -41,6 +44,7 @@
 
     private val onStartMirroringCallback = mock<View.OnClickListener>()
     private val onCancelCallback = mock<View.OnClickListener>()
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
@@ -96,10 +100,40 @@
         verify(onStartMirroringCallback).onClick(any())
     }
 
+    @Test
+    fun onInsetsChanged_navBarInsets_updatesBottomPadding() {
+        dialog.show()
+
+        val insets = buildInsets(WindowInsets.Type.navigationBars(), TEST_BOTTOM_INSETS)
+        dialog.onInsetsChanged(WindowInsets.Type.navigationBars(), insets)
+
+        assertThat(dialog.requireViewById<View>(R.id.cd_bottom_sheet).paddingBottom)
+            .isEqualTo(TEST_BOTTOM_INSETS)
+    }
+
+    @Test
+    fun onInsetsChanged_otherType_doesNotUpdateBottomPadding() {
+        dialog.show()
+
+        val insets = buildInsets(WindowInsets.Type.ime(), TEST_BOTTOM_INSETS)
+        dialog.onInsetsChanged(WindowInsets.Type.ime(), insets)
+
+        assertThat(dialog.requireViewById<View>(R.id.cd_bottom_sheet).paddingBottom)
+            .isNotEqualTo(TEST_BOTTOM_INSETS)
+    }
+
+    private fun buildInsets(@WindowInsets.Type.InsetsType type: Int, bottom: Int): WindowInsets {
+        return WindowInsets.Builder().setInsets(type, Insets.of(0, 0, 0, bottom)).build()
+    }
+
     @After
     fun teardown() {
         if (::dialog.isInitialized) {
             dialog.dismiss()
         }
     }
+
+    private companion object {
+        const val TEST_BOTTOM_INSETS = 1000 // arbitrarily high number
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
index 2f92afa..83e4d31 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
@@ -84,7 +84,7 @@
 import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel
 import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.media.controls.util.MediaUiEventLogger
-import com.android.systemui.media.dialog.MediaOutputDialogFactory
+import com.android.systemui.media.dialog.MediaOutputDialogManager
 import com.android.systemui.monet.ColorScheme
 import com.android.systemui.monet.Style
 import com.android.systemui.plugins.ActivityStarter
@@ -160,7 +160,7 @@
     @Mock private lateinit var mediaDataManager: MediaDataManager
     @Mock private lateinit var expandedSet: ConstraintSet
     @Mock private lateinit var collapsedSet: ConstraintSet
-    @Mock private lateinit var mediaOutputDialogFactory: MediaOutputDialogFactory
+    @Mock private lateinit var mediaOutputDialogManager: MediaOutputDialogManager
     @Mock private lateinit var mediaCarouselController: MediaCarouselController
     @Mock private lateinit var falsingManager: FalsingManager
     @Mock private lateinit var transitionParent: ViewGroup
@@ -266,7 +266,7 @@
                     mediaViewController,
                     seekBarViewModel,
                     Lazy { mediaDataManager },
-                    mediaOutputDialogFactory,
+                    mediaOutputDialogManager,
                     mediaCarouselController,
                     falsingManager,
                     clock,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
index 0879884..83def8e4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
@@ -42,16 +42,16 @@
 
     private MediaOutputDialogReceiver mMediaOutputDialogReceiver;
 
-    private final MediaOutputDialogFactory mMockMediaOutputDialogFactory =
-            mock(MediaOutputDialogFactory.class);
+    private final MediaOutputDialogManager mMockMediaOutputDialogManager =
+            mock(MediaOutputDialogManager.class);
 
-    private final MediaOutputBroadcastDialogFactory mMockMediaOutputBroadcastDialogFactory =
-            mock(MediaOutputBroadcastDialogFactory.class);
+    private final MediaOutputBroadcastDialogManager mMockMediaOutputBroadcastDialogManager =
+            mock(MediaOutputBroadcastDialogManager.class);
 
     @Before
     public void setup() {
-        mMediaOutputDialogReceiver = new MediaOutputDialogReceiver(mMockMediaOutputDialogFactory,
-                mMockMediaOutputBroadcastDialogFactory);
+        mMediaOutputDialogReceiver = new MediaOutputDialogReceiver(mMockMediaOutputDialogManager,
+                mMockMediaOutputBroadcastDialogManager);
     }
 
     @Test
@@ -60,9 +60,10 @@
         intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName());
         mMediaOutputDialogReceiver.onReceive(getContext(), intent);
 
-        verify(mMockMediaOutputDialogFactory, times(1))
-                .create(getContext().getPackageName(), false, null);
-        verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+        verify(mMockMediaOutputDialogManager, times(1))
+                .createAndShow(getContext().getPackageName(), false, null);
+        verify(mMockMediaOutputBroadcastDialogManager, never())
+                .createAndShow(any(), anyBoolean(), any());
     }
 
     @Test
@@ -71,8 +72,9 @@
         intent.putExtra("Wrong Package Name Key", getContext().getPackageName());
         mMediaOutputDialogReceiver.onReceive(getContext(), intent);
 
-        verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
-        verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+        verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+        verify(mMockMediaOutputBroadcastDialogManager, never())
+                .createAndShow(any(), anyBoolean(), any());
     }
 
     @Test
@@ -80,8 +82,9 @@
         Intent intent = new Intent(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG);
         mMediaOutputDialogReceiver.onReceive(getContext(), intent);
 
-        verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
-        verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+        verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+        verify(mMockMediaOutputBroadcastDialogManager, never())
+                .createAndShow(any(), anyBoolean(), any());
     }
 
     @Test
@@ -92,8 +95,9 @@
         intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName());
         mMediaOutputDialogReceiver.onReceive(getContext(), intent);
 
-        verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
-        verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+        verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+        verify(mMockMediaOutputBroadcastDialogManager, never())
+                .createAndShow(any(), anyBoolean(), any());
     }
 
     @Test
@@ -104,9 +108,9 @@
         intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName());
         mMediaOutputDialogReceiver.onReceive(getContext(), intent);
 
-        verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
-        verify(mMockMediaOutputBroadcastDialogFactory, times(1))
-                .create(getContext().getPackageName(), true, null);
+        verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+        verify(mMockMediaOutputBroadcastDialogManager, times(1))
+                .createAndShow(getContext().getPackageName(), true, null);
     }
 
     @Test
@@ -117,8 +121,9 @@
         intent.putExtra("Wrong Package Name Key", getContext().getPackageName());
         mMediaOutputDialogReceiver.onReceive(getContext(), intent);
 
-        verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
-        verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+        verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+        verify(mMockMediaOutputBroadcastDialogManager, never())
+                .createAndShow(any(), anyBoolean(), any());
     }
 
     @Test
@@ -128,8 +133,9 @@
                 MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG);
         mMediaOutputDialogReceiver.onReceive(getContext(), intent);
 
-        verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
-        verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+        verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+        verify(mMockMediaOutputBroadcastDialogManager, never())
+                .createAndShow(any(), anyBoolean(), any());
     }
 
     @Test
@@ -139,8 +145,9 @@
         intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName());
         mMediaOutputDialogReceiver.onReceive(getContext(), intent);
 
-        verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
-        verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+        verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+        verify(mMockMediaOutputBroadcastDialogManager, never())
+                .createAndShow(any(), anyBoolean(), any());
     }
 
     @Test
@@ -148,7 +155,8 @@
         Intent intent = new Intent("UnKnown Action");
         mMediaOutputDialogReceiver.onReceive(getContext(), intent);
 
-        verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
-        verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+        verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+        verify(mMockMediaOutputBroadcastDialogManager, never())
+                .createAndShow(any(), anyBoolean(), any());
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt
index dbfab64..bda0e1e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt
@@ -21,32 +21,25 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createToken
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createToken
+import com.android.systemui.mediaprojection.taskswitcher.activityTaskManagerTasksRepository
+import com.android.systemui.mediaprojection.taskswitcher.fakeActivityTaskManager
+import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherKosmos
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
 class ActivityTaskManagerTasksRepositoryTest : SysuiTestCase() {
 
-    private val fakeActivityTaskManager = FakeActivityTaskManager()
-
-    private val dispatcher = UnconfinedTestDispatcher()
-    private val testScope = TestScope(dispatcher)
-
-    private val repo =
-        ActivityTaskManagerTasksRepository(
-            activityTaskManager = fakeActivityTaskManager.activityTaskManager,
-            applicationScope = testScope.backgroundScope,
-            backgroundDispatcher = dispatcher
-        )
+    private val kosmos = taskSwitcherKosmos()
+    private val fakeActivityTaskManager = kosmos.fakeActivityTaskManager
+    private val testScope = kosmos.testScope
+    private val repo = kosmos.activityTaskManagerTasksRepository
 
     @Test
     fun launchRecentTask_taskIsMovedToForeground() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt
index fdd434a..6043ede 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt
@@ -17,50 +17,35 @@
 package com.android.systemui.mediaprojection.taskswitcher.data.repository
 
 import android.os.Binder
-import android.os.Handler
 import android.testing.AndroidTestingRunner
 import android.view.ContentRecordingSession
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createToken
 import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createToken
+import com.android.systemui.mediaprojection.taskswitcher.fakeActivityTaskManager
+import com.android.systemui.mediaprojection.taskswitcher.fakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.mediaProjectionManagerRepository
+import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherKosmos
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
 class MediaProjectionManagerRepositoryTest : SysuiTestCase() {
 
-    private val dispatcher = UnconfinedTestDispatcher()
-    private val testScope = TestScope(dispatcher)
+    private val kosmos = taskSwitcherKosmos()
+    private val testScope = kosmos.testScope
 
-    private val fakeMediaProjectionManager = FakeMediaProjectionManager()
-    private val fakeActivityTaskManager = FakeActivityTaskManager()
+    private val fakeMediaProjectionManager = kosmos.fakeMediaProjectionManager
+    private val fakeActivityTaskManager = kosmos.fakeActivityTaskManager
 
-    private val tasksRepo =
-        ActivityTaskManagerTasksRepository(
-            activityTaskManager = fakeActivityTaskManager.activityTaskManager,
-            applicationScope = testScope.backgroundScope,
-            backgroundDispatcher = dispatcher
-        )
-
-    private val repo =
-        MediaProjectionManagerRepository(
-            mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
-            handler = Handler.getMain(),
-            applicationScope = testScope.backgroundScope,
-            tasksRepository = tasksRepo,
-            backgroundDispatcher = dispatcher,
-            mediaProjectionServiceHelper = fakeMediaProjectionManager.helper
-        )
+    private val repo = kosmos.mediaProjectionManagerRepository
 
     @Test
     fun switchProjectedTask_stateIsUpdatedWithNewTask() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt
index dfb688b..33e65f26 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt
@@ -17,55 +17,33 @@
 package com.android.systemui.mediaprojection.taskswitcher.domain.interactor
 
 import android.content.Intent
-import android.os.Handler
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager.Companion.createSingleTaskSession
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.FakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.FakeMediaProjectionManager.Companion.createSingleTaskSession
 import com.android.systemui.mediaprojection.taskswitcher.domain.model.TaskSwitchState
+import com.android.systemui.mediaprojection.taskswitcher.fakeActivityTaskManager
+import com.android.systemui.mediaprojection.taskswitcher.fakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherInteractor
+import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherKosmos
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
 class TaskSwitchInteractorTest : SysuiTestCase() {
 
-    private val dispatcher = UnconfinedTestDispatcher()
-    private val testScope = TestScope(dispatcher)
-
-    private val fakeActivityTaskManager = FakeActivityTaskManager()
-    private val fakeMediaProjectionManager = FakeMediaProjectionManager()
-
-    private val tasksRepo =
-        ActivityTaskManagerTasksRepository(
-            activityTaskManager = fakeActivityTaskManager.activityTaskManager,
-            applicationScope = testScope.backgroundScope,
-            backgroundDispatcher = dispatcher
-        )
-
-    private val mediaRepo =
-        MediaProjectionManagerRepository(
-            mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
-            handler = Handler.getMain(),
-            applicationScope = testScope.backgroundScope,
-            tasksRepository = tasksRepo,
-            backgroundDispatcher = dispatcher,
-            mediaProjectionServiceHelper = fakeMediaProjectionManager.helper,
-        )
-
-    private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo)
+    private val kosmos = taskSwitcherKosmos()
+    private val testScope = kosmos.testScope
+    private val fakeActivityTaskManager = kosmos.fakeActivityTaskManager
+    private val fakeMediaProjectionManager = kosmos.fakeMediaProjectionManager
+    private val interactor = kosmos.taskSwitcherInteractor
 
     @Test
     fun taskSwitchChanges_notProjecting_foregroundTaskChange_emitsNotProjectingTask() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt
index c4e9393..9382c58 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt
@@ -18,26 +18,22 @@
 
 import android.app.Notification
 import android.app.NotificationManager
-import android.os.Handler
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository
-import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor
-import com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel.TaskSwitcherNotificationViewModel
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.FakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.fakeActivityTaskManager
+import com.android.systemui.mediaprojection.taskswitcher.fakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherKosmos
+import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherViewModel
 import com.android.systemui.res.R
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import junit.framework.Assert.assertEquals
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
@@ -46,39 +42,16 @@
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
 class TaskSwitcherNotificationCoordinatorTest : SysuiTestCase() {
 
     private val notificationManager = mock<NotificationManager>()
-
-    private val dispatcher = UnconfinedTestDispatcher()
-    private val testScope = TestScope(dispatcher)
-
-    private val fakeActivityTaskManager = FakeActivityTaskManager()
-    private val fakeMediaProjectionManager = FakeMediaProjectionManager()
-
-    private val tasksRepo =
-        ActivityTaskManagerTasksRepository(
-            activityTaskManager = fakeActivityTaskManager.activityTaskManager,
-            applicationScope = testScope.backgroundScope,
-            backgroundDispatcher = dispatcher
-        )
-
-    private val mediaRepo =
-        MediaProjectionManagerRepository(
-            mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
-            handler = Handler.getMain(),
-            applicationScope = testScope.backgroundScope,
-            tasksRepository = tasksRepo,
-            backgroundDispatcher = dispatcher,
-            mediaProjectionServiceHelper = fakeMediaProjectionManager.helper,
-        )
-
-    private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo)
-    private val viewModel =
-        TaskSwitcherNotificationViewModel(interactor, backgroundDispatcher = dispatcher)
+    private val kosmos = taskSwitcherKosmos()
+    private val testScope = kosmos.testScope
+    private val fakeActivityTaskManager = kosmos.fakeActivityTaskManager
+    private val fakeMediaProjectionManager = kosmos.fakeMediaProjectionManager
+    private val viewModel = kosmos.taskSwitcherViewModel
 
     private lateinit var coordinator: TaskSwitcherNotificationCoordinator
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
index 687f970..a468953 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
@@ -17,64 +17,35 @@
 package com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel
 
 import android.content.Intent
-import android.os.Handler
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager.Companion.createDisplaySession
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager.Companion.createSingleTaskSession
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository
-import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.FakeMediaProjectionManager.Companion.createDisplaySession
+import com.android.systemui.mediaprojection.taskswitcher.FakeMediaProjectionManager.Companion.createSingleTaskSession
+import com.android.systemui.mediaprojection.taskswitcher.fakeActivityTaskManager
+import com.android.systemui.mediaprojection.taskswitcher.fakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherKosmos
+import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherViewModel
 import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState
 import com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel.TaskSwitcherNotificationViewModel.Companion.NOTIFICATION_MAX_SHOW_DURATION
 import com.google.common.truth.Truth.assertThat
 import kotlin.time.Duration.Companion.milliseconds
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestCoroutineScheduler
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
 class TaskSwitcherNotificationViewModelTest : SysuiTestCase() {
 
-    private val scheduler = TestCoroutineScheduler()
-    private val dispatcher = UnconfinedTestDispatcher(scheduler)
-    private val testScope = TestScope(dispatcher)
-
-    private val fakeActivityTaskManager = FakeActivityTaskManager()
-    private val fakeMediaProjectionManager = FakeMediaProjectionManager()
-
-    private val tasksRepo =
-        ActivityTaskManagerTasksRepository(
-            activityTaskManager = fakeActivityTaskManager.activityTaskManager,
-            applicationScope = testScope.backgroundScope,
-            backgroundDispatcher = dispatcher
-        )
-
-    private val mediaRepo =
-        MediaProjectionManagerRepository(
-            mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
-            handler = Handler.getMain(),
-            applicationScope = testScope.backgroundScope,
-            tasksRepository = tasksRepo,
-            backgroundDispatcher = dispatcher,
-            mediaProjectionServiceHelper = fakeMediaProjectionManager.helper,
-        )
-
-    private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo)
-
-    private val viewModel =
-        TaskSwitcherNotificationViewModel(interactor, backgroundDispatcher = dispatcher)
+    private val kosmos = taskSwitcherKosmos()
+    private val testScope = kosmos.testScope
+    private val fakeActivityTaskManager = kosmos.fakeActivityTaskManager
+    private val fakeMediaProjectionManager = kosmos.fakeMediaProjectionManager
+    private val viewModel = kosmos.taskSwitcherViewModel
 
     @Test
     fun uiState_notProjecting_emitsNotShowing() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
index a59ba07..eb692eb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
@@ -32,9 +32,9 @@
 import android.view.LayoutInflater;
 import android.view.View;
 
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
+import com.android.systemui.media.dialog.MediaOutputDialogManager;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -51,6 +51,7 @@
 import java.util.List;
 import java.util.Map;
 
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
@@ -65,7 +66,7 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mDependency.injectMockDependency(MediaOutputDialogFactory.class);
+        mDependency.injectMockDependency(MediaOutputDialogManager.class);
         allowTestableLooperAsMainThread();
         when(mBindStage.getStageParams(any())).thenReturn(new RowContentBindParams());
         mDynamicChildBindController =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index fb49499f..718f998 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -58,7 +58,7 @@
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.media.controls.util.MediaFeatureFlag;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
+import com.android.systemui.media.dialog.MediaOutputDialogManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.NotificationMediaManager;
@@ -159,7 +159,7 @@
         dependency.injectTestDependency(FeatureFlags.class, mFeatureFlags);
         dependency.injectMockDependency(NotificationMediaManager.class);
         dependency.injectMockDependency(NotificationShadeWindowController.class);
-        dependency.injectMockDependency(MediaOutputDialogFactory.class);
+        dependency.injectMockDependency(MediaOutputDialogManager.class);
         mMockLogger = mock(ExpandableNotificationRowLogger.class);
         mStatusBarStateController = mock(StatusBarStateController.class);
         mKeyguardBypassController = mock(KeyguardBypassController.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelTest.kt
new file mode 100644
index 0000000..f88bd7e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelTest.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.notification.row.ui.viewmodel
+
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.fakePowerRepository
+import com.android.systemui.power.shared.model.WakefulnessState
+import com.android.systemui.statusbar.notification.shared.NotificationViewFlipperPausing
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(NotificationViewFlipperPausing.FLAG_NAME)
+class NotificationViewFlipperViewModelTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    val underTest
+        get() = kosmos.notificationViewFlipperViewModel
+
+    @Test
+    fun testIsPaused_falseWhenViewingShade() =
+        kosmos.testScope.runTest {
+            val isPaused by collectLastValue(underTest.isPaused)
+
+            // WHEN shade is open
+            kosmos.fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            runCurrent()
+
+            // THEN view flippers should NOT be paused
+            assertThat(isPaused).isFalse()
+        }
+
+    @Test
+    fun testIsPaused_trueWhenViewingKeyguard() =
+        kosmos.testScope.runTest {
+            val isPaused by collectLastValue(underTest.isPaused)
+
+            // WHEN on keyguard
+            kosmos.fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+            runCurrent()
+
+            // THEN view flippers should be paused
+            assertThat(isPaused).isTrue()
+        }
+
+    @Test
+    fun testIsPaused_trueWhenStartingToSleep() =
+        kosmos.testScope.runTest {
+            val isPaused by collectLastValue(underTest.isPaused)
+
+            // WHEN shade is open
+            kosmos.fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            // AND device is starting to go to sleep
+            kosmos.fakePowerRepository.updateWakefulness(WakefulnessState.STARTING_TO_SLEEP)
+            runCurrent()
+
+            // THEN view flippers should be paused
+            assertThat(isPaused).isTrue()
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
index 3d75288..4715b33 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
@@ -57,39 +57,6 @@
             )
     }
 
-    // region isDimmed
-    @Test
-    fun isDimmed_whenTrue_shouldReturnTrue() {
-        sut.arrangeDimmed(true)
-
-        assertThat(sut.isDimmed).isTrue()
-    }
-
-    @Test
-    fun isDimmed_whenFalse_shouldReturnFalse() {
-        sut.arrangeDimmed(false)
-
-        assertThat(sut.isDimmed).isFalse()
-    }
-
-    @Test
-    fun isDimmed_whenDozeAmountIsEmpty_shouldReturnTrue() {
-        sut.arrangeDimmed(true)
-        sut.dozeAmount = 0f
-
-        assertThat(sut.isDimmed).isTrue()
-    }
-
-    @Test
-    fun isDimmed_whenPulseExpandingIsFalse_shouldReturnTrue() {
-        sut.arrangeDimmed(true)
-        sut.arrangePulseExpanding(false)
-        sut.dozeAmount = 1f // arrangePulseExpanding changes dozeAmount
-
-        assertThat(sut.isDimmed).isTrue()
-    }
-    // endregion
-
     // region pulseHeight
     @Test
     fun pulseHeight_whenValueChanged_shouldCallListener() {
@@ -383,12 +350,6 @@
 }
 
 // region Arrange helper methods.
-private fun AmbientState.arrangeDimmed(value: Boolean) {
-    isDimmed = value
-    dozeAmount = if (value) 0f else 1f
-    arrangePulseExpanding(!value)
-}
-
 private fun AmbientState.arrangePulseExpanding(value: Boolean) {
     if (value) {
         dozeAmount = 1f
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index f326cea..220305c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -38,7 +38,6 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.clearInvocations;
@@ -73,11 +72,11 @@
 import com.android.systemui.ExpandHelper;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.EnableSceneContainer;
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.res.R;
-import com.android.systemui.scene.shared.flag.SceneContainerFlag;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
 import com.android.systemui.statusbar.EmptyShadeView;
@@ -97,13 +96,11 @@
 import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
 
 import org.junit.Assert;
-import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
-import org.mockito.ArgumentMatcher;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
@@ -232,6 +229,7 @@
     }
 
     @Test
+    @DisableFlags(FLAG_SCENE_CONTAINER) // TODO(b/312473478): address disabled test
     public void testUpdateStackHeight_qsExpansionZero() {
         final float expansionFraction = 0.2f;
         final float overExpansion = 50f;
@@ -307,14 +305,6 @@
     }
 
     @Test
-    public void testNotDimmedOnKeyguard() {
-        when(mBarState.getState()).thenReturn(StatusBarState.SHADE);
-        mStackScroller.setDimmed(true /* dimmed */, false /* animate */);
-        mStackScroller.setDimmed(true /* dimmed */, true /* animate */);
-        assertFalse(mStackScroller.isDimmed());
-    }
-
-    @Test
     public void updateEmptyView_dndSuppressing() {
         when(mEmptyShadeView.willBeGone()).thenReturn(true);
 
@@ -738,6 +728,7 @@
     }
 
     @Test
+    @DisableFlags(FLAG_SCENE_CONTAINER) // TODO(b/312473478): address lack of QS Header
     public void testInsideQSHeader_noOffset() {
         ViewGroup qsHeader = mock(ViewGroup.class);
         Rect boundsOnScreen = new Rect(0, 0, 1000, 1000);
@@ -754,6 +745,7 @@
     }
 
     @Test
+    @DisableFlags(FLAG_SCENE_CONTAINER) // TODO(b/312473478): address lack of QS Header
     public void testInsideQSHeader_Offset() {
         ViewGroup qsHeader = mock(ViewGroup.class);
         Rect boundsOnScreen = new Rect(100, 100, 1000, 1000);
@@ -773,12 +765,14 @@
     }
 
     @Test
+    @DisableFlags(FLAG_SCENE_CONTAINER) // TODO(b/312473478): address disabled test
     public void setFractionToShade_recomputesStackHeight() {
         mStackScroller.setFractionToShade(1f);
         verify(mNotificationStackSizeCalculator).computeHeight(any(), anyInt(), anyFloat());
     }
 
     @Test
+    @DisableFlags(FLAG_SCENE_CONTAINER) // TODO(b/312473478): address disabled test
     public void testSetOwnScrollY_shadeNotClosing_scrollYChanges() {
         // Given: shade is not closing, scrollY is 0
         mAmbientState.setScrollY(0);
@@ -877,6 +871,7 @@
     }
 
     @Test
+    @DisableFlags(FLAG_SCENE_CONTAINER) // TODO(b/312473478): address disabled test
     public void testSplitShade_hasTopOverscroll() {
         mTestableResources
                 .addOverride(R.bool.config_use_split_notification_shade, /* value= */ true);
@@ -949,6 +944,7 @@
     }
 
     @Test
+    @DisableFlags(FLAG_SCENE_CONTAINER) // TODO(b/312473478): address disabled test
     public void testSetMaxDisplayedNotifications_notifiesListeners() {
         ExpandableView.OnHeightChangedListener listener =
                 mock(ExpandableView.OnHeightChangedListener.class);
@@ -963,9 +959,8 @@
     }
 
     @Test
+    @DisableFlags(FLAG_SCENE_CONTAINER)
     public void testDispatchTouchEvent_sceneContainerDisabled() {
-        Assume.assumeFalse(SceneContainerFlag.isEnabled());
-
         MotionEvent event = MotionEvent.obtain(
                 SystemClock.uptimeMillis(),
                 SystemClock.uptimeMillis(),
@@ -981,34 +976,60 @@
     }
 
     @Test
+    @EnableSceneContainer
     public void testDispatchTouchEvent_sceneContainerEnabled() {
-        Assume.assumeTrue(SceneContainerFlag.isEnabled());
         mStackScroller.setIsBeingDragged(true);
 
-        MotionEvent moveEvent = MotionEvent.obtain(
-                SystemClock.uptimeMillis(),
-                SystemClock.uptimeMillis(),
+        long downTime = SystemClock.uptimeMillis() - 100;
+        MotionEvent moveEvent1 = MotionEvent.obtain(
+                /* downTime= */ downTime,
+                /* eventTime= */ SystemClock.uptimeMillis(),
                 MotionEvent.ACTION_MOVE,
-                0,
-                0,
+                101,
+                201,
                 0
         );
-        MotionEvent syntheticDownEvent = moveEvent.copy();
+        MotionEvent syntheticDownEvent = moveEvent1.copy();
         syntheticDownEvent.setAction(MotionEvent.ACTION_DOWN);
-        mStackScroller.dispatchTouchEvent(moveEvent);
+        mStackScroller.dispatchTouchEvent(moveEvent1);
 
-        verify(mStackScrollLayoutController).sendTouchToSceneFramework(argThat(
-                new MotionEventMatcher(syntheticDownEvent)));
+        assertThatMotionEvent(captureTouchSentToSceneFramework()).matches(syntheticDownEvent);
+        assertTrue(mStackScroller.getIsBeingDragged());
+        clearInvocations(mStackScrollLayoutController);
 
-        mStackScroller.dispatchTouchEvent(moveEvent);
+        MotionEvent moveEvent2 = MotionEvent.obtain(
+                /* downTime= */ downTime,
+                /* eventTime= */ SystemClock.uptimeMillis(),
+                MotionEvent.ACTION_MOVE,
+                102,
+                202,
+                0
+        );
 
-        verify(mStackScrollLayoutController).sendTouchToSceneFramework(moveEvent);
+        mStackScroller.dispatchTouchEvent(moveEvent2);
+
+        assertThatMotionEvent(captureTouchSentToSceneFramework()).matches(moveEvent2);
+        assertTrue(mStackScroller.getIsBeingDragged());
+        clearInvocations(mStackScrollLayoutController);
+
+        MotionEvent upEvent = MotionEvent.obtain(
+                /* downTime= */ downTime,
+                /* eventTime= */ SystemClock.uptimeMillis(),
+                MotionEvent.ACTION_UP,
+                103,
+                203,
+                0
+        );
+
+        mStackScroller.dispatchTouchEvent(upEvent);
+
+        assertThatMotionEvent(captureTouchSentToSceneFramework()).matches(upEvent);
+        assertFalse(mStackScroller.getIsBeingDragged());
     }
 
     @Test
-    @EnableFlags(FLAG_SCENE_CONTAINER)
-    public void testDispatchTouchEvent_sceneContainerEnabled_actionUp() {
-        Assume.assumeTrue(SceneContainerFlag.isEnabled());
+    @EnableSceneContainer
+    public void testDispatchTouchEvent_sceneContainerEnabled_ignoresInitialActionUp() {
         mStackScroller.setIsBeingDragged(true);
 
         MotionEvent upEvent = MotionEvent.obtain(
@@ -1019,21 +1040,18 @@
                 0,
                 0
         );
-        MotionEvent syntheticDownEvent = upEvent.copy();
-        syntheticDownEvent.setAction(MotionEvent.ACTION_DOWN);
 
         mStackScroller.dispatchTouchEvent(upEvent);
-
-        verify(mStackScrollLayoutController, atLeastOnce()).sendTouchToSceneFramework(argThat(
-                new MotionEventMatcher(syntheticDownEvent)));
-
-        mStackScroller.dispatchTouchEvent(upEvent);
-
-        verify(mStackScrollLayoutController, atLeastOnce()).sendTouchToSceneFramework(argThat(
-                new MotionEventMatcher(upEvent)));
+        verify(mStackScrollLayoutController, never()).sendTouchToSceneFramework(any());
         assertFalse(mStackScroller.getIsBeingDragged());
     }
 
+    private MotionEvent captureTouchSentToSceneFramework() {
+        ArgumentCaptor<MotionEvent> captor = ArgumentCaptor.forClass(MotionEvent.class);
+        verify(mStackScrollLayoutController).sendTouchToSceneFramework(captor.capture());
+        return captor.getValue();
+    }
+
     private void setBarStateForTest(int state) {
         // Can't inject this through the listener or we end up on the actual implementation
         // rather than the mock because the spy just coppied the anonymous inner /shruggie.
@@ -1081,20 +1099,23 @@
         );
     }
 
-    private static class MotionEventMatcher implements ArgumentMatcher<MotionEvent> {
-        private final MotionEvent mLeftEvent;
+    private MotionEventSubject assertThatMotionEvent(MotionEvent actual) {
+        return new MotionEventSubject(actual);
+    }
 
-        MotionEventMatcher(MotionEvent leftEvent) {
-            mLeftEvent = leftEvent;
+    private static class MotionEventSubject {
+        private final MotionEvent mActual;
+
+        MotionEventSubject(MotionEvent actual) {
+            mActual = actual;
         }
 
-        @Override
-        public boolean matches(MotionEvent right) {
-            return mLeftEvent.getActionMasked() == right.getActionMasked()
-                    && mLeftEvent.getDownTime() == right.getDownTime()
-                    && mLeftEvent.getEventTime() == right.getEventTime()
-                    && mLeftEvent.getX() == right.getX()
-                    && mLeftEvent.getY() == right.getY();
+        public void matches(MotionEvent expected) {
+            assertThat(mActual.getActionMasked()).isEqualTo(expected.getActionMasked());
+            assertThat(mActual.getDownTime()).isEqualTo(expected.getDownTime());
+            assertThat(mActual.getEventTime()).isEqualTo(expected.getEventTime());
+            assertThat(mActual.getX()).isEqualTo(expected.getX());
+            assertThat(mActual.getY()).isEqualTo(expected.getY());
         }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index c02583a..ab28a2f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -718,7 +718,8 @@
 
     @Test
     public void onPrivateProfileAdded_ignoresUntilStartComplete() {
-        mSetFlagsRule.enableFlags(FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.enableFlags(FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         reset(mDeviceProvisionedController);
         when(mUserManager.isManagedProfile(anyInt())).thenReturn(false);
         mBroadcastReceiver.getValue().onReceive(null,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index 3a6324d..d0261ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -69,7 +69,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.AnimatorTestRule;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
+import com.android.systemui.media.dialog.MediaOutputDialogManager;
 import com.android.systemui.plugins.VolumeDialogController;
 import com.android.systemui.plugins.VolumeDialogController.State;
 import com.android.systemui.res.R;
@@ -130,7 +130,7 @@
     @Mock
     DeviceProvisionedController mDeviceProvisionedController;
     @Mock
-    MediaOutputDialogFactory mMediaOutputDialogFactory;
+    MediaOutputDialogManager mMediaOutputDialogManager;
     @Mock
     InteractionJankMonitor mInteractionJankMonitor;
     @Mock
@@ -196,7 +196,7 @@
                 mAccessibilityMgr,
                 mDeviceProvisionedController,
                 mConfigurationController,
-                mMediaOutputDialogFactory,
+                mMediaOutputDialogManager,
                 mInteractionJankMonitor,
                 mVolumePanelNavigationInteractor,
                 mVolumeNavigator,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/MediaKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/MediaKosmos.kt
index e1b1966..e788669 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/MediaKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/MediaKosmos.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.media
 
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.media.dialog.MediaOutputDialogFactory
+import com.android.systemui.media.dialog.MediaOutputDialogManager
 import com.android.systemui.util.mockito.mock
 
-var Kosmos.mediaOutputDialogFactory: MediaOutputDialogFactory by Kosmos.Fixture { mock {} }
+var Kosmos.mediaOutputDialogManager: MediaOutputDialogManager by Kosmos.Fixture { mock {} }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeActivityTaskManager.kt
similarity index 95%
rename from packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeActivityTaskManager.kt
index 920e5ee..41d2d60 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeActivityTaskManager.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 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.systemui.mediaprojection.taskswitcher.data.repository
+package com.android.systemui.mediaprojection.taskswitcher
 
 import android.app.ActivityManager.RunningTaskInfo
 import android.app.IActivityTaskManager
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeMediaProjectionManager.kt
similarity index 95%
rename from packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeMediaProjectionManager.kt
index 28393e8..2b6032c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeMediaProjectionManager.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 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.systemui.mediaprojection.taskswitcher.data.repository
+package com.android.systemui.mediaprojection.taskswitcher
 
 import android.media.projection.MediaProjectionInfo
 import android.media.projection.MediaProjectionManager
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/TaskSwitcherKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/TaskSwitcherKosmos.kt
new file mode 100644
index 0000000..d344b75
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/TaskSwitcherKosmos.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 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.mediaprojection.taskswitcher
+
+import android.os.Handler
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository
+import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor
+import com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel.TaskSwitcherNotificationViewModel
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+
+val Kosmos.fakeActivityTaskManager by Kosmos.Fixture { FakeActivityTaskManager() }
+
+val Kosmos.fakeMediaProjectionManager by Kosmos.Fixture { FakeMediaProjectionManager() }
+
+val Kosmos.activityTaskManagerTasksRepository by
+    Kosmos.Fixture {
+        ActivityTaskManagerTasksRepository(
+            activityTaskManager = fakeActivityTaskManager.activityTaskManager,
+            applicationScope = applicationCoroutineScope,
+            backgroundDispatcher = testDispatcher
+        )
+    }
+
+val Kosmos.mediaProjectionManagerRepository by
+    Kosmos.Fixture {
+        MediaProjectionManagerRepository(
+            mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
+            handler = Handler.getMain(),
+            applicationScope = applicationCoroutineScope,
+            tasksRepository = activityTaskManagerTasksRepository,
+            backgroundDispatcher = testDispatcher,
+            mediaProjectionServiceHelper = fakeMediaProjectionManager.helper,
+        )
+    }
+
+val Kosmos.taskSwitcherInteractor by
+    Kosmos.Fixture {
+        TaskSwitchInteractor(mediaProjectionManagerRepository, activityTaskManagerTasksRepository)
+    }
+
+val Kosmos.taskSwitcherViewModel by
+    Kosmos.Fixture { TaskSwitcherNotificationViewModel(taskSwitcherInteractor, testDispatcher) }
+
+@OptIn(ExperimentalCoroutinesApi::class)
+fun taskSwitcherKosmos() = Kosmos().apply { testDispatcher = UnconfinedTestDispatcher() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/battery/BatterySaverTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/battery/BatterySaverTileKosmos.kt
new file mode 100644
index 0000000..a2d1d93
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/battery/BatterySaverTileKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 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.qs.tiles.impl.battery
+
+import com.android.systemui.battery.BatterySaverModule
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.qsEventLogger
+
+val Kosmos.qsBatterySaverTileConfig by
+    Kosmos.Fixture { BatterySaverModule.provideBatterySaverTileConfig(qsEventLogger) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/internet/InternetTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/internet/InternetTileKosmos.kt
new file mode 100644
index 0000000..6772ba3
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/internet/InternetTileKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 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.qs.tiles.impl.internet
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.qsEventLogger
+import com.android.systemui.statusbar.connectivity.ConnectivityModule
+
+val Kosmos.qsInternetTileConfig by
+    Kosmos.Fixture { ConnectivityModule.provideInternetTileConfig(qsEventLogger) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelKosmos.kt
new file mode 100644
index 0000000..7ffa262
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 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.statusbar.notification.row.ui.viewmodel
+
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackInteractor
+
+val Kosmos.notificationViewFlipperViewModel by Fixture {
+    NotificationViewFlipperViewModel(
+        dumpManager = dumpManager,
+        stackInteractor = notificationStackInteractor,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java
index 5ae033c..d798b3b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java
@@ -30,6 +30,8 @@
     private boolean mIsAodPowerSave = false;
     private boolean mWirelessCharging;
     private boolean mPowerSaveMode = false;
+    private boolean mIsPluggedIn = false;
+    private boolean mIsExtremePowerSave = false;
 
     private final List<BatteryStateChangeCallback> mCallbacks = new ArrayList<>();
 
@@ -64,8 +66,35 @@
     }
 
     @Override
+    public boolean isExtremeSaverOn() {
+        return mIsExtremePowerSave;
+    }
+
+    /**
+     * Note: this does not affect the regular power saver. Triggers all callbacks, only on change.
+     */
+    public void setExtremeSaverOn(Boolean extremePowerSave) {
+        if (extremePowerSave == mIsExtremePowerSave) return;
+
+        mIsExtremePowerSave = extremePowerSave;
+        for (BatteryStateChangeCallback callback: mCallbacks) {
+            callback.onExtremeBatterySaverChanged(extremePowerSave);
+        }
+    }
+
+    @Override
     public boolean isPluggedIn() {
-        return false;
+        return mIsPluggedIn;
+    }
+
+    /**
+     * Notifies all registered callbacks
+     */
+    public void setPluggedIn(boolean pluggedIn) {
+        mIsPluggedIn = pluggedIn;
+        for (BatteryStateChangeCallback cb : mCallbacks) {
+            cb.onBatteryLevelChanged(0, pluggedIn, false);
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
index a3b1a0e..3938f77 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
@@ -24,7 +24,7 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testCase
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.media.mediaOutputDialogFactory
+import com.android.systemui.media.mediaOutputDialogManager
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
@@ -42,7 +42,7 @@
     Kosmos.Fixture { FakeLocalMediaRepositoryFactory { localMediaRepository } }
 
 val Kosmos.mediaOutputActionsInteractor by
-    Kosmos.Fixture { MediaOutputActionsInteractor(mediaOutputDialogFactory) }
+    Kosmos.Fixture { MediaOutputActionsInteractor(mediaOutputDialogManager) }
 val Kosmos.mediaControllerRepository by Kosmos.Fixture { FakeMediaControllerRepository() }
 val Kosmos.mediaOutputInteractor by
     Kosmos.Fixture {
diff --git a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
index f31eb44..23e269a 100644
--- a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
+++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
@@ -39,7 +39,9 @@
 import android.content.SharedPreferences;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
+import android.graphics.Point;
 import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
 import android.os.FileUtils;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
@@ -49,16 +51,22 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.Xml;
+import android.view.Display;
+import android.view.DisplayInfo;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.content.PackageMonitor;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
 
 import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -102,6 +110,9 @@
     @VisibleForTesting
     static final String WALLPAPER_INFO_STAGE = "wallpaper-info-stage";
 
+    @VisibleForTesting
+    static final String WALLPAPER_BACKUP_DEVICE_INFO_STAGE = "wallpaper-backup-device-info-stage";
+
     static final String EMPTY_SENTINEL = "empty";
     static final String QUOTA_SENTINEL = "quota";
 
@@ -110,6 +121,11 @@
     static final String SYSTEM_GENERATION = "system_gen";
     static final String LOCK_GENERATION = "lock_gen";
 
+    /**
+     * An approximate area threshold to compare device dimension similarity
+     */
+    static final int AREA_THRESHOLD = 50; // TODO (b/327637867): determine appropriate threshold
+
     // If this file exists, it means we exceeded our quota last time
     private File mQuotaFile;
     private boolean mQuotaExceeded;
@@ -121,6 +137,8 @@
     private boolean mSystemHasLiveComponent;
     private boolean mLockHasLiveComponent;
 
+    private DisplayManager mDisplayManager;
+
     @Override
     public void onCreate() {
         if (DEBUG) {
@@ -137,6 +155,8 @@
 
         mBackupManager = new BackupManager(getBaseContext());
         mEventLogger = new WallpaperEventLogger(mBackupManager, /* wallpaperAgent */ this);
+
+        mDisplayManager = getSystemService(DisplayManager.class);
     }
 
     @Override
@@ -175,9 +195,11 @@
             mSystemHasLiveComponent = mWallpaperManager.getWallpaperInfo(FLAG_SYSTEM) != null;
             mLockHasLiveComponent = mWallpaperManager.getWallpaperInfo(FLAG_LOCK) != null;
 
+            // performing backup of each file based on order of importance
             backupWallpaperInfoFile(/* sysOrLockChanged= */ sysChanged || lockChanged, data);
             backupSystemWallpaperFile(sharedPrefs, sysChanged, sysGeneration, data);
             backupLockWallpaperFileIfItExists(sharedPrefs, lockChanged, lockGeneration, data);
+            backupDeviceInfoFile(data);
         } catch (Exception e) {
             Slog.e(TAG, "Unable to back up wallpaper", e);
             mEventLogger.onBackupException(e);
@@ -191,6 +213,54 @@
         }
     }
 
+    /**
+     * This method backs up the device dimension information. The device data will always get
+     * overwritten when triggering a backup
+     */
+    private void backupDeviceInfoFile(FullBackupDataOutput data)
+            throws IOException {
+        final File deviceInfoStage = new File(getFilesDir(), WALLPAPER_BACKUP_DEVICE_INFO_STAGE);
+
+        // save the dimensions of the device with xml formatting
+        Point dimensions = getScreenDimensions();
+        Display smallerDisplay = getSmallerDisplayIfExists();
+        Point secondaryDimensions = smallerDisplay != null ? getRealSize(smallerDisplay) :
+                new Point(0, 0);
+
+        deviceInfoStage.createNewFile();
+        FileOutputStream fstream = new FileOutputStream(deviceInfoStage, false);
+        TypedXmlSerializer out = Xml.resolveSerializer(fstream);
+        out.startDocument(null, true);
+        out.startTag(null, "dimensions");
+
+        out.startTag(null, "width");
+        out.text(String.valueOf(dimensions.x));
+        out.endTag(null, "width");
+
+        out.startTag(null, "height");
+        out.text(String.valueOf(dimensions.y));
+        out.endTag(null, "height");
+
+        if (smallerDisplay != null) {
+            out.startTag(null, "secondarywidth");
+            out.text(String.valueOf(secondaryDimensions.x));
+            out.endTag(null, "secondarywidth");
+
+            out.startTag(null, "secondaryheight");
+            out.text(String.valueOf(secondaryDimensions.y));
+            out.endTag(null, "secondaryheight");
+        }
+
+        out.endTag(null, "dimensions");
+        out.endDocument();
+        fstream.flush();
+        FileUtils.sync(fstream);
+        fstream.close();
+
+        if (DEBUG) Slog.v(TAG, "Storing device dimension data");
+        backupFile(deviceInfoStage, data);
+    }
+
     private void backupWallpaperInfoFile(boolean sysOrLockChanged, FullBackupDataOutput data)
             throws IOException {
         final ParcelFileDescriptor wallpaperInfoFd = mWallpaperManager.getWallpaperInfoFile();
@@ -364,9 +434,22 @@
         final File infoStage = new File(filesDir, WALLPAPER_INFO_STAGE);
         final File imageStage = new File(filesDir, SYSTEM_WALLPAPER_STAGE);
         final File lockImageStage = new File(filesDir, LOCK_WALLPAPER_STAGE);
+        final File deviceDimensionsStage = new File(filesDir, WALLPAPER_BACKUP_DEVICE_INFO_STAGE);
         boolean lockImageStageExists = lockImageStage.exists();
 
         try {
+            // Parse the device dimensions of the source device and compare with target to
+            // to identify whether we need to skip the remainder of the restore process
+            Pair<Point, Point> sourceDeviceDimensions = parseDeviceDimensions(
+                    deviceDimensionsStage);
+
+            Point targetDeviceDimensions = getScreenDimensions();
+            if (sourceDeviceDimensions != null && targetDeviceDimensions != null
+                    && isSourceDeviceSignificantlySmallerThanTarget(sourceDeviceDimensions.first,
+                    targetDeviceDimensions)) {
+                Slog.d(TAG, "The source device is significantly smaller than target");
+            }
+
             // First parse the live component name so that we know for logging if we care about
             // logging errors with the image restore.
             ComponentName wpService = parseWallpaperComponent(infoStage, "wp");
@@ -400,6 +483,7 @@
             infoStage.delete();
             imageStage.delete();
             lockImageStage.delete();
+            deviceDimensionsStage.delete();
 
             SharedPreferences prefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
             prefs.edit()
@@ -409,6 +493,66 @@
         }
     }
 
+    /**
+     * This method parses the given file for the backed up device dimensions
+     *
+     * @param deviceDimensions the file which holds the device dimensions
+     * @return the backed up device dimensions
+     */
+    private Pair<Point, Point> parseDeviceDimensions(File deviceDimensions) {
+        int width = 0, height = 0, secondaryHeight = 0, secondaryWidth = 0;
+        try {
+            TypedXmlPullParser parser = Xml.resolvePullParser(
+                    new FileInputStream(deviceDimensions));
+
+            while (parser.next() != XmlPullParser.END_TAG) {
+                if (parser.getEventType() != XmlPullParser.START_TAG) {
+                    continue;
+                }
+
+                String name = parser.getName();
+
+                switch (name) {
+                    case "width":
+                        String widthText = readText(parser);
+                        width = Integer.valueOf(widthText);
+                        break;
+
+                    case "height":
+                        String textHeight = readText(parser);
+                        height = Integer.valueOf(textHeight);
+                        break;
+
+                    case "secondarywidth":
+                        String secondaryWidthText = readText(parser);
+                        secondaryWidth = Integer.valueOf(secondaryWidthText);
+                        break;
+
+                    case "secondaryheight":
+                        String secondaryHeightText = readText(parser);
+                        secondaryHeight = Integer.valueOf(secondaryHeightText);
+                        break;
+                    default:
+                        break;
+                }
+            }
+            return new Pair<>(new Point(width, height), new Point(secondaryWidth, secondaryHeight));
+
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    private static String readText(TypedXmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        String result = "";
+        if (parser.next() == XmlPullParser.TEXT) {
+            result = parser.getText();
+            parser.nextTag();
+        }
+        return result;
+    }
+
     @VisibleForTesting
     void updateWallpaperComponent(ComponentName wpService, int which)
             throws IOException {
@@ -691,6 +835,94 @@
         };
     }
 
+    /**
+     * This method retrieves the dimensions of the largest display of the device
+     *
+     * @return a @{Point} object that contains the dimensions of the largest display on the device
+     */
+    private Point getScreenDimensions() {
+        Point largetDimensions = null;
+        int maxArea = 0;
+
+        for (Display display : getInternalDisplays()) {
+            Point displaySize = getRealSize(display);
+
+            int width = displaySize.x;
+            int height = displaySize.y;
+            int area = width * height;
+
+            if (area > maxArea) {
+                maxArea = area;
+                largetDimensions = displaySize;
+            }
+        }
+
+        return largetDimensions;
+    }
+
+    private Point getRealSize(Display display) {
+        DisplayInfo displayInfo = new DisplayInfo();
+        display.getDisplayInfo(displayInfo);
+        return new Point(displayInfo.logicalWidth, displayInfo.logicalHeight);
+    }
+
+    /**
+     * This method returns the smaller display on a multi-display device
+     *
+     * @return Display that corresponds to the smaller display on a device or null if ther is only
+     * one Display on a device
+     */
+    private Display getSmallerDisplayIfExists() {
+        List<Display> internalDisplays = getInternalDisplays();
+        Point largestDisplaySize = getScreenDimensions();
+
+        // Find the first non-matching internal display
+        for (Display display : internalDisplays) {
+            Point displaySize = getRealSize(display);
+            if (displaySize.x != largestDisplaySize.x || displaySize.y != largestDisplaySize.y) {
+                return display;
+            }
+        }
+
+        // If no smaller display found, return null, as there is only a single display
+        return null;
+    }
+
+    /**
+     * This method retrieves the collection of Display objects available in the device.
+     * i.e. non-external displays are ignored
+     *
+     * @return list of displays corresponding to each display in the device
+     */
+    private List<Display> getInternalDisplays() {
+        Display[] allDisplays = mDisplayManager.getDisplays(
+                DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED);
+
+        List<Display> internalDisplays = new ArrayList<>();
+        for (Display display : allDisplays) {
+            if (display.getType() == Display.TYPE_INTERNAL) {
+                internalDisplays.add(display);
+            }
+        }
+        return internalDisplays;
+    }
+
+    /**
+     * This method compares the source and target dimensions, and returns true if there is a
+     * significant difference in area between them and the source dimensions are smaller than the
+     * target dimensions.
+     *
+     * @param sourceDimensions is the dimensions of the source device
+     * @param targetDimensions is the dimensions of the target device
+     */
+    @VisibleForTesting
+    boolean isSourceDeviceSignificantlySmallerThanTarget(Point sourceDimensions,
+            Point targetDimensions) {
+        int rawAreaDelta = (targetDimensions.x * targetDimensions.y)
+                - (sourceDimensions.x * sourceDimensions.y);
+        return rawAreaDelta > AREA_THRESHOLD;
+    }
+
     @VisibleForTesting
     boolean isDeviceInRestore() {
         try {
diff --git a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
index 3ecdf3f..ec9223c 100644
--- a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
+++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
@@ -59,6 +59,7 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.FileUtils;
 import android.os.ParcelFileDescriptor;
@@ -840,6 +841,26 @@
         testParseCropHints(testMap);
     }
 
+    @Test
+    public void test_sourceDimensionsAreLargerThanTarget() {
+        // source device is larger than target, expecting to get false
+        Point sourceDimensions = new Point(2208, 1840);
+        Point targetDimensions = new Point(1080, 2092);
+        boolean isSourceSmaller = mWallpaperBackupAgent
+                .isSourceDeviceSignificantlySmallerThanTarget(sourceDimensions, targetDimensions);
+        assertThat(isSourceSmaller).isEqualTo(false);
+    }
+
+    @Test
+    public void test_sourceDimensionsMuchSmallerThanTarget() {
+        // source device is smaller than target, expecting to get true
+        Point sourceDimensions = new Point(1080, 2092);
+        Point targetDimensions = new Point(2208, 1840);
+        boolean isSourceSmaller = mWallpaperBackupAgent
+                .isSourceDeviceSignificantlySmallerThanTarget(sourceDimensions, targetDimensions);
+        assertThat(isSourceSmaller).isEqualTo(true);
+    }
+
     private void testParseCropHints(Map<Integer, Rect> testMap) throws Exception {
         assumeTrue(multiCrop());
         mockRestoredStaticWallpaperFile(testMap);
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 29b9d44..1749ee3 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -84,6 +84,7 @@
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
@@ -98,6 +99,7 @@
 import android.service.appwidget.AppWidgetServiceDumpProto;
 import android.service.appwidget.WidgetProto;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.AttributeSet;
@@ -148,6 +150,7 @@
 import java.io.OutputStream;
 import java.io.PrintWriter;
 import java.nio.charset.StandardCharsets;
+import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -159,6 +162,7 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.LongSupplier;
 
 class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBackupProvider,
         OnCrossProfileWidgetProvidersChangeListener {
@@ -187,6 +191,13 @@
     // used to verify which request has successfully been received by the host.
     private static final AtomicLong UPDATE_COUNTER = new AtomicLong();
 
+    // Default reset interval for generated preview API rate limiting.
+    private static final long DEFAULT_GENERATED_PREVIEW_RESET_INTERVAL_MS =
+            Duration.ofHours(1).toMillis();
+    // Default max API calls per reset interval for generated preview API rate limiting.
+    private static final int DEFAULT_GENERATED_PREVIEW_MAX_CALLS_PER_INTERVAL = 2;
+
+
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -266,6 +277,8 @@
     // Mark widget lifecycle broadcasts as 'interactive'
     private Bundle mInteractiveBroadcast;
 
+    private ApiCounter mGeneratedPreviewsApiCounter;
+
     AppWidgetServiceImpl(Context context) {
         mContext = context;
     }
@@ -294,6 +307,17 @@
         mIsCombinedBroadcastEnabled = DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
             SystemUiDeviceConfigFlags.COMBINED_BROADCAST_ENABLED, true);
 
+        final long generatedPreviewResetInterval = DeviceConfig.getLong(NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.GENERATED_PREVIEW_API_RESET_INTERVAL_MS,
+                DEFAULT_GENERATED_PREVIEW_RESET_INTERVAL_MS);
+        final int generatedPreviewMaxCallsPerInterval = DeviceConfig.getInt(NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.GENERATED_PREVIEW_API_RESET_INTERVAL_MS,
+                DEFAULT_GENERATED_PREVIEW_MAX_CALLS_PER_INTERVAL);
+        mGeneratedPreviewsApiCounter = new ApiCounter(generatedPreviewResetInterval,
+                generatedPreviewMaxCallsPerInterval);
+        DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_SYSTEMUI,
+                new HandlerExecutor(mCallbackHandler), this::handleSystemUiDeviceConfigChange);
+
         BroadcastOptions opts = BroadcastOptions.makeBasic();
         opts.setBackgroundActivityStartsAllowed(false);
         opts.setInteractive(true);
@@ -2426,7 +2450,8 @@
         AppWidgetProviderInfo info = createPartialProviderInfo(providerId, ri, existing);
 
         if (android.os.Flags.allowPrivateProfile()
-                && android.multiuser.Flags.disablePrivateSpaceItemsOnHome()) {
+                && android.multiuser.Flags.disablePrivateSpaceItemsOnHome()
+                && android.multiuser.Flags.enablePrivateSpaceFeatures()) {
             // Do not add widget providers for profiles with items restricted on home screen.
             if (info != null && mUserManager
                     .getUserProperties(info.getProfile()).areItemsRestrictedOnHomeScreen()) {
@@ -2480,6 +2505,7 @@
     private void deleteProviderLocked(Provider provider) {
         deleteWidgetsLocked(provider, UserHandle.USER_ALL);
         mProviders.remove(provider);
+        mGeneratedPreviewsApiCounter.remove(provider.id);
 
         // no need to send the DISABLE broadcast, since the receiver is gone anyway
         cancelBroadcastsLocked(provider);
@@ -4004,7 +4030,7 @@
     }
 
     @Override
-    public void setWidgetPreview(@NonNull ComponentName providerComponent,
+    public boolean setWidgetPreview(@NonNull ComponentName providerComponent,
             @AppWidgetProviderInfo.CategoryFlags int widgetCategories,
             @NonNull RemoteViews preview) {
         final int userId = UserHandle.getCallingUserId();
@@ -4026,8 +4052,12 @@
                 throw new IllegalArgumentException(
                         providerComponent + " is not a valid AppWidget provider");
             }
-            provider.setGeneratedPreviewLocked(widgetCategories, preview);
-            scheduleNotifyGroupHostsForProvidersChangedLocked(userId);
+            if (mGeneratedPreviewsApiCounter.tryApiCall(providerId)) {
+                provider.setGeneratedPreviewLocked(widgetCategories, preview);
+                scheduleNotifyGroupHostsForProvidersChangedLocked(userId);
+                return true;
+            }
+            return false;
         }
     }
 
@@ -4068,6 +4098,26 @@
         }
     }
 
+    private void handleSystemUiDeviceConfigChange(DeviceConfig.Properties properties) {
+        Set<String> changed = properties.getKeyset();
+        synchronized (mLock) {
+            if (changed.contains(
+                    SystemUiDeviceConfigFlags.GENERATED_PREVIEW_API_RESET_INTERVAL_MS)) {
+                long resetIntervalMs = properties.getLong(
+                        SystemUiDeviceConfigFlags.GENERATED_PREVIEW_API_RESET_INTERVAL_MS,
+                        /* defaultValue= */ mGeneratedPreviewsApiCounter.getResetIntervalMs());
+                mGeneratedPreviewsApiCounter.setResetIntervalMs(resetIntervalMs);
+            }
+            if (changed.contains(
+                    SystemUiDeviceConfigFlags.GENERATED_PREVIEW_API_MAX_CALLS_PER_INTERVAL)) {
+                int maxCallsPerInterval = properties.getInt(
+                        SystemUiDeviceConfigFlags.GENERATED_PREVIEW_API_MAX_CALLS_PER_INTERVAL,
+                        /* defaultValue= */ mGeneratedPreviewsApiCounter.getMaxCallsPerInterval());
+                mGeneratedPreviewsApiCounter.setMaxCallsPerInterval(maxCallsPerInterval);
+            }
+        }
+    }
+
     private final class CallbackHandler extends Handler {
         public static final int MSG_NOTIFY_UPDATE_APP_WIDGET = 1;
         public static final int MSG_NOTIFY_PROVIDER_CHANGED = 2;
@@ -4541,11 +4591,11 @@
         }
     }
 
-    private static final class ProviderId {
+    static final class ProviderId {
         final int uid;
         final ComponentName componentName;
 
-        private ProviderId(int uid, ComponentName componentName) {
+        ProviderId(int uid, ComponentName componentName) {
             this.uid = uid;
             this.componentName = componentName;
         }
@@ -4788,6 +4838,96 @@
         }
     }
 
+    /**
+     * This class keeps track of API calls and implements rate limiting. One instance of this class
+     * tracks calls from all providers for one API, or a group of APIs that should share the same
+     * rate limit.
+     */
+    static final class ApiCounter {
+
+        private static final class ApiCallRecord {
+            // Number of times the API has been called for this provider.
+            public int apiCallCount = 0;
+            // The last time (from SystemClock.elapsedRealtime) the api call count was reset.
+            public long lastResetTimeMs = 0;
+
+            void reset(long nowMs) {
+                apiCallCount = 0;
+                lastResetTimeMs = nowMs;
+            }
+        }
+
+        private final Map<ProviderId, ApiCallRecord> mCallCount = new ArrayMap<>();
+        // The interval at which the call count is reset.
+        private long mResetIntervalMs;
+        // The max number of API calls per interval.
+        private int mMaxCallsPerInterval;
+        // Returns the current time (monotonic). By default this is SystemClock.elapsedRealtime.
+        private LongSupplier mMonotonicClock;
+
+        ApiCounter(long resetIntervalMs, int maxCallsPerInterval) {
+            this(resetIntervalMs, maxCallsPerInterval, SystemClock::elapsedRealtime);
+        }
+
+        ApiCounter(long resetIntervalMs, int maxCallsPerInterval,
+                LongSupplier monotonicClock) {
+            mResetIntervalMs = resetIntervalMs;
+            mMaxCallsPerInterval = maxCallsPerInterval;
+            mMonotonicClock = monotonicClock;
+        }
+
+        public void setResetIntervalMs(long resetIntervalMs) {
+            mResetIntervalMs = resetIntervalMs;
+        }
+
+        public long getResetIntervalMs() {
+            return mResetIntervalMs;
+        }
+
+        public void setMaxCallsPerInterval(int maxCallsPerInterval) {
+            mMaxCallsPerInterval = maxCallsPerInterval;
+        }
+
+        public int getMaxCallsPerInterval() {
+            return mMaxCallsPerInterval;
+        }
+
+        /**
+         * Returns true if the API call for the provider should be allowed, false if it should be
+         * rate-limited.
+         */
+        public boolean tryApiCall(@NonNull ProviderId provider) {
+            final ApiCallRecord record = getOrCreateRecord(provider);
+            final long now = mMonotonicClock.getAsLong();
+            final long timeSinceLastResetMs = now - record.lastResetTimeMs;
+            // If the last reset was beyond the reset interval, reset now.
+            if (timeSinceLastResetMs > mResetIntervalMs) {
+                record.reset(now);
+            }
+            if (record.apiCallCount < mMaxCallsPerInterval) {
+                record.apiCallCount++;
+                return true;
+            }
+            return false;
+        }
+
+        /**
+         * Remove the provider's call record from this counter, when the provider is no longer
+         * tracked.
+         */
+        public void remove(@NonNull ProviderId id) {
+            mCallCount.remove(id);
+        }
+
+        @NonNull
+        private ApiCallRecord getOrCreateRecord(@NonNull ProviderId provider) {
+            if (!mCallCount.containsKey(provider)) {
+                mCallCount.put(provider, new ApiCallRecord());
+            }
+            return mCallCount.get(provider);
+        }
+    }
+
     private class LoadedWidgetState {
         final Widget widget;
         final int hostTag;
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 551297b..7afb780 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -3935,6 +3935,24 @@
      */
     @GuardedBy("mLock")
     @Nullable
+    private ViewNode getViewNodeFromContextsLocked(@NonNull AutofillId autofillId) {
+        final int numContexts = mContexts.size();
+        for (int i = numContexts - 1; i >= 0; i--) {
+            final FillContext context = mContexts.get(i);
+            final ViewNode node = Helper.findViewNodeByAutofillId(context.getStructure(),
+                    autofillId);
+            if (node != null) {
+                return node;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Gets the latest non-empty value for the given id in the autofill contexts.
+     */
+    @GuardedBy("mLock")
+    @Nullable
     private AutofillValue getValueFromContextsLocked(@NonNull AutofillId autofillId) {
         final int numContexts = mContexts.size();
         for (int i = numContexts - 1; i >= 0; i--) {
@@ -6417,7 +6435,21 @@
                     mClient.onGetCredentialException(id, viewId, exception.getType(),
                             exception.getMessage());
                 } else if (response != null) {
-                    mClient.onGetCredentialResponse(id, viewId, response);
+                    if (viewId.isVirtualInt()) {
+                        ViewNode viewNode = getViewNodeFromContextsLocked(viewId);
+                        if (viewNode != null && viewNode.getCredentialManagerCallback() != null) {
+                            Bundle resultData = new Bundle();
+                            resultData.putParcelable(
+                                    CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE,
+                                    response);
+                            viewNode.getCredentialManagerCallback().send(SUCCESS_CREDMAN_SELECTOR,
+                                        resultData);
+                        } else {
+                            Slog.w(TAG, "View node not found after GetCredentialResponse");
+                        }
+                    } else {
+                        mClient.onGetCredentialResponse(id, viewId, response);
+                    }
                 } else {
                     Slog.w(TAG, "sendCredentialManagerResponseToApp called with null response"
                             + "and exception");
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 6d731b2..b1672ed 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -210,7 +210,7 @@
                     mActivityListener.onTopActivityChanged(displayId, topActivity,
                             UserHandle.USER_NULL);
                 } catch (RemoteException e) {
-                    Slog.w(TAG, "Unable to call mActivityListener", e);
+                    Slog.w(TAG, "Unable to call mActivityListener for display: " + displayId, e);
                 }
             }
 
@@ -220,7 +220,7 @@
                 try {
                     mActivityListener.onTopActivityChanged(displayId, topActivity, userId);
                 } catch (RemoteException e) {
-                    Slog.w(TAG, "Unable to call mActivityListener", e);
+                    Slog.w(TAG, "Unable to call mActivityListener for display: " + displayId, e);
                 }
             }
 
@@ -229,7 +229,7 @@
                 try {
                     mActivityListener.onDisplayEmpty(displayId);
                 } catch (RemoteException e) {
-                    Slog.w(TAG, "Unable to call mActivityListener", e);
+                    Slog.w(TAG, "Unable to call mActivityListener for display: " + displayId, e);
                 }
             }
         };
@@ -1213,7 +1213,7 @@
         mContext.startActivityAsUser(
                 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK),
                 ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle(),
-                mContext.getUser());
+                UserHandle.SYSTEM);
     }
 
     private void onSecureWindowShown(int displayId, int uid) {
diff --git a/services/core/java/com/android/server/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java
index f82a6aa..748253f 100644
--- a/services/core/java/com/android/server/DropBoxManagerService.java
+++ b/services/core/java/com/android/server/DropBoxManagerService.java
@@ -106,7 +106,7 @@
     private static final int DEFAULT_AGE_SECONDS = 3 * 86400;
     private static final int DEFAULT_MAX_FILES = 1000;
     private static final int DEFAULT_MAX_FILES_LOWRAM = 300;
-    private static final int DEFAULT_QUOTA_KB = 10 * 1024;
+    private static final int DEFAULT_QUOTA_KB = Build.IS_USERDEBUG ? 20 * 1024 : 10 * 1024;
     private static final int DEFAULT_QUOTA_PERCENT = 10;
     private static final int DEFAULT_RESERVE_PERCENT = 0;
     private static final int QUOTA_RESCAN_MILLIS = 5000;
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index 8dc15ad..25337a4 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -100,7 +100,7 @@
             "file_patterns": ["VcnManagementService\\.java"]
         },
         {
-            "name": "FrameworksNetTests",
+            "name": "FrameworksVpnTests",
             "options": [
                 {
                     "exclude-annotation": "com.android.testutils.SkipPresubmit"
@@ -163,9 +163,6 @@
                 }
             ],
             "file_patterns": ["PinnerService\\.java"]
-        },
-        {
-            "name": "FrameworksVpnTests"
         }
     ]
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index cfe1e18..5e36709 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -141,7 +141,6 @@
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKGROUND_CHECK;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKUP;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_BACKGROUND;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_LIGHT;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_NETWORK;
@@ -726,29 +725,19 @@
     // Whether we should use SCHED_FIFO for UI and RenderThreads.
     final boolean mUseFifoUiScheduling;
 
-    // Use an offload queue for long broadcasts, e.g. BOOT_COMPLETED.
-    // For simplicity, since we statically declare the size of the array of BroadcastQueues,
-    // we still create this new offload queue, but never ever put anything on it.
-    final boolean mEnableOffloadQueue;
-
     /**
      * Flag indicating if we should use {@link BroadcastQueueModernImpl} instead
      * of the default {@link BroadcastQueueImpl}.
      */
     final boolean mEnableModernQueue;
 
-    static final int BROADCAST_QUEUE_FG = 0;
-    static final int BROADCAST_QUEUE_BG = 1;
-    static final int BROADCAST_QUEUE_BG_OFFLOAD = 2;
-    static final int BROADCAST_QUEUE_FG_OFFLOAD = 3;
-
     @GuardedBy("this")
     private final SparseArray<IUnsafeIntentStrictModeCallback>
             mStrictModeCallbacks = new SparseArray<>();
 
     // Convenient for easy iteration over the queues. Foreground is first
     // so that dispatch of foreground broadcasts gets precedence.
-    final BroadcastQueue[] mBroadcastQueues;
+    private BroadcastQueue mBroadcastQueue;
 
     @GuardedBy("this")
     BroadcastStats mLastBroadcastStats;
@@ -758,43 +747,6 @@
 
     TraceErrorLogger mTraceErrorLogger;
 
-    BroadcastQueue broadcastQueueForIntent(Intent intent) {
-        return broadcastQueueForFlags(intent.getFlags(), intent);
-    }
-
-    BroadcastQueue broadcastQueueForFlags(int flags) {
-        return broadcastQueueForFlags(flags, null);
-    }
-
-    BroadcastQueue broadcastQueueForFlags(int flags, Object cookie) {
-        if (mEnableModernQueue) {
-            return mBroadcastQueues[0];
-        }
-
-        if (isOnFgOffloadQueue(flags)) {
-            if (DEBUG_BROADCAST_BACKGROUND) {
-                Slog.i(TAG_BROADCAST,
-                        "Broadcast intent " + cookie + " on foreground offload queue");
-            }
-            return mBroadcastQueues[BROADCAST_QUEUE_FG_OFFLOAD];
-        }
-
-        if (isOnBgOffloadQueue(flags)) {
-            if (DEBUG_BROADCAST_BACKGROUND) {
-                Slog.i(TAG_BROADCAST,
-                        "Broadcast intent " + cookie + " on background offload queue");
-            }
-            return mBroadcastQueues[BROADCAST_QUEUE_BG_OFFLOAD];
-        }
-
-        final boolean isFg = (flags & Intent.FLAG_RECEIVER_FOREGROUND) != 0;
-        if (DEBUG_BROADCAST_BACKGROUND) Slog.i(TAG_BROADCAST,
-                "Broadcast intent " + cookie + " on "
-                + (isFg ? "foreground" : "background") + " queue");
-        return (isFg) ? mBroadcastQueues[BROADCAST_QUEUE_FG]
-                : mBroadcastQueues[BROADCAST_QUEUE_BG];
-    }
-
     private volatile int mDeviceOwnerUid = INVALID_UID;
 
     /**
@@ -2556,9 +2508,8 @@
         mInternal = new LocalService();
         mPendingStartActivityUids = new PendingStartActivityUids();
         mUseFifoUiScheduling = false;
-        mEnableOffloadQueue = false;
         mEnableModernQueue = false;
-        mBroadcastQueues = injector.getBroadcastQueues(this);
+        mBroadcastQueue = injector.getBroadcastQueue(this);
         mComponentAliasResolver = new ComponentAliasResolver(this);
     }
 
@@ -2599,12 +2550,10 @@
                 ? new OomAdjusterModernImpl(this, mProcessList, activeUids)
                 : new OomAdjuster(this, mProcessList, activeUids);
 
-        mEnableOffloadQueue = SystemProperties.getBoolean(
-                "persist.device_config.activity_manager_native_boot.offload_queue_enabled", true);
         mEnableModernQueue = new BroadcastConstants(
                 Settings.Global.BROADCAST_FG_CONSTANTS).MODERN_QUEUE_ENABLED;
 
-        mBroadcastQueues = mInjector.getBroadcastQueues(this);
+        mBroadcastQueue = mInjector.getBroadcastQueue(this);
 
         mServices = new ActiveServices(this);
         mCpHelper = new ContentProviderHelper(this, true);
@@ -2671,6 +2620,14 @@
         mComponentAliasResolver = new ComponentAliasResolver(this);
     }
 
+    void setBroadcastQueueForTest(BroadcastQueue broadcastQueue) {
+        mBroadcastQueue = broadcastQueue;
+    }
+
+    BroadcastQueue getBroadcastQueue() {
+        return mBroadcastQueue;
+    }
+
     public void setSystemServiceManager(SystemServiceManager mgr) {
         mSystemServiceManager = mgr;
     }
@@ -4280,19 +4237,14 @@
         }
 
         // Clean-up disabled broadcast receivers.
-        for (int i = mBroadcastQueues.length - 1; i >= 0; i--) {
-            mBroadcastQueues[i].cleanupDisabledPackageReceiversLocked(
-                    packageName, disabledClasses, userId);
-        }
+        mBroadcastQueue.cleanupDisabledPackageReceiversLocked(
+                packageName, disabledClasses, userId);
 
     }
 
     final boolean clearBroadcastQueueForUserLocked(int userId) {
-        boolean didSomething = false;
-        for (int i = mBroadcastQueues.length - 1; i >= 0; i--) {
-            didSomething |= mBroadcastQueues[i].cleanupDisabledPackageReceiversLocked(
-                    null, null, userId);
-        }
+        boolean didSomething = mBroadcastQueue.cleanupDisabledPackageReceiversLocked(
+                null, null, userId);
         return didSomething;
     }
 
@@ -4445,10 +4397,8 @@
         mUgmInternal.removeUriPermissionsForPackage(packageName, userId, false, false);
 
         if (doit) {
-            for (i = mBroadcastQueues.length - 1; i >= 0; i--) {
-                didSomething |= mBroadcastQueues[i].cleanupDisabledPackageReceiversLocked(
+            didSomething |= mBroadcastQueue.cleanupDisabledPackageReceiversLocked(
                         packageName, null, userId);
-            }
         }
 
         if (packageName == null || uninstalling || packageStateStopped) {
@@ -4515,9 +4465,7 @@
                 // Take care of any services that are waiting for the process.
                 mServices.processStartTimedOutLocked(app);
                 // Take care of any broadcasts waiting for the process.
-                for (BroadcastQueue queue : mBroadcastQueues) {
-                    queue.onApplicationTimeoutLocked(app);
-                }
+                mBroadcastQueue.onApplicationTimeoutLocked(app);
                 if (!isKillTimeout) {
                     mBatteryStatsService.noteProcessFinish(app.processName, app.info.uid);
                     app.killLocked("start timeout",
@@ -4959,9 +4907,7 @@
             // Check if a next-broadcast receiver is in this process...
             if (!badApp) {
                 try {
-                    for (BroadcastQueue queue : mBroadcastQueues) {
-                        didSomething |= queue.onApplicationAttachedLocked(app);
-                    }
+                    didSomething |= mBroadcastQueue.onApplicationAttachedLocked(app);
                     checkTime(startTime, "finishAttachApplicationInner: "
                             + "after dispatching broadcasts");
                 } catch (BroadcastDeliveryFailedException e) {
@@ -9101,9 +9047,7 @@
     }
 
     private void startBroadcastObservers() {
-        for (BroadcastQueue queue : mBroadcastQueues) {
-            queue.start(mContext.getContentResolver());
-        }
+        mBroadcastQueue.start(mContext.getContentResolver());
     }
 
     private void updateForceBackgroundCheck(boolean enabled) {
@@ -11509,9 +11453,7 @@
             }
         }
         mReceiverResolver.dumpDebug(proto, ActivityManagerServiceDumpBroadcastsProto.RECEIVER_RESOLVER);
-        for (BroadcastQueue q : mBroadcastQueues) {
-            q.dumpDebug(proto, ActivityManagerServiceDumpBroadcastsProto.BROADCAST_QUEUE);
-        }
+        mBroadcastQueue.dumpDebug(proto, ActivityManagerServiceDumpBroadcastsProto.BROADCAST_QUEUE);
         synchronized (mStickyBroadcasts) {
             for (int user = 0; user < mStickyBroadcasts.size(); user++) {
                 long token = proto.start(
@@ -11661,11 +11603,9 @@
         }
 
         if (!onlyReceivers) {
-            for (BroadcastQueue q : mBroadcastQueues) {
-                needSep = q.dumpLocked(fd, pw, args, opti,
-                        dumpConstants, dumpHistory, dumpAll, dumpPackage, needSep);
-                printedAnything |= needSep;
-            }
+            needSep = mBroadcastQueue.dumpLocked(fd, pw, args, opti,
+                    dumpConstants, dumpHistory, dumpAll, dumpPackage, needSep);
+            printedAnything |= needSep;
         }
 
         needSep = true;
@@ -11721,9 +11661,8 @@
 
         if (!onlyHistory && !onlyReceivers && dumpAll) {
             pw.println();
-            for (BroadcastQueue queue : mBroadcastQueues) {
-                pw.println("  Queue " + queue.toString() + ": " + queue.describeStateLocked());
-            }
+            pw.println("  Queue " + mBroadcastQueue.toString() + ": "
+                    + mBroadcastQueue.describeStateLocked());
             pw.println("  mHandler:");
             mHandler.dump(new PrintWriterPrinter(pw), "    ");
             needSep = true;
@@ -13571,9 +13510,7 @@
             mOomAdjuster.mCachedAppOptimizer.onCleanupApplicationRecordLocked(app);
         }
         mAppProfiler.onCleanupApplicationRecordLocked(app);
-        for (BroadcastQueue queue : mBroadcastQueues) {
-            queue.onApplicationCleanupLocked(app);
-        }
+        mBroadcastQueue.onApplicationCleanupLocked(app);
         clearProcessForegroundLocked(app);
         mServices.killServicesLocked(app, allowRestart);
         mPhantomProcessList.onAppDied(pid);
@@ -14687,7 +14624,7 @@
                             originalStickyCallingUid))) {
                         sticky = broadcast.intent;
                     }
-                    BroadcastQueue queue = broadcastQueueForIntent(broadcast.intent);
+                    BroadcastQueue queue = mBroadcastQueue;
                     BroadcastRecord r = new BroadcastRecord(queue, broadcast.intent, null,
                             null, null, -1, -1, false, null, null, null, null, OP_NONE,
                             BroadcastOptions.makeWithDeferUntilActive(broadcast.deferUntilActive),
@@ -15686,7 +15623,7 @@
                 checkBroadcastFromSystem(intent, callerApp, callerPackage, callingUid,
                         isProtectedBroadcast, registeredReceivers);
             }
-            final BroadcastQueue queue = broadcastQueueForIntent(intent);
+            final BroadcastQueue queue = mBroadcastQueue;
             BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage,
                     callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType,
                     requiredPermissions, excludedPermissions, excludedPackages, appOp, brOptions,
@@ -15779,7 +15716,7 @@
 
         if ((receivers != null && receivers.size() > 0)
                 || resultTo != null) {
-            BroadcastQueue queue = broadcastQueueForIntent(intent);
+            BroadcastQueue queue = mBroadcastQueue;
             filterNonExportedComponents(intent, callingUid, callingPid, receivers,
                     mPlatformCompat, callerPackage, resolvedType);
             BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage,
@@ -16067,9 +16004,7 @@
     }
 
     void backgroundServicesFinishedLocked(int userId) {
-        for (BroadcastQueue queue : mBroadcastQueues) {
-            queue.backgroundServicesFinishedLocked(userId);
-        }
+        mBroadcastQueue.backgroundServicesFinishedLocked(userId);
     }
 
     public void finishReceiver(IBinder caller, int resultCode, String resultData,
@@ -16090,8 +16025,7 @@
                     return;
                 }
 
-                final BroadcastQueue queue = broadcastQueueForFlags(flags);
-                queue.finishReceiverLocked(callerApp, resultCode,
+                mBroadcastQueue.finishReceiverLocked(callerApp, resultCode,
                         resultData, resultExtras, resultAbort, true);
                 // updateOomAdjLocked() will be done here
                 trimApplicationsLocked(false, OOM_ADJ_REASON_FINISH_RECEIVER);
@@ -16682,10 +16616,7 @@
     // =========================================================
 
     boolean isReceivingBroadcastLocked(ProcessRecord app, int[] outSchedGroup) {
-        int res = ProcessList.SCHED_GROUP_UNDEFINED;
-        for (BroadcastQueue queue : mBroadcastQueues) {
-            res = Math.max(res, queue.getPreferredSchedulingGroupLocked(app));
-        }
+        final int res = mBroadcastQueue.getPreferredSchedulingGroupLocked(app);
         outSchedGroup[0] = res;
         return res != ProcessList.SCHED_GROUP_UNDEFINED;
     }
@@ -16809,10 +16740,8 @@
      */
     @GuardedBy("this")
     final boolean canGcNowLocked() {
-        for (BroadcastQueue q : mBroadcastQueues) {
-            if (!q.isIdleLocked()) {
-                return false;
-            }
+        if (!mBroadcastQueue.isIdleLocked()) {
+            return false;
         }
         return mAtmInternal.canGcNow();
     }
@@ -18022,9 +17951,7 @@
     }
 
     void onProcessFreezableChangedLocked(ProcessRecord app) {
-        if (mEnableModernQueue) {
-            mBroadcastQueues[0].onProcessFreezableChangedLocked(app);
-        }
+        mBroadcastQueue.onProcessFreezableChangedLocked(app);
     }
 
     @VisibleForTesting
@@ -19715,9 +19642,7 @@
         if (flushBroadcastLoopers) {
             BroadcastLoopers.waitForIdle(pw);
         }
-        for (BroadcastQueue queue : mBroadcastQueues) {
-            queue.waitForIdle(pw);
-        }
+        mBroadcastQueue.waitForIdle(pw);
         pw.println("All broadcast queues are idle!");
         pw.flush();
     }
@@ -19733,9 +19658,7 @@
         if (flushBroadcastLoopers) {
             BroadcastLoopers.waitForBarrier(pw);
         }
-        for (BroadcastQueue queue : mBroadcastQueues) {
-            queue.waitForBarrier(pw);
-        }
+        mBroadcastQueue.waitForBarrier(pw);
         if (flushApplicationThreads) {
             waitForApplicationBarrier(pw);
         }
@@ -19811,9 +19734,7 @@
 
     void waitForBroadcastDispatch(@NonNull PrintWriter pw, @NonNull Intent intent) {
         enforceCallingPermission(permission.DUMP, "waitForBroadcastDispatch");
-        for (BroadcastQueue queue : mBroadcastQueues) {
-            queue.waitForDispatched(intent, pw);
-        }
+        mBroadcastQueue.waitForDispatched(intent, pw);
     }
 
     void setIgnoreDeliveryGroupPolicy(@NonNull String broadcastAction) {
@@ -19858,9 +19779,7 @@
             return;
         }
 
-        for (BroadcastQueue queue : mBroadcastQueues) {
-            queue.forceDelayBroadcastDelivery(targetPackage, delayedDurationMs);
-        }
+        mBroadcastQueue.forceDelayBroadcastDelivery(targetPackage, delayedDurationMs);
     }
 
     @Override
@@ -20414,7 +20333,7 @@
             return mNmi != null;
         }
 
-        public BroadcastQueue[] getBroadcastQueues(ActivityManagerService service) {
+        public BroadcastQueue getBroadcastQueue(ActivityManagerService service) {
             // Broadcast policy parameters
             final BroadcastConstants foreConstants = new BroadcastConstants(
                     Settings.Global.BROADCAST_FG_CONSTANTS);
@@ -20430,26 +20349,8 @@
             // by default, no "slow" policy in this queue
             offloadConstants.SLOW_TIME = Integer.MAX_VALUE;
 
-            final BroadcastQueue[] broadcastQueues;
-            final Handler handler = service.mHandler;
-            if (service.mEnableModernQueue) {
-                broadcastQueues = new BroadcastQueue[1];
-                broadcastQueues[0] = new BroadcastQueueModernImpl(service, handler,
+            return new BroadcastQueueModernImpl(service, service.mHandler,
                         foreConstants, backConstants);
-            } else {
-                broadcastQueues = new BroadcastQueue[4];
-                broadcastQueues[BROADCAST_QUEUE_FG] = new BroadcastQueueImpl(service, handler,
-                        "foreground", foreConstants, false, ProcessList.SCHED_GROUP_DEFAULT);
-                broadcastQueues[BROADCAST_QUEUE_BG] = new BroadcastQueueImpl(service, handler,
-                        "background", backConstants, true, ProcessList.SCHED_GROUP_BACKGROUND);
-                broadcastQueues[BROADCAST_QUEUE_BG_OFFLOAD] = new BroadcastQueueImpl(service,
-                        handler, "offload_bg", offloadConstants, true,
-                        ProcessList.SCHED_GROUP_BACKGROUND);
-                broadcastQueues[BROADCAST_QUEUE_FG_OFFLOAD] = new BroadcastQueueImpl(service,
-                        handler, "offload_fg", foreConstants, true,
-                        ProcessList.SCHED_GROUP_BACKGROUND);
-            }
-            return broadcastQueues;
         }
 
         /** @see Binder#getCallingUid */
@@ -20771,14 +20672,6 @@
         }
     }
 
-    private boolean isOnFgOffloadQueue(int flags) {
-        return ((flags & Intent.FLAG_RECEIVER_OFFLOAD_FOREGROUND) != 0);
-    }
-
-    private boolean isOnBgOffloadQueue(int flags) {
-        return (mEnableOffloadQueue && ((flags & Intent.FLAG_RECEIVER_OFFLOAD) != 0));
-    }
-
     @Override
     public ParcelFileDescriptor getLifeMonitor() {
         if (!isCallerShell()) {
diff --git a/services/core/java/com/android/server/am/BroadcastDispatcher.java b/services/core/java/com/android/server/am/BroadcastDispatcher.java
deleted file mode 100644
index 8aa3921..0000000
--- a/services/core/java/com/android/server/am/BroadcastDispatcher.java
+++ /dev/null
@@ -1,1266 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.am;
-
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_DEFERRAL;
-import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_NONE;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.UptimeMillisLong;
-import android.content.Intent;
-import android.content.pm.ResolveInfo;
-import android.os.Handler;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.util.Slog;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-import android.util.SparseIntArray;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.AlarmManagerInternal;
-import com.android.server.LocalServices;
-
-import dalvik.annotation.optimization.NeverCompile;
-
-import java.io.PrintWriter;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Set;
-
-/**
- * Manages ordered broadcast delivery, applying policy to mitigate the effects of
- * slow receivers.
- */
-public class BroadcastDispatcher {
-    private static final String TAG = "BroadcastDispatcher";
-
-    // Deferred broadcasts to one app; times are all uptime time base like
-    // other broadcast-related timekeeping
-    static class Deferrals {
-        final int uid;
-        long deferredAt;    // when we started deferring
-        long deferredBy;    // how long did we defer by last time?
-        long deferUntil;    // when does the next element become deliverable?
-        int alarmCount;
-
-        final ArrayList<BroadcastRecord> broadcasts;
-
-        Deferrals(int uid, long now, long backoff, int count) {
-            this.uid = uid;
-            this.deferredAt = now;
-            this.deferredBy = backoff;
-            this.deferUntil = now + backoff;
-            this.alarmCount = count;
-            broadcasts = new ArrayList<>();
-        }
-
-        void add(BroadcastRecord br) {
-            broadcasts.add(br);
-        }
-
-        int size() {
-            return broadcasts.size();
-        }
-
-        boolean isEmpty() {
-            return broadcasts.isEmpty();
-        }
-
-        @NeverCompile
-        void dumpDebug(ProtoOutputStream proto, long fieldId) {
-            for (BroadcastRecord br : broadcasts) {
-                br.dumpDebug(proto, fieldId);
-            }
-        }
-
-        @NeverCompile
-        void dumpLocked(Dumper d) {
-            for (BroadcastRecord br : broadcasts) {
-                d.dump(br);
-            }
-        }
-
-        @Override
-        public String toString() {
-            StringBuilder sb = new StringBuilder(128);
-            sb.append("Deferrals{uid=");
-            sb.append(uid);
-            sb.append(", deferUntil=");
-            sb.append(deferUntil);
-            sb.append(", #broadcasts=");
-            sb.append(broadcasts.size());
-            sb.append("}");
-            return sb.toString();
-        }
-    }
-
-    // Carrying dump formatting state across multiple concatenated datasets
-    class Dumper {
-        final PrintWriter mPw;
-        final String mQueueName;
-        final String mDumpPackage;
-        final SimpleDateFormat mSdf;
-        boolean mPrinted;
-        boolean mNeedSep;
-        String mHeading;
-        String mLabel;
-        int mOrdinal;
-
-        Dumper(PrintWriter pw, String queueName, String dumpPackage, SimpleDateFormat sdf) {
-            mPw = pw;
-            mQueueName = queueName;
-            mDumpPackage = dumpPackage;
-            mSdf = sdf;
-
-            mPrinted = false;
-            mNeedSep = true;
-        }
-
-        void setHeading(String heading) {
-            mHeading = heading;
-            mPrinted = false;
-        }
-
-        void setLabel(String label) {
-            //"  Active Ordered Broadcast " + mQueueName + " #" + i + ":"
-            mLabel = "  " + label + " " + mQueueName + " #";
-            mOrdinal = 0;
-        }
-
-        boolean didPrint() {
-            return mPrinted;
-        }
-
-        @NeverCompile
-        void dump(BroadcastRecord br) {
-            if (mDumpPackage == null || mDumpPackage.equals(br.callerPackage)) {
-                if (!mPrinted) {
-                    if (mNeedSep) {
-                        mPw.println();
-                    }
-                    mPrinted = true;
-                    mNeedSep = true;
-                    mPw.println("  " + mHeading + " [" + mQueueName + "]:");
-                }
-                mPw.println(mLabel + mOrdinal + ":");
-                mOrdinal++;
-
-                br.dump(mPw, "    ", mSdf);
-            }
-        }
-    }
-
-    private final Object mLock;
-    private final BroadcastQueueImpl mQueue;
-    private final BroadcastConstants mConstants;
-    private final Handler mHandler;
-    private AlarmManagerInternal mAlarm;
-
-    // Current alarm targets; mapping uid -> in-flight alarm count
-    final SparseIntArray mAlarmUids = new SparseIntArray();
-    final AlarmManagerInternal.InFlightListener mAlarmListener =
-            new AlarmManagerInternal.InFlightListener() {
-        @Override
-        public void broadcastAlarmPending(final int recipientUid) {
-            synchronized (mLock) {
-                final int newCount = mAlarmUids.get(recipientUid, 0) + 1;
-                mAlarmUids.put(recipientUid, newCount);
-                // any deferred broadcasts to this app now get fast-tracked
-                final int numEntries = mDeferredBroadcasts.size();
-                for (int i = 0; i < numEntries; i++) {
-                    if (recipientUid == mDeferredBroadcasts.get(i).uid) {
-                        Deferrals d = mDeferredBroadcasts.remove(i);
-                        mAlarmDeferrals.add(d);
-                        break;
-                    }
-                }
-            }
-        }
-
-        @Override
-        public void broadcastAlarmComplete(final int recipientUid) {
-            synchronized (mLock) {
-                final int newCount = mAlarmUids.get(recipientUid, 0) - 1;
-                if (newCount >= 0) {
-                    mAlarmUids.put(recipientUid, newCount);
-                } else {
-                    Slog.wtf(TAG, "Undercount of broadcast alarms in flight for " + recipientUid);
-                    mAlarmUids.put(recipientUid, 0);
-                }
-
-                // No longer an alarm target, so resume ordinary deferral policy
-                if (newCount <= 0) {
-                    final int numEntries = mAlarmDeferrals.size();
-                    for (int i = 0; i < numEntries; i++) {
-                        if (recipientUid == mAlarmDeferrals.get(i).uid) {
-                            Deferrals d = mAlarmDeferrals.remove(i);
-                            insertLocked(mDeferredBroadcasts, d);
-                            break;
-                        }
-                    }
-                }
-            }
-        }
-    };
-
-    // Queue recheck operation used to tickle broadcast delivery when appropriate
-    final Runnable mScheduleRunnable = new Runnable() {
-        @Override
-        public void run() {
-            synchronized (mLock) {
-                if (DEBUG_BROADCAST_DEFERRAL) {
-                    Slog.v(TAG, "Deferral recheck of pending broadcasts");
-                }
-                mQueue.scheduleBroadcastsLocked();
-                mRecheckScheduled = false;
-            }
-        }
-    };
-    private boolean mRecheckScheduled = false;
-
-    // Usual issuance-order outbound queue
-    private final ArrayList<BroadcastRecord> mOrderedBroadcasts = new ArrayList<>();
-    // General deferrals not holding up alarms
-    private final ArrayList<Deferrals> mDeferredBroadcasts = new ArrayList<>();
-    // Deferrals that *are* holding up alarms; ordered by alarm dispatch time
-    private final ArrayList<Deferrals> mAlarmDeferrals = new ArrayList<>();
-    // Under the "deliver alarm broadcasts immediately" policy, the queue of
-    // upcoming alarm broadcasts.  These are always delivered first - if the
-    // policy is changed on the fly from immediate-alarm-delivery to the previous
-    // in-order-queueing behavior, pending immediate alarm deliveries will drain
-    // and then the behavior settle into the pre-U semantics.
-    private final ArrayList<BroadcastRecord> mAlarmQueue = new ArrayList<>();
-
-    // Next outbound broadcast, established by getNextBroadcastLocked()
-    private BroadcastRecord mCurrentBroadcast;
-
-    // Map userId to its deferred boot completed broadcasts.
-    private SparseArray<DeferredBootCompletedBroadcastPerUser> mUser2Deferred = new SparseArray<>();
-
-    /**
-     * Deferred LOCKED_BOOT_COMPLETED and BOOT_COMPLETED broadcasts that is sent to a user.
-     */
-    static class DeferredBootCompletedBroadcastPerUser {
-        private int mUserId;
-        // UID that has process started at least once, ready to execute LOCKED_BOOT_COMPLETED
-        // receivers.
-        @VisibleForTesting
-        SparseBooleanArray mUidReadyForLockedBootCompletedBroadcast = new SparseBooleanArray();
-        // UID that has process started at least once, ready to execute BOOT_COMPLETED receivers.
-        @VisibleForTesting
-        SparseBooleanArray mUidReadyForBootCompletedBroadcast = new SparseBooleanArray();
-        // Map UID to deferred LOCKED_BOOT_COMPLETED broadcasts.
-        // LOCKED_BOOT_COMPLETED broadcast receivers are deferred until the first time the uid has
-        // any process started.
-        @VisibleForTesting
-        SparseArray<BroadcastRecord> mDeferredLockedBootCompletedBroadcasts = new SparseArray<>();
-        // is the LOCKED_BOOT_COMPLETED broadcast received by the user.
-        @VisibleForTesting
-        boolean mLockedBootCompletedBroadcastReceived;
-        // Map UID to deferred BOOT_COMPLETED broadcasts.
-        // BOOT_COMPLETED broadcast receivers are deferred until the first time the uid has any
-        // process started.
-        @VisibleForTesting
-        SparseArray<BroadcastRecord> mDeferredBootCompletedBroadcasts = new SparseArray<>();
-        // is the BOOT_COMPLETED broadcast received by the user.
-        @VisibleForTesting
-        boolean mBootCompletedBroadcastReceived;
-
-        DeferredBootCompletedBroadcastPerUser(int userId) {
-            this.mUserId = userId;
-        }
-
-        public void updateUidReady(int uid) {
-            if (!mLockedBootCompletedBroadcastReceived
-                    || mDeferredLockedBootCompletedBroadcasts.size() != 0) {
-                mUidReadyForLockedBootCompletedBroadcast.put(uid, true);
-            }
-            if (!mBootCompletedBroadcastReceived
-                    || mDeferredBootCompletedBroadcasts.size() != 0) {
-                mUidReadyForBootCompletedBroadcast.put(uid, true);
-            }
-        }
-
-        public void enqueueBootCompletedBroadcasts(String action,
-                SparseArray<BroadcastRecord> deferred) {
-            if (Intent.ACTION_LOCKED_BOOT_COMPLETED.equals(action)) {
-                enqueueBootCompletedBroadcasts(deferred, mDeferredLockedBootCompletedBroadcasts,
-                        mUidReadyForLockedBootCompletedBroadcast);
-                mLockedBootCompletedBroadcastReceived = true;
-                if (DEBUG_BROADCAST_DEFERRAL) {
-                    dumpBootCompletedBroadcastRecord(mDeferredLockedBootCompletedBroadcasts);
-                }
-            } else if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
-                enqueueBootCompletedBroadcasts(deferred, mDeferredBootCompletedBroadcasts,
-                        mUidReadyForBootCompletedBroadcast);
-                mBootCompletedBroadcastReceived = true;
-                if (DEBUG_BROADCAST_DEFERRAL) {
-                    dumpBootCompletedBroadcastRecord(mDeferredBootCompletedBroadcasts);
-                }
-            }
-        }
-
-        /**
-         * Merge UID to BroadcastRecord map into {@link #mDeferredBootCompletedBroadcasts} or
-         * {@link #mDeferredLockedBootCompletedBroadcasts}
-         * @param from the UID to BroadcastRecord map.
-         * @param into The UID to list of BroadcastRecord map.
-         */
-        private void enqueueBootCompletedBroadcasts(SparseArray<BroadcastRecord> from,
-                SparseArray<BroadcastRecord> into, SparseBooleanArray uidReadyForReceiver) {
-            // remove unwanted uids from uidReadyForReceiver.
-            for (int i = uidReadyForReceiver.size() - 1; i >= 0; i--) {
-                if (from.indexOfKey(uidReadyForReceiver.keyAt(i)) < 0) {
-                    uidReadyForReceiver.removeAt(i);
-                }
-            }
-            for (int i = 0, size = from.size(); i < size; i++) {
-                final int uid = from.keyAt(i);
-                into.put(uid, from.valueAt(i));
-                if (uidReadyForReceiver.indexOfKey(uid) < 0) {
-                    // uid is wanted but not ready.
-                    uidReadyForReceiver.put(uid, false);
-                }
-            }
-        }
-
-        public @Nullable BroadcastRecord dequeueDeferredBootCompletedBroadcast(
-                boolean isAllUidReady) {
-            BroadcastRecord next = dequeueDeferredBootCompletedBroadcast(
-                    mDeferredLockedBootCompletedBroadcasts,
-                    mUidReadyForLockedBootCompletedBroadcast, isAllUidReady);
-            if (next == null) {
-                next = dequeueDeferredBootCompletedBroadcast(mDeferredBootCompletedBroadcasts,
-                        mUidReadyForBootCompletedBroadcast, isAllUidReady);
-            }
-            return next;
-        }
-
-        private @Nullable BroadcastRecord dequeueDeferredBootCompletedBroadcast(
-                SparseArray<BroadcastRecord> uid2br, SparseBooleanArray uidReadyForReceiver,
-                boolean isAllUidReady) {
-            for (int i = 0, size = uid2br.size(); i < size; i++) {
-                final int uid = uid2br.keyAt(i);
-                if (isAllUidReady || uidReadyForReceiver.get(uid)) {
-                    final BroadcastRecord br = uid2br.valueAt(i);
-                    if (DEBUG_BROADCAST_DEFERRAL) {
-                        final Object receiver = br.receivers.get(0);
-                        if (receiver instanceof BroadcastFilter) {
-                            if (DEBUG_BROADCAST_DEFERRAL) {
-                                Slog.i(TAG, "getDeferredBootCompletedBroadcast uid:" + uid
-                                        + " BroadcastFilter:" + (BroadcastFilter) receiver
-                                        + " broadcast:" + br.intent.getAction());
-                            }
-                        } else /* if (receiver instanceof ResolveInfo) */ {
-                            ResolveInfo info = (ResolveInfo) receiver;
-                            String packageName = info.activityInfo.applicationInfo.packageName;
-                            if (DEBUG_BROADCAST_DEFERRAL) {
-                                Slog.i(TAG, "getDeferredBootCompletedBroadcast uid:" + uid
-                                        + " packageName:" + packageName
-                                        + " broadcast:" + br.intent.getAction());
-                            }
-                        }
-                    }
-                    // remove the BroadcastRecord.
-                    uid2br.removeAt(i);
-                    if (uid2br.size() == 0) {
-                        // All deferred receivers are executed, do not need uidReadyForReceiver
-                        // any more.
-                        uidReadyForReceiver.clear();
-                    }
-                    return br;
-                }
-            }
-            return null;
-        }
-
-        private @Nullable SparseArray<BroadcastRecord> getDeferredList(String action) {
-            SparseArray<BroadcastRecord> brs = null;
-            if (action.equals(Intent.ACTION_LOCKED_BOOT_COMPLETED)) {
-                brs = mDeferredLockedBootCompletedBroadcasts;
-            } else if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
-                brs = mDeferredBootCompletedBroadcasts;
-            }
-            return brs;
-        }
-
-        /**
-         * Return the total number of UIDs in all BroadcastRecord in
-         * {@link #mDeferredBootCompletedBroadcasts} or
-         * {@link #mDeferredLockedBootCompletedBroadcasts}
-         */
-        private int getBootCompletedBroadcastsUidsSize(String action) {
-            SparseArray<BroadcastRecord> brs = getDeferredList(action);
-            return brs != null ? brs.size() : 0;
-        }
-
-        /**
-         * Return the total number of receivers in all BroadcastRecord in
-         * {@link #mDeferredBootCompletedBroadcasts} or
-         * {@link #mDeferredLockedBootCompletedBroadcasts}
-         */
-        private int getBootCompletedBroadcastsReceiversSize(String action) {
-            SparseArray<BroadcastRecord> brs = getDeferredList(action);
-            if (brs == null) {
-                return 0;
-            }
-            int size = 0;
-            for (int i = 0, s = brs.size(); i < s; i++) {
-                size += brs.valueAt(i).receivers.size();
-            }
-            return size;
-        }
-
-        @NeverCompile
-        public void dump(Dumper dumper, String action) {
-            SparseArray<BroadcastRecord> brs = getDeferredList(action);
-            if (brs == null) {
-                return;
-            }
-            for (int i = 0, size = brs.size(); i < size; i++) {
-                dumper.dump(brs.valueAt(i));
-            }
-        }
-
-        @NeverCompile
-        public void dumpDebug(ProtoOutputStream proto, long fieldId) {
-            for (int i = 0, size = mDeferredLockedBootCompletedBroadcasts.size(); i < size; i++) {
-                mDeferredLockedBootCompletedBroadcasts.valueAt(i).dumpDebug(proto, fieldId);
-            }
-            for (int i = 0, size = mDeferredBootCompletedBroadcasts.size(); i < size; i++) {
-                mDeferredBootCompletedBroadcasts.valueAt(i).dumpDebug(proto, fieldId);
-            }
-        }
-
-        @NeverCompile
-        private void dumpBootCompletedBroadcastRecord(SparseArray<BroadcastRecord> brs) {
-            for (int i = 0, size = brs.size(); i < size; i++) {
-                final Object receiver = brs.valueAt(i).receivers.get(0);
-                String packageName = null;
-                if (receiver instanceof BroadcastFilter) {
-                    BroadcastFilter recv = (BroadcastFilter) receiver;
-                    packageName = recv.receiverList.app.processName;
-                } else /* if (receiver instanceof ResolveInfo) */ {
-                    ResolveInfo info = (ResolveInfo) receiver;
-                    packageName = info.activityInfo.applicationInfo.packageName;
-                }
-                Slog.i(TAG, "uid:" + brs.keyAt(i)
-                        + " packageName:" + packageName
-                        + " receivers:" + brs.valueAt(i).receivers.size());
-            }
-        }
-    }
-
-    private DeferredBootCompletedBroadcastPerUser getDeferredPerUser(int userId) {
-        if (mUser2Deferred.contains(userId)) {
-            return mUser2Deferred.get(userId);
-        } else {
-            final DeferredBootCompletedBroadcastPerUser temp =
-                    new DeferredBootCompletedBroadcastPerUser(userId);
-            mUser2Deferred.put(userId, temp);
-            return temp;
-        }
-    }
-
-    /**
-     * ActivityManagerService.attachApplication() call this method to notify that the UID is ready
-     * to accept deferred LOCKED_BOOT_COMPLETED and BOOT_COMPLETED broadcasts.
-     * @param uid
-     */
-    public void updateUidReadyForBootCompletedBroadcastLocked(int uid) {
-        getDeferredPerUser(UserHandle.getUserId(uid)).updateUidReady(uid);
-    }
-
-    private @Nullable BroadcastRecord dequeueDeferredBootCompletedBroadcast() {
-        final boolean isAllUidReady = (mQueue.mService.mConstants.mDeferBootCompletedBroadcast
-                == DEFER_BOOT_COMPLETED_BROADCAST_NONE);
-        BroadcastRecord next = null;
-        for (int i = 0, size = mUser2Deferred.size(); i < size; i++) {
-            next = mUser2Deferred.valueAt(i).dequeueDeferredBootCompletedBroadcast(isAllUidReady);
-            if (next != null) {
-                break;
-            }
-        }
-        return next;
-    }
-
-    /**
-     * Constructed & sharing a lock with its associated BroadcastQueue instance
-     */
-    public BroadcastDispatcher(BroadcastQueueImpl queue, BroadcastConstants constants,
-            Handler handler, Object lock) {
-        mQueue = queue;
-        mConstants = constants;
-        mHandler = handler;
-        mLock = lock;
-    }
-
-    /**
-     * Spin up the integration with the alarm manager service; done lazily to manage
-     * service availability ordering during boot.
-     */
-    public void start() {
-        // Set up broadcast alarm tracking
-        mAlarm = LocalServices.getService(AlarmManagerInternal.class);
-        mAlarm.registerInFlightListener(mAlarmListener);
-    }
-
-    /**
-     * Standard contents-are-empty check
-     */
-    public boolean isEmpty() {
-        synchronized (mLock) {
-            return isIdle()
-                    && getBootCompletedBroadcastsUidsSize(Intent.ACTION_LOCKED_BOOT_COMPLETED) == 0
-                    && getBootCompletedBroadcastsUidsSize(Intent.ACTION_BOOT_COMPLETED) == 0;
-        }
-    }
-
-    /**
-     * Have less check than {@link #isEmpty()}.
-     * The dispatcher is considered as idle even with deferred LOCKED_BOOT_COMPLETED/BOOT_COMPLETED
-     * broadcasts because those can be deferred until the first time the uid's process is started.
-     * @return
-     */
-    public boolean isIdle() {
-        synchronized (mLock) {
-            return mCurrentBroadcast == null
-                    && mOrderedBroadcasts.isEmpty()
-                    && mAlarmQueue.isEmpty()
-                    && isDeferralsListEmpty(mDeferredBroadcasts)
-                    && isDeferralsListEmpty(mAlarmDeferrals);
-        }
-    }
-
-    private static boolean isDeferralsBeyondBarrier(@NonNull ArrayList<Deferrals> list,
-            @UptimeMillisLong long barrierTime) {
-        for (int i = 0; i < list.size(); i++) {
-            if (!isBeyondBarrier(list.get(i).broadcasts, barrierTime)) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    private static boolean isBeyondBarrier(@NonNull ArrayList<BroadcastRecord> list,
-            @UptimeMillisLong long barrierTime) {
-        for (int i = 0; i < list.size(); i++) {
-            if (list.get(i).enqueueTime <= barrierTime) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    public boolean isBeyondBarrier(@UptimeMillisLong long barrierTime) {
-        synchronized (mLock) {
-            if ((mCurrentBroadcast != null) && mCurrentBroadcast.enqueueTime <= barrierTime) {
-                return false;
-            }
-            return isBeyondBarrier(mOrderedBroadcasts, barrierTime)
-                    && isBeyondBarrier(mAlarmQueue, barrierTime)
-                    && isDeferralsBeyondBarrier(mDeferredBroadcasts, barrierTime)
-                    && isDeferralsBeyondBarrier(mAlarmDeferrals, barrierTime);
-        }
-    }
-
-    private static boolean isDispatchedInDeferrals(@NonNull ArrayList<Deferrals> list,
-            @NonNull Intent intent) {
-        for (int i = 0; i < list.size(); i++) {
-            if (!isDispatched(list.get(i).broadcasts, intent)) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    private static boolean isDispatched(@NonNull ArrayList<BroadcastRecord> list,
-            @NonNull Intent intent) {
-        for (int i = 0; i < list.size(); i++) {
-            if (intent.filterEquals(list.get(i).intent)) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    public boolean isDispatched(@NonNull Intent intent) {
-        synchronized (mLock) {
-            if ((mCurrentBroadcast != null) && intent.filterEquals(mCurrentBroadcast.intent)) {
-                return false;
-            }
-            return isDispatched(mOrderedBroadcasts, intent)
-                    && isDispatched(mAlarmQueue, intent)
-                    && isDispatchedInDeferrals(mDeferredBroadcasts, intent)
-                    && isDispatchedInDeferrals(mAlarmDeferrals, intent);
-        }
-    }
-
-    private static int pendingInDeferralsList(ArrayList<Deferrals> list) {
-        int pending = 0;
-        final int numEntries = list.size();
-        for (int i = 0; i < numEntries; i++) {
-            pending += list.get(i).size();
-        }
-        return pending;
-    }
-
-    private static boolean isDeferralsListEmpty(ArrayList<Deferrals> list) {
-        return pendingInDeferralsList(list) == 0;
-    }
-
-    /**
-     * Strictly for logging, describe the currently pending contents in a human-
-     * readable way
-     */
-    public String describeStateLocked() {
-        final StringBuilder sb = new StringBuilder(128);
-        if (mCurrentBroadcast != null) {
-            sb.append("1 in flight, ");
-        }
-        sb.append(mOrderedBroadcasts.size());
-        sb.append(" ordered");
-        int n = mAlarmQueue.size();
-        if (n > 0) {
-            sb.append(", ");
-            sb.append(n);
-            sb.append(" alarms");
-        }
-        n = pendingInDeferralsList(mAlarmDeferrals);
-        if (n > 0) {
-            sb.append(", ");
-            sb.append(n);
-            sb.append(" deferrals in alarm recipients");
-        }
-        n = pendingInDeferralsList(mDeferredBroadcasts);
-        if (n > 0) {
-            sb.append(", ");
-            sb.append(n);
-            sb.append(" deferred");
-        }
-        n = getBootCompletedBroadcastsUidsSize(Intent.ACTION_LOCKED_BOOT_COMPLETED);
-        if (n > 0) {
-            sb.append(", ");
-            sb.append(n);
-            sb.append(" deferred LOCKED_BOOT_COMPLETED/");
-            sb.append(getBootCompletedBroadcastsReceiversSize(Intent.ACTION_LOCKED_BOOT_COMPLETED));
-            sb.append(" receivers");
-        }
-
-        n = getBootCompletedBroadcastsUidsSize(Intent.ACTION_BOOT_COMPLETED);
-        if (n > 0) {
-            sb.append(", ");
-            sb.append(n);
-            sb.append(" deferred BOOT_COMPLETED/");
-            sb.append(getBootCompletedBroadcastsReceiversSize(Intent.ACTION_BOOT_COMPLETED));
-            sb.append(" receivers");
-        }
-        return sb.toString();
-    }
-
-    // ----------------------------------
-    // BroadcastQueue operation support
-    void enqueueOrderedBroadcastLocked(BroadcastRecord r) {
-        final ArrayList<BroadcastRecord> queue =
-                (r.alarm && mQueue.mService.mConstants.mPrioritizeAlarmBroadcasts)
-                        ? mAlarmQueue
-                        : mOrderedBroadcasts;
-
-        if (r.receivers == null || r.receivers.isEmpty()) {
-            // Fast no-op path for broadcasts that won't actually be dispatched to
-            // receivers - we still need to handle completion callbacks and historical
-            // records, but we don't need to consider the fancy cases.
-            queue.add(r);
-            return;
-        }
-
-        if (Intent.ACTION_LOCKED_BOOT_COMPLETED.equals(r.intent.getAction())) {
-            // Create one BroadcastRecord for each UID that can be deferred.
-            final SparseArray<BroadcastRecord> deferred =
-                    r.splitDeferredBootCompletedBroadcastLocked(mQueue.mService.mInternal,
-                            mQueue.mService.mConstants.mDeferBootCompletedBroadcast);
-            getDeferredPerUser(r.userId).enqueueBootCompletedBroadcasts(
-                    Intent.ACTION_LOCKED_BOOT_COMPLETED, deferred);
-            if (!r.receivers.isEmpty()) {
-                // The non-deferred receivers.
-                mOrderedBroadcasts.add(r);
-                return;
-            }
-        } else if (Intent.ACTION_BOOT_COMPLETED.equals(r.intent.getAction())) {
-            // Create one BroadcastRecord for each UID that can be deferred.
-            final SparseArray<BroadcastRecord> deferred =
-                    r.splitDeferredBootCompletedBroadcastLocked(mQueue.mService.mInternal,
-                            mQueue.mService.mConstants.mDeferBootCompletedBroadcast);
-            getDeferredPerUser(r.userId).enqueueBootCompletedBroadcasts(
-                    Intent.ACTION_BOOT_COMPLETED, deferred);
-            if (!r.receivers.isEmpty()) {
-                // The non-deferred receivers.
-                mOrderedBroadcasts.add(r);
-                return;
-            }
-        } else {
-            // Ordinary broadcast, so put it on the appropriate queue and carry on
-            queue.add(r);
-        }
-    }
-
-    /**
-     * Return the total number of UIDs in all deferred boot completed BroadcastRecord.
-     */
-    private int getBootCompletedBroadcastsUidsSize(String action) {
-        int size = 0;
-        for (int i = 0, s = mUser2Deferred.size(); i < s; i++) {
-            size += mUser2Deferred.valueAt(i).getBootCompletedBroadcastsUidsSize(action);
-        }
-        return size;
-    }
-
-    /**
-     * Return the total number of receivers in all deferred boot completed BroadcastRecord.
-     */
-    private int getBootCompletedBroadcastsReceiversSize(String action) {
-        int size = 0;
-        for (int i = 0, s = mUser2Deferred.size(); i < s; i++) {
-            size += mUser2Deferred.valueAt(i).getBootCompletedBroadcastsReceiversSize(action);
-        }
-        return size;
-    }
-
-    // Returns the now-replaced broadcast record, or null if none
-    BroadcastRecord replaceBroadcastLocked(BroadcastRecord r, String typeForLogging) {
-        // Simple case, in the ordinary queue.
-        BroadcastRecord old = replaceBroadcastLocked(mOrderedBroadcasts, r, typeForLogging);
-        // ... or possibly in the simple alarm queue
-        if (old == null) {
-            old = replaceBroadcastLocked(mAlarmQueue, r, typeForLogging);
-        }
-        // If we didn't find it, less-simple:  in a deferral queue?
-        if (old == null) {
-            old = replaceDeferredBroadcastLocked(mAlarmDeferrals, r, typeForLogging);
-        }
-        if (old == null) {
-            old = replaceDeferredBroadcastLocked(mDeferredBroadcasts, r, typeForLogging);
-        }
-        return old;
-    }
-
-    private BroadcastRecord replaceDeferredBroadcastLocked(ArrayList<Deferrals> list,
-            BroadcastRecord r, String typeForLogging) {
-        BroadcastRecord old;
-        final int numEntries = list.size();
-        for (int i = 0; i < numEntries; i++) {
-            final Deferrals d = list.get(i);
-            old = replaceBroadcastLocked(d.broadcasts, r, typeForLogging);
-            if (old != null) {
-                return old;
-            }
-        }
-        return null;
-    }
-
-    private BroadcastRecord replaceBroadcastLocked(ArrayList<BroadcastRecord> list,
-            BroadcastRecord r, String typeForLogging) {
-        BroadcastRecord old;
-        final Intent intent = r.intent;
-        // Any in-flight broadcast has already been popped, and cannot be replaced.
-        // (This preserves existing behavior of the replacement API)
-        for (int i = list.size() - 1; i >= 0; i--) {
-            old = list.get(i);
-            if (old.userId == r.userId && intent.filterEquals(old.intent)) {
-                if (DEBUG_BROADCAST) {
-                    Slog.v(TAG, "***** Replacing " + typeForLogging
-                            + " [" + mQueue.mQueueName + "]: " + intent);
-                }
-                // Clone deferral state too if any
-                r.deferred = old.deferred;
-                list.set(i, r);
-                return old;
-            }
-        }
-        return null;
-    }
-
-    boolean cleanupDisabledPackageReceiversLocked(final String packageName,
-            Set<String> filterByClasses, final int userId, final boolean doit) {
-        // Note: fast short circuits when 'doit' is false, as soon as we hit any
-        // "yes we would do something" circumstance
-        boolean didSomething = cleanupBroadcastListDisabledReceiversLocked(mOrderedBroadcasts,
-                packageName, filterByClasses, userId, doit);
-        if (doit || !didSomething) {
-            didSomething = cleanupBroadcastListDisabledReceiversLocked(mAlarmQueue,
-                    packageName, filterByClasses, userId, doit);
-        }
-        if (doit || !didSomething) {
-            ArrayList<BroadcastRecord> lockedBootCompletedBroadcasts = new ArrayList<>();
-            for (int u = 0, usize = mUser2Deferred.size(); u < usize; u++) {
-                SparseArray<BroadcastRecord> brs =
-                        mUser2Deferred.valueAt(u).mDeferredLockedBootCompletedBroadcasts;
-                for (int i = 0, size = brs.size(); i < size; i++) {
-                    lockedBootCompletedBroadcasts.add(brs.valueAt(i));
-                }
-            }
-            didSomething = cleanupBroadcastListDisabledReceiversLocked(
-                    lockedBootCompletedBroadcasts,
-                    packageName, filterByClasses, userId, doit);
-        }
-        if (doit || !didSomething) {
-            ArrayList<BroadcastRecord> bootCompletedBroadcasts = new ArrayList<>();
-            for (int u = 0, usize = mUser2Deferred.size(); u < usize; u++) {
-                SparseArray<BroadcastRecord> brs =
-                        mUser2Deferred.valueAt(u).mDeferredBootCompletedBroadcasts;
-                for (int i = 0, size = brs.size(); i < size; i++) {
-                    bootCompletedBroadcasts.add(brs.valueAt(i));
-                }
-            }
-            didSomething = cleanupBroadcastListDisabledReceiversLocked(bootCompletedBroadcasts,
-                    packageName, filterByClasses, userId, doit);
-        }
-        if (doit || !didSomething) {
-            didSomething |= cleanupDeferralsListDisabledReceiversLocked(mAlarmDeferrals,
-                    packageName, filterByClasses, userId, doit);
-        }
-        if (doit || !didSomething) {
-            didSomething |= cleanupDeferralsListDisabledReceiversLocked(mDeferredBroadcasts,
-                    packageName, filterByClasses, userId, doit);
-        }
-        if ((doit || !didSomething) && mCurrentBroadcast != null) {
-            didSomething |= mCurrentBroadcast.cleanupDisabledPackageReceiversLocked(
-                    packageName, filterByClasses, userId, doit);
-        }
-
-        return didSomething;
-    }
-
-    private boolean cleanupDeferralsListDisabledReceiversLocked(ArrayList<Deferrals> list,
-            final String packageName, Set<String> filterByClasses, final int userId,
-            final boolean doit) {
-        boolean didSomething = false;
-        for (Deferrals d : list) {
-            didSomething = cleanupBroadcastListDisabledReceiversLocked(d.broadcasts,
-                    packageName, filterByClasses, userId, doit);
-            if (!doit && didSomething) {
-                return true;
-            }
-        }
-        return didSomething;
-    }
-
-    private boolean cleanupBroadcastListDisabledReceiversLocked(ArrayList<BroadcastRecord> list,
-            final String packageName, Set<String> filterByClasses, final int userId,
-            final boolean doit) {
-        boolean didSomething = false;
-        for (BroadcastRecord br : list) {
-            didSomething |= br.cleanupDisabledPackageReceiversLocked(packageName,
-                    filterByClasses, userId, doit);
-            if (!doit && didSomething) {
-                return true;
-            }
-        }
-        return didSomething;
-    }
-
-    /**
-     * Standard proto dump entry point
-     */
-    @NeverCompile
-    public void dumpDebug(ProtoOutputStream proto, long fieldId) {
-        if (mCurrentBroadcast != null) {
-            mCurrentBroadcast.dumpDebug(proto, fieldId);
-        }
-        for (Deferrals d : mAlarmDeferrals) {
-            d.dumpDebug(proto, fieldId);
-        }
-        for (BroadcastRecord br : mOrderedBroadcasts) {
-            br.dumpDebug(proto, fieldId);
-        }
-        for (BroadcastRecord br : mAlarmQueue) {
-            br.dumpDebug(proto, fieldId);
-        }
-        for (Deferrals d : mDeferredBroadcasts) {
-            d.dumpDebug(proto, fieldId);
-        }
-
-        for (int i = 0, size = mUser2Deferred.size(); i < size; i++) {
-            mUser2Deferred.valueAt(i).dumpDebug(proto, fieldId);
-        }
-    }
-
-    // ----------------------------------
-    // Dispatch & deferral management
-
-    public BroadcastRecord getActiveBroadcastLocked() {
-        return mCurrentBroadcast;
-    }
-
-    /**
-     * If there is a deferred broadcast that is being sent to an alarm target, return
-     * that one.  If there's no deferred alarm target broadcast but there is one
-     * that has reached the end of its deferral, return that.
-     *
-     * This stages the broadcast internally until it is retired, and returns that
-     * staged record if this is called repeatedly, until retireBroadcast(r) is called.
-     */
-    public BroadcastRecord getNextBroadcastLocked(final long now) {
-        if (mCurrentBroadcast != null) {
-            return mCurrentBroadcast;
-        }
-
-        BroadcastRecord next = null;
-
-        // Alarms in flight take precedence over everything else.  This queue
-        // will be non-empty only when the relevant policy is in force, but if
-        // policy has changed on the fly we still need to drain this before we
-        // settle into the legacy behavior.
-        if (!mAlarmQueue.isEmpty()) {
-            next = mAlarmQueue.remove(0);
-        }
-
-        // Next in precedence are deferred BOOT_COMPLETED broadcasts
-        if (next == null) {
-            next = dequeueDeferredBootCompletedBroadcast();
-        }
-
-        // Alarm-related deferrals are next in precedence...
-        if (next == null && !mAlarmDeferrals.isEmpty()) {
-            next = popLocked(mAlarmDeferrals);
-            if (DEBUG_BROADCAST_DEFERRAL && next != null) {
-                Slog.i(TAG, "Next broadcast from alarm targets: " + next);
-            }
-        }
-
-        final boolean someQueued = !mOrderedBroadcasts.isEmpty();
-
-        if (next == null && !mDeferredBroadcasts.isEmpty()) {
-            // A this point we're going to deliver either:
-            // 1. the next "overdue" deferral; or
-            // 2. the next ordinary ordered broadcast; *or*
-            // 3. the next not-yet-overdue deferral.
-
-            for (int i = 0; i < mDeferredBroadcasts.size(); i++) {
-                Deferrals d = mDeferredBroadcasts.get(i);
-                if (now < d.deferUntil && someQueued) {
-                    // stop looking when we haven't hit the next time-out boundary
-                    // but only if we have un-deferred broadcasts waiting,
-                    // otherwise we can deliver whatever deferred broadcast
-                    // is next available.
-                    break;
-                }
-
-                if (d.broadcasts.size() > 0) {
-                    next = d.broadcasts.remove(0);
-                    // apply deferral-interval decay policy and move this uid's
-                    // deferred broadcasts down in the delivery queue accordingly
-                    mDeferredBroadcasts.remove(i); // already 'd'
-                    d.deferredBy = calculateDeferral(d.deferredBy);
-                    d.deferUntil += d.deferredBy;
-                    insertLocked(mDeferredBroadcasts, d);
-                    if (DEBUG_BROADCAST_DEFERRAL) {
-                        Slog.i(TAG, "Next broadcast from deferrals " + next
-                                + ", deferUntil now " + d.deferUntil);
-                    }
-                    break;
-                }
-            }
-        }
-
-        if (next == null && someQueued) {
-            next = mOrderedBroadcasts.remove(0);
-            if (DEBUG_BROADCAST_DEFERRAL) {
-                Slog.i(TAG, "Next broadcast from main queue: " + next);
-            }
-        }
-
-        mCurrentBroadcast = next;
-        return next;
-    }
-
-    /**
-     * Called after the broadcast queue finishes processing the currently
-     * active broadcast (obtained by calling getNextBroadcastLocked()).
-     */
-    public void retireBroadcastLocked(final BroadcastRecord r) {
-        // ERROR if 'r' is not the active broadcast
-        if (r != mCurrentBroadcast) {
-            Slog.wtf(TAG, "Retiring broadcast " + r
-                    + " doesn't match current outgoing " + mCurrentBroadcast);
-        }
-        mCurrentBroadcast = null;
-    }
-
-    /**
-     * Called prior to broadcast dispatch to check whether the intended
-     * recipient is currently subject to deferral policy.
-     */
-    public boolean isDeferringLocked(final int uid) {
-        Deferrals d = findUidLocked(uid);
-        if (d != null && d.broadcasts.isEmpty()) {
-            // once we've caught up with deferred broadcasts to this uid
-            // and time has advanced sufficiently that we wouldn't be
-            // deferring newly-enqueued ones, we're back to normal policy.
-            if (SystemClock.uptimeMillis() >= d.deferUntil) {
-                if (DEBUG_BROADCAST_DEFERRAL) {
-                    Slog.i(TAG, "No longer deferring broadcasts to uid " + d.uid);
-                }
-                removeDeferral(d);
-                return false;
-            }
-        }
-        return (d != null);
-    }
-
-    /**
-     * Defer broadcasts for the given app.  If 'br' is non-null, this also makes
-     * sure that broadcast record is enqueued as the next upcoming broadcast for
-     * the app.
-     */
-    public void startDeferring(final int uid) {
-        synchronized (mLock) {
-            Deferrals d = findUidLocked(uid);
-
-            // If we're not yet tracking this app, set up that bookkeeping
-            if (d == null) {
-                // Start a new deferral
-                final long now = SystemClock.uptimeMillis();
-                d = new Deferrals(uid,
-                        now,
-                        mConstants.DEFERRAL,
-                        mAlarmUids.get(uid, 0));
-                if (DEBUG_BROADCAST_DEFERRAL) {
-                    Slog.i(TAG, "Now deferring broadcasts to " + uid
-                            + " until " + d.deferUntil);
-                }
-                // where it goes depends on whether it is coming into an alarm-related situation
-                if (d.alarmCount == 0) {
-                    // common case, put it in the ordinary priority queue
-                    insertLocked(mDeferredBroadcasts, d);
-                    scheduleDeferralCheckLocked(true);
-                } else {
-                    // alarm-related: strict order-encountered
-                    mAlarmDeferrals.add(d);
-                }
-            } else {
-                // We're already deferring, but something was slow again.  Reset the
-                // deferral decay progression.
-                d.deferredBy = mConstants.DEFERRAL;
-                if (DEBUG_BROADCAST_DEFERRAL) {
-                    Slog.i(TAG, "Uid " + uid + " slow again, deferral interval reset to "
-                            + d.deferredBy);
-                }
-            }
-        }
-    }
-
-    /**
-     * Key entry point when a broadcast about to be delivered is instead
-     * set aside for deferred delivery
-     */
-    public void addDeferredBroadcast(final int uid, BroadcastRecord br) {
-        if (DEBUG_BROADCAST_DEFERRAL) {
-            Slog.i(TAG, "Enqueuing deferred broadcast " + br);
-        }
-        synchronized (mLock) {
-            Deferrals d = findUidLocked(uid);
-            if (d == null) {
-                Slog.wtf(TAG, "Adding deferred broadcast but not tracking " + uid);
-            } else {
-                if (br == null) {
-                    Slog.wtf(TAG, "Deferring null broadcast to " + uid);
-                } else {
-                    br.deferred = true;
-                    d.add(br);
-                }
-            }
-        }
-    }
-
-    /**
-     * When there are deferred broadcasts, we need to make sure to recheck the
-     * dispatch queue when they come due.  Alarm-sensitive deferrals get dispatched
-     * aggressively, so we only need to use the ordinary deferrals timing to figure
-     * out when to recheck.
-     */
-    public void scheduleDeferralCheckLocked(boolean force) {
-        if ((force || !mRecheckScheduled) && !mDeferredBroadcasts.isEmpty()) {
-            final Deferrals d = mDeferredBroadcasts.get(0);
-            if (!d.broadcasts.isEmpty()) {
-                mHandler.removeCallbacks(mScheduleRunnable);
-                mHandler.postAtTime(mScheduleRunnable, d.deferUntil);
-                mRecheckScheduled = true;
-                if (DEBUG_BROADCAST_DEFERRAL) {
-                    Slog.i(TAG, "Scheduling deferred broadcast recheck at " + d.deferUntil);
-                }
-            }
-        }
-    }
-
-    /**
-     * Cancel all current deferrals; that is, make all currently-deferred broadcasts
-     * immediately deliverable.  Used by the wait-for-broadcast-idle mechanism.
-     */
-    public void cancelDeferralsLocked() {
-        zeroDeferralTimes(mAlarmDeferrals);
-        zeroDeferralTimes(mDeferredBroadcasts);
-    }
-
-    private static void zeroDeferralTimes(ArrayList<Deferrals> list) {
-        final int num = list.size();
-        for (int i = 0; i < num; i++) {
-            Deferrals d = list.get(i);
-            // Safe to do this in-place because it won't break ordering
-            d.deferUntil = d.deferredBy = 0;
-        }
-    }
-
-    // ----------------------------------
-
-    /**
-     * If broadcasts to this uid are being deferred, find the deferrals record about it.
-     * @return null if this uid's broadcasts are not being deferred
-     */
-    private Deferrals findUidLocked(final int uid) {
-        // The common case is that they it isn't also an alarm target...
-        Deferrals d = findUidLocked(uid, mDeferredBroadcasts);
-        // ...but if not there, also check alarm-prioritized deferrals
-        if (d == null) {
-            d = findUidLocked(uid, mAlarmDeferrals);
-        }
-        return d;
-    }
-
-    /**
-     * Remove the given deferral record from whichever queue it might be in at present
-     * @return true if the deferral was in fact found, false if this made no changes
-     */
-    private boolean removeDeferral(Deferrals d) {
-        boolean didRemove = mDeferredBroadcasts.remove(d);
-        if (!didRemove) {
-            didRemove = mAlarmDeferrals.remove(d);
-        }
-        return didRemove;
-    }
-
-    /**
-     * Find the deferrals record for the given uid in the given list
-     */
-    private static Deferrals findUidLocked(final int uid, ArrayList<Deferrals> list) {
-        final int numElements = list.size();
-        for (int i = 0; i < numElements; i++) {
-            Deferrals d = list.get(i);
-            if (uid == d.uid) {
-                return d;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Pop the next broadcast record from the head of the given deferrals list,
-     * if one exists.
-     */
-    private static BroadcastRecord popLocked(ArrayList<Deferrals> list) {
-        final Deferrals d = list.get(0);
-        return d.broadcasts.isEmpty() ? null : d.broadcasts.remove(0);
-    }
-
-    /**
-     * Insert the given Deferrals into the priority queue, sorted by defer-until milestone
-     */
-    private static void insertLocked(ArrayList<Deferrals> list, Deferrals d) {
-        // Simple linear search is appropriate here because we expect to
-        // have very few entries in the deferral lists (i.e. very few badly-
-        // behaving apps currently facing deferral)
-        int i;
-        final int numElements = list.size();
-        for (i = 0; i < numElements; i++) {
-            if (d.deferUntil < list.get(i).deferUntil) {
-                break;
-            }
-        }
-        list.add(i, d);
-    }
-
-    /**
-     * Calculate a new deferral time based on the previous time.  This should decay
-     * toward zero, though a small nonzero floor is an option.
-     */
-    private long calculateDeferral(long previous) {
-        return Math.max(mConstants.DEFERRAL_FLOOR,
-                (long) (previous * mConstants.DEFERRAL_DECAY_FACTOR));
-    }
-
-    // ----------------------------------
-
-    @NeverCompile
-    boolean dumpLocked(PrintWriter pw, String dumpPackage, String queueName,
-            SimpleDateFormat sdf) {
-        final Dumper dumper = new Dumper(pw, queueName, dumpPackage, sdf);
-        boolean printed = false;
-
-        dumper.setHeading("Currently in flight");
-        dumper.setLabel("In-Flight Ordered Broadcast");
-        if (mCurrentBroadcast != null) {
-            dumper.dump(mCurrentBroadcast);
-        } else {
-            pw.println("  (null)");
-        }
-        printed |= dumper.didPrint();
-
-        dumper.setHeading("Active alarm broadcasts");
-        dumper.setLabel("Active Alarm Broadcast");
-        for (BroadcastRecord br : mAlarmQueue) {
-            dumper.dump(br);
-        }
-        printed |= dumper.didPrint();
-
-        dumper.setHeading("Active ordered broadcasts");
-        dumper.setLabel("Active Ordered Broadcast");
-        for (Deferrals d : mAlarmDeferrals) {
-            d.dumpLocked(dumper);
-        }
-        for (BroadcastRecord br : mOrderedBroadcasts) {
-            dumper.dump(br);
-        }
-        printed |= dumper.didPrint();
-
-        dumper.setHeading("Deferred ordered broadcasts");
-        dumper.setLabel("Deferred Ordered Broadcast");
-        for (Deferrals d : mDeferredBroadcasts) {
-            d.dumpLocked(dumper);
-        }
-        printed |= dumper.didPrint();
-
-        dumper.setHeading("Deferred LOCKED_BOOT_COMPLETED broadcasts");
-        dumper.setLabel("Deferred LOCKED_BOOT_COMPLETED Broadcast");
-        for (int i = 0, size = mUser2Deferred.size(); i < size; i++) {
-            mUser2Deferred.valueAt(i).dump(dumper, Intent.ACTION_LOCKED_BOOT_COMPLETED);
-        }
-        printed |= dumper.didPrint();
-
-        dumper.setHeading("Deferred BOOT_COMPLETED broadcasts");
-        dumper.setLabel("Deferred BOOT_COMPLETED Broadcast");
-        for (int i = 0, size = mUser2Deferred.size(); i < size; i++) {
-            mUser2Deferred.valueAt(i).dump(dumper, Intent.ACTION_BOOT_COMPLETED);
-        }
-        printed |= dumper.didPrint();
-
-        return printed;
-    }
-}
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
deleted file mode 100644
index 3c56752..0000000
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ /dev/null
@@ -1,1983 +0,0 @@
-/*
- * Copyright (C) 2012 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 android.app.ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET;
-import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER;
-import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_APP_FREEZING_DELAYED;
-import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
-import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE;
-import static android.text.TextUtils.formatSimple;
-
-import static com.android.internal.util.FrameworkStatsLog.BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED;
-import static com.android.internal.util.FrameworkStatsLog.BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__BOOT_COMPLETED;
-import static com.android.internal.util.FrameworkStatsLog.BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__LOCKED_BOOT_COMPLETED;
-import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED;
-import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD;
-import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM;
-import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST;
-import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME;
-import static com.android.internal.util.FrameworkStatsLog.SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL;
-import static com.android.internal.util.FrameworkStatsLog.SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_STOPPED;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_DEFERRAL;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_LIGHT;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.Activity;
-import android.app.ActivityManager;
-import android.app.ApplicationExitInfo;
-import android.app.BroadcastOptions;
-import android.app.IApplicationThread;
-import android.app.usage.UsageEvents.Event;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.IIntentReceiver;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.UserInfo;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.PowerExemptionManager.ReasonCode;
-import android.os.PowerExemptionManager.TempAllowListType;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.os.Trace;
-import android.os.UserHandle;
-import android.text.TextUtils;
-import android.util.EventLog;
-import android.util.IndentingPrintWriter;
-import android.util.Slog;
-import android.util.SparseIntArray;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.internal.os.TimeoutRecord;
-import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.LocalServices;
-import com.android.server.pm.UserJourneyLogger;
-import com.android.server.pm.UserManagerInternal;
-
-import dalvik.annotation.optimization.NeverCompile;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Set;
-import java.util.function.BooleanSupplier;
-
-/**
- * BROADCASTS
- *
- * We keep three broadcast queues and associated bookkeeping, one for those at
- * foreground priority, and one for normal (background-priority) broadcasts, and one to
- * offload special broadcasts that we know take a long time, such as BOOT_COMPLETED.
- */
-public class BroadcastQueueImpl extends BroadcastQueue {
-    private static final String TAG_MU = TAG + POSTFIX_MU;
-    private static final String TAG_BROADCAST = TAG + POSTFIX_BROADCAST;
-
-    final BroadcastConstants mConstants;
-
-    /**
-     * If true, we can delay broadcasts while waiting services to finish in the previous
-     * receiver's process.
-     */
-    final boolean mDelayBehindServices;
-
-    final int mSchedGroup;
-
-    /**
-     * Lists of all active broadcasts that are to be executed immediately
-     * (without waiting for another broadcast to finish).  Currently this only
-     * contains broadcasts to registered receivers, to avoid spinning up
-     * a bunch of processes to execute IntentReceiver components.  Background-
-     * and foreground-priority broadcasts are queued separately.
-     */
-    final ArrayList<BroadcastRecord> mParallelBroadcasts = new ArrayList<>();
-
-    /**
-     * Tracking of the ordered broadcast queue, including deferral policy and alarm
-     * prioritization.
-     */
-    final BroadcastDispatcher mDispatcher;
-
-    /**
-     * Refcounting for completion callbacks of split/deferred broadcasts.  The key
-     * is an opaque integer token assigned lazily when a broadcast is first split
-     * into multiple BroadcastRecord objects.
-     */
-    final SparseIntArray mSplitRefcounts = new SparseIntArray();
-    private int mNextToken = 0;
-
-    /**
-     * Set when we current have a BROADCAST_INTENT_MSG in flight.
-     */
-    boolean mBroadcastsScheduled = false;
-
-    /**
-     * True if we have a pending unexpired BROADCAST_TIMEOUT_MSG posted to our handler.
-     */
-    boolean mPendingBroadcastTimeoutMessage;
-
-    /**
-     * Intent broadcasts that we have tried to start, but are
-     * waiting for the application's process to be created.  We only
-     * need one per scheduling class (instead of a list) because we always
-     * process broadcasts one at a time, so no others can be started while
-     * waiting for this one.
-     */
-    BroadcastRecord mPendingBroadcast = null;
-
-    /**
-     * The receiver index that is pending, to restart the broadcast if needed.
-     */
-    int mPendingBroadcastRecvIndex;
-
-    static final int BROADCAST_INTENT_MSG = ActivityManagerService.FIRST_BROADCAST_QUEUE_MSG;
-    static final int BROADCAST_TIMEOUT_MSG = ActivityManagerService.FIRST_BROADCAST_QUEUE_MSG + 1;
-
-    // log latency metrics for ordered broadcasts during BOOT_COMPLETED processing
-    boolean mLogLatencyMetrics = true;
-
-    final BroadcastHandler mHandler;
-
-    private final class BroadcastHandler extends Handler {
-        public BroadcastHandler(Looper looper) {
-            super(looper, null);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case BROADCAST_INTENT_MSG: {
-                    if (DEBUG_BROADCAST) Slog.v(
-                            TAG_BROADCAST, "Received BROADCAST_INTENT_MSG ["
-                            + mQueueName + "]");
-                    processNextBroadcast(true);
-                } break;
-                case BROADCAST_TIMEOUT_MSG: {
-                    synchronized (mService) {
-                        broadcastTimeoutLocked(true);
-                    }
-                } break;
-            }
-        }
-    }
-
-    BroadcastQueueImpl(ActivityManagerService service, Handler handler,
-            String name, BroadcastConstants constants, boolean allowDelayBehindServices,
-            int schedGroup) {
-        this(service, handler, name, constants, new BroadcastSkipPolicy(service),
-                new BroadcastHistory(constants), allowDelayBehindServices, schedGroup);
-    }
-
-    BroadcastQueueImpl(ActivityManagerService service, Handler handler,
-            String name, BroadcastConstants constants, BroadcastSkipPolicy skipPolicy,
-            BroadcastHistory history, boolean allowDelayBehindServices, int schedGroup) {
-        super(service, handler, name, skipPolicy, history);
-        mHandler = new BroadcastHandler(handler.getLooper());
-        mConstants = constants;
-        mDelayBehindServices = allowDelayBehindServices;
-        mSchedGroup = schedGroup;
-        mDispatcher = new BroadcastDispatcher(this, mConstants, mHandler, mService);
-    }
-
-    public void start(ContentResolver resolver) {
-        mDispatcher.start();
-        mConstants.startObserving(mHandler, resolver);
-    }
-
-    public boolean isDelayBehindServices() {
-        return mDelayBehindServices;
-    }
-
-    public BroadcastRecord getPendingBroadcastLocked() {
-        return mPendingBroadcast;
-    }
-
-    public BroadcastRecord getActiveBroadcastLocked() {
-        return mDispatcher.getActiveBroadcastLocked();
-    }
-
-    public int getPreferredSchedulingGroupLocked(ProcessRecord app) {
-        final BroadcastRecord active = getActiveBroadcastLocked();
-        if (active != null && active.curApp == app) {
-            return mSchedGroup;
-        }
-        final BroadcastRecord pending = getPendingBroadcastLocked();
-        if (pending != null && pending.curApp == app) {
-            return mSchedGroup;
-        }
-        return ProcessList.SCHED_GROUP_UNDEFINED;
-    }
-
-    public void enqueueBroadcastLocked(BroadcastRecord r) {
-        r.applySingletonPolicy(mService);
-
-        final boolean replacePending = (r.intent.getFlags()
-                & Intent.FLAG_RECEIVER_REPLACE_PENDING) != 0;
-
-        // Ordered broadcasts obviously need to be dispatched in serial order,
-        // but this implementation expects all manifest receivers to also be
-        // dispatched in a serial fashion
-        boolean serialDispatch = r.ordered;
-        if (!serialDispatch) {
-            final int N = (r.receivers != null) ? r.receivers.size() : 0;
-            for (int i = 0; i < N; i++) {
-                if (r.receivers.get(i) instanceof ResolveInfo) {
-                    serialDispatch = true;
-                    break;
-                }
-            }
-        }
-
-        if (serialDispatch) {
-            final BroadcastRecord oldRecord =
-                    replacePending ? replaceOrderedBroadcastLocked(r) : null;
-            if (oldRecord != null) {
-                // Replaced, fire the result-to receiver.
-                if (oldRecord.resultTo != null) {
-                    try {
-                        oldRecord.mIsReceiverAppRunning = true;
-                        performReceiveLocked(oldRecord, oldRecord.resultToApp, oldRecord.resultTo,
-                                oldRecord.intent,
-                                Activity.RESULT_CANCELED, null, null,
-                                false, false, oldRecord.shareIdentity, oldRecord.userId,
-                                oldRecord.callingUid, r.callingUid, r.callerPackage,
-                                SystemClock.uptimeMillis() - oldRecord.enqueueTime, 0, 0,
-                                oldRecord.resultToApp != null
-                                        ? oldRecord.resultToApp.mState.getCurProcState()
-                                        : ActivityManager.PROCESS_STATE_UNKNOWN);
-                    } catch (RemoteException e) {
-                        Slog.w(TAG, "Failure ["
-                                + mQueueName + "] sending broadcast result of "
-                                + oldRecord.intent, e);
-
-                    }
-                }
-            } else {
-                enqueueOrderedBroadcastLocked(r);
-                scheduleBroadcastsLocked();
-            }
-        } else {
-            final boolean replaced = replacePending
-                    && (replaceParallelBroadcastLocked(r) != null);
-            // Note: We assume resultTo is null for non-ordered broadcasts.
-            if (!replaced) {
-                enqueueParallelBroadcastLocked(r);
-                scheduleBroadcastsLocked();
-            }
-        }
-    }
-
-    public void enqueueParallelBroadcastLocked(BroadcastRecord r) {
-        r.enqueueClockTime = System.currentTimeMillis();
-        r.enqueueTime = SystemClock.uptimeMillis();
-        r.enqueueRealTime = SystemClock.elapsedRealtime();
-        mParallelBroadcasts.add(r);
-        enqueueBroadcastHelper(r);
-    }
-
-    public void enqueueOrderedBroadcastLocked(BroadcastRecord r) {
-        r.enqueueClockTime = System.currentTimeMillis();
-        r.enqueueTime = SystemClock.uptimeMillis();
-        r.enqueueRealTime = SystemClock.elapsedRealtime();
-        mDispatcher.enqueueOrderedBroadcastLocked(r);
-        enqueueBroadcastHelper(r);
-    }
-
-    /**
-     * Don't call this method directly; call enqueueParallelBroadcastLocked or
-     * enqueueOrderedBroadcastLocked.
-     */
-    private void enqueueBroadcastHelper(BroadcastRecord r) {
-        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
-            Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
-                createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_PENDING),
-                System.identityHashCode(r));
-        }
-    }
-
-    /**
-     * Find the same intent from queued parallel broadcast, replace with a new one and return
-     * the old one.
-     */
-    public final BroadcastRecord replaceParallelBroadcastLocked(BroadcastRecord r) {
-        return replaceBroadcastLocked(mParallelBroadcasts, r, "PARALLEL");
-    }
-
-    /**
-     * Find the same intent from queued ordered broadcast, replace with a new one and return
-     * the old one.
-     */
-    public final BroadcastRecord replaceOrderedBroadcastLocked(BroadcastRecord r) {
-        return mDispatcher.replaceBroadcastLocked(r, "ORDERED");
-    }
-
-    private BroadcastRecord replaceBroadcastLocked(ArrayList<BroadcastRecord> queue,
-            BroadcastRecord r, String typeForLogging) {
-        final Intent intent = r.intent;
-        for (int i = queue.size() - 1; i >= 0; i--) {
-            final BroadcastRecord old = queue.get(i);
-            if (old.userId == r.userId && intent.filterEquals(old.intent)) {
-                if (DEBUG_BROADCAST) {
-                    Slog.v(TAG_BROADCAST, "***** DROPPING "
-                            + typeForLogging + " [" + mQueueName + "]: " + intent);
-                }
-                queue.set(i, r);
-                return old;
-            }
-        }
-        return null;
-    }
-
-    private final void processCurBroadcastLocked(BroadcastRecord r,
-            ProcessRecord app) throws RemoteException {
-        if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
-                "Process cur broadcast " + r + " for app " + app);
-        final IApplicationThread thread = app.getThread();
-        if (thread == null) {
-            throw new RemoteException();
-        }
-        if (app.isInFullBackup()) {
-            skipReceiverLocked(r);
-            return;
-        }
-
-        r.curApp = app;
-        r.curAppLastProcessState = app.mState.getCurProcState();
-        final ProcessReceiverRecord prr = app.mReceivers;
-        prr.addCurReceiver(r);
-        app.mState.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_RECEIVER);
-        // Don't bump its LRU position if it's in the background restricted.
-        if (mService.mInternal.getRestrictionLevel(app.info.packageName, app.userId)
-                < RESTRICTION_LEVEL_RESTRICTED_BUCKET) {
-            mService.updateLruProcessLocked(app, false, null);
-        }
-        // Make sure the oom adj score is updated before delivering the broadcast.
-        // Force an update, even if there are other pending requests, overall it still saves time,
-        // because time(updateOomAdj(N apps)) <= N * time(updateOomAdj(1 app)).
-        mService.enqueueOomAdjTargetLocked(app);
-        mService.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_RECEIVER);
-
-        // Tell the application to launch this receiver.
-        maybeReportBroadcastDispatchedEventLocked(r, r.curReceiver.applicationInfo.uid);
-        r.intent.setComponent(r.curComponent);
-
-        // See if we need to delay the freezer based on BroadcastOptions
-        if (r.options != null
-                && r.options.getTemporaryAppAllowlistDuration() > 0
-                && r.options.getTemporaryAppAllowlistType()
-                    == TEMPORARY_ALLOW_LIST_TYPE_APP_FREEZING_DELAYED) {
-            mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(app,
-                    CachedAppOptimizer.UNFREEZE_REASON_START_RECEIVER,
-                    r.options.getTemporaryAppAllowlistDuration());
-        }
-
-        boolean started = false;
-        try {
-            if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,
-                    "Delivering to component " + r.curComponent
-                    + ": " + r);
-            mService.notifyPackageUse(r.intent.getComponent().getPackageName(),
-                                      PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER);
-            final boolean assumeDelivered = false;
-            thread.scheduleReceiver(
-                    prepareReceiverIntent(r.intent, r.curFilteredExtras),
-                    r.curReceiver, null /* compatInfo (unused but need to keep method signature) */,
-                    r.resultCode, r.resultData, r.resultExtras, r.ordered, assumeDelivered,
-                    r.userId, r.shareIdentity ? r.callingUid : Process.INVALID_UID,
-                    app.mState.getReportedProcState(),
-                    r.shareIdentity ? r.callerPackage : null);
-            if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
-                    "Process cur broadcast " + r + " DELIVERED for app " + app);
-            started = true;
-        } finally {
-            if (!started) {
-                if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
-                        "Process cur broadcast " + r + ": NOT STARTED!");
-                r.curApp = null;
-                r.curAppLastProcessState = ActivityManager.PROCESS_STATE_UNKNOWN;
-                prr.removeCurReceiver(r);
-            }
-        }
-
-        // if something bad happens here, launch the app and try again
-        if (app.isKilled()) {
-            throw new RemoteException("app gets killed during broadcasting");
-        }
-    }
-
-    /**
-     * Called by ActivityManagerService to notify that the uid has process started, if there is any
-     * deferred BOOT_COMPLETED broadcast, the BroadcastDispatcher can dispatch the broadcast now.
-     * @param uid
-     */
-    public void updateUidReadyForBootCompletedBroadcastLocked(int uid) {
-        mDispatcher.updateUidReadyForBootCompletedBroadcastLocked(uid);
-        scheduleBroadcastsLocked();
-    }
-
-    public boolean onApplicationAttachedLocked(ProcessRecord app)
-            throws BroadcastDeliveryFailedException {
-        updateUidReadyForBootCompletedBroadcastLocked(app.uid);
-
-        if (mPendingBroadcast != null && mPendingBroadcast.curApp == app) {
-            return sendPendingBroadcastsLocked(app);
-        } else {
-            return false;
-        }
-    }
-
-    public void onApplicationTimeoutLocked(ProcessRecord app) {
-        skipCurrentOrPendingReceiverLocked(app);
-    }
-
-    public void onApplicationProblemLocked(ProcessRecord app) {
-        skipCurrentOrPendingReceiverLocked(app);
-    }
-
-    public void onApplicationCleanupLocked(ProcessRecord app) {
-        skipCurrentOrPendingReceiverLocked(app);
-    }
-
-    public void onProcessFreezableChangedLocked(ProcessRecord app) {
-        // Not supported; ignore
-    }
-
-    public boolean sendPendingBroadcastsLocked(ProcessRecord app)
-            throws BroadcastDeliveryFailedException {
-        boolean didSomething = false;
-        final BroadcastRecord br = mPendingBroadcast;
-        if (br != null && br.curApp.getPid() > 0 && br.curApp.getPid() == app.getPid()) {
-            if (br.curApp != app) {
-                Slog.e(TAG, "App mismatch when sending pending broadcast to "
-                        + app.processName + ", intended target is " + br.curApp.processName);
-                return false;
-            }
-            try {
-                mPendingBroadcast = null;
-                br.mIsReceiverAppRunning = false;
-                processCurBroadcastLocked(br, app);
-                didSomething = true;
-            } catch (Exception e) {
-                Slog.w(TAG, "Exception in new application when starting receiver "
-                        + br.curComponent.flattenToShortString(), e);
-                logBroadcastReceiverDiscardLocked(br);
-                finishReceiverLocked(br, br.resultCode, br.resultData,
-                        br.resultExtras, br.resultAbort, false);
-                scheduleBroadcastsLocked();
-                // We need to reset the state if we failed to start the receiver.
-                br.state = BroadcastRecord.IDLE;
-                throw new BroadcastDeliveryFailedException(e);
-            }
-        }
-        return didSomething;
-    }
-
-    // Skip the current receiver, if any, that is in flight to the given process
-    public boolean skipCurrentOrPendingReceiverLocked(ProcessRecord app) {
-        BroadcastRecord r = null;
-        final BroadcastRecord curActive = mDispatcher.getActiveBroadcastLocked();
-        if (curActive != null && curActive.curApp == app) {
-            // confirmed: the current active broadcast is to the given app
-            r = curActive;
-        }
-
-        // If the current active broadcast isn't this BUT we're waiting for
-        // mPendingBroadcast to spin up the target app, that's what we use.
-        if (r == null && mPendingBroadcast != null && mPendingBroadcast.curApp == app) {
-            if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
-                    "[" + mQueueName + "] skip & discard pending app " + r);
-            r = mPendingBroadcast;
-        }
-
-        if (r != null) {
-            skipReceiverLocked(r);
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    private void skipReceiverLocked(BroadcastRecord r) {
-        logBroadcastReceiverDiscardLocked(r);
-        finishReceiverLocked(r, r.resultCode, r.resultData,
-                r.resultExtras, r.resultAbort, false);
-        scheduleBroadcastsLocked();
-    }
-
-    public void scheduleBroadcastsLocked() {
-        if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Schedule broadcasts ["
-                + mQueueName + "]: current="
-                + mBroadcastsScheduled);
-
-        if (mBroadcastsScheduled) {
-            return;
-        }
-        mHandler.sendMessage(mHandler.obtainMessage(BROADCAST_INTENT_MSG, this));
-        mBroadcastsScheduled = true;
-    }
-
-    public BroadcastRecord getMatchingOrderedReceiver(ProcessRecord app) {
-        BroadcastRecord br = mDispatcher.getActiveBroadcastLocked();
-        if (br == null) {
-            Slog.w(TAG_BROADCAST, "getMatchingOrderedReceiver [" + mQueueName
-                    + "] no active broadcast");
-            return null;
-        }
-        if (br.curApp != app) {
-            Slog.w(TAG_BROADCAST, "getMatchingOrderedReceiver [" + mQueueName
-                    + "] active broadcast " + br.curApp + " doesn't match " + app);
-            return null;
-        }
-        return br;
-    }
-
-    // > 0 only, no worry about "eventual" recycling
-    private int nextSplitTokenLocked() {
-        int next = mNextToken + 1;
-        if (next <= 0) {
-            next = 1;
-        }
-        mNextToken = next;
-        return next;
-    }
-
-    private void postActivityStartTokenRemoval(ProcessRecord app, BroadcastRecord r) {
-        // the receiver had run for less than allowed bg activity start timeout,
-        // so allow the process to still start activities from bg for some more time
-        String msgToken = (app.toShortString() + r.toString()).intern();
-        // first, if there exists a past scheduled request to remove this token, drop
-        // that request - we don't want the token to be swept from under our feet...
-        mHandler.removeCallbacksAndMessages(msgToken);
-        // ...then schedule the removal of the token after the extended timeout
-        mHandler.postAtTime(() -> {
-            synchronized (mService) {
-                app.removeBackgroundStartPrivileges(r);
-            }
-        }, msgToken, (r.receiverTime + mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT));
-    }
-
-    public boolean finishReceiverLocked(ProcessRecord app, int resultCode,
-            String resultData, Bundle resultExtras, boolean resultAbort, boolean waitForServices) {
-        final BroadcastRecord r = getMatchingOrderedReceiver(app);
-        if (r != null) {
-            return finishReceiverLocked(r, resultCode,
-                    resultData, resultExtras, resultAbort, waitForServices);
-        } else {
-            return false;
-        }
-    }
-
-    public boolean finishReceiverLocked(BroadcastRecord r, int resultCode,
-            String resultData, Bundle resultExtras, boolean resultAbort, boolean waitForServices) {
-        final int state = r.state;
-        final ActivityInfo receiver = r.curReceiver;
-        final long finishTime = SystemClock.uptimeMillis();
-        final long elapsed = finishTime - r.receiverTime;
-        r.state = BroadcastRecord.IDLE;
-        final int curIndex = r.nextReceiver - 1;
-
-        final int packageState = r.mWasReceiverAppStopped
-                ? SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_STOPPED
-                : SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL;
-
-        if (curIndex >= 0 && curIndex < r.receivers.size() && r.curApp != null) {
-            final Object curReceiver = r.receivers.get(curIndex);
-            FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED, r.curApp.uid,
-                    r.callingUid == -1 ? Process.SYSTEM_UID : r.callingUid,
-                    r.intent.getAction(),
-                    curReceiver instanceof BroadcastFilter
-                    ? BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME
-                    : BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST,
-                    r.mIsReceiverAppRunning
-                    ? BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM
-                    : BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD,
-                    r.dispatchTime - r.enqueueTime,
-                    r.receiverTime - r.dispatchTime,
-                    finishTime - r.receiverTime,
-                    packageState,
-                    r.curApp.info.packageName,
-                    r.callerPackage,
-                    r.calculateTypeForLogging(),
-                    r.getDeliveryGroupPolicy(),
-                    r.intent.getFlags(),
-                    BroadcastRecord.getReceiverPriority(curReceiver),
-                    r.callerProcState,
-                    r.curAppLastProcessState);
-        }
-        if (state == BroadcastRecord.IDLE) {
-            Slog.w(TAG_BROADCAST, "finishReceiver [" + mQueueName + "] called but state is IDLE");
-        }
-        if (r.mBackgroundStartPrivileges.allowsAny() && r.curApp != null) {
-            if (elapsed > mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT) {
-                // if the receiver has run for more than allowed bg activity start timeout,
-                // just remove the token for this process now and we're done
-                r.curApp.removeBackgroundStartPrivileges(r);
-            } else {
-                // It gets more time; post the removal to happen at the appropriate moment
-                postActivityStartTokenRemoval(r.curApp, r);
-            }
-        }
-        // If we're abandoning this broadcast before any receivers were actually spun up,
-        // nextReceiver is zero; in which case time-to-process bookkeeping doesn't apply.
-        if (r.nextReceiver > 0) {
-            r.terminalTime[r.nextReceiver - 1] = finishTime;
-        }
-
-        // if this receiver was slow, impose deferral policy on the app.  This will kick in
-        // when processNextBroadcastLocked() next finds this uid as a receiver identity.
-        if (!r.timeoutExempt) {
-            // r.curApp can be null if finish has raced with process death - benign
-            // edge case, and we just ignore it because we're already cleaning up
-            // as expected.
-            if (r.curApp != null
-                    && mConstants.SLOW_TIME > 0 && elapsed > mConstants.SLOW_TIME) {
-                // Core system packages are exempt from deferral policy
-                if (!UserHandle.isCore(r.curApp.uid)) {
-                    if (DEBUG_BROADCAST_DEFERRAL) {
-                        Slog.i(TAG_BROADCAST, "Broadcast receiver " + (r.nextReceiver - 1)
-                                + " was slow: " + receiver + " br=" + r);
-                    }
-                    mDispatcher.startDeferring(r.curApp.uid);
-                } else {
-                    if (DEBUG_BROADCAST_DEFERRAL) {
-                        Slog.i(TAG_BROADCAST, "Core uid " + r.curApp.uid
-                                + " receiver was slow but not deferring: "
-                                + receiver + " br=" + r);
-                    }
-                }
-            }
-        } else {
-            if (DEBUG_BROADCAST_DEFERRAL) {
-                Slog.i(TAG_BROADCAST, "Finished broadcast " + r.intent.getAction()
-                        + " is exempt from deferral policy");
-            }
-        }
-
-        r.intent.setComponent(null);
-        if (r.curApp != null && r.curApp.mReceivers.hasCurReceiver(r)) {
-            r.curApp.mReceivers.removeCurReceiver(r);
-            mService.enqueueOomAdjTargetLocked(r.curApp);
-        }
-        if (r.curFilter != null) {
-            r.curFilter.receiverList.curBroadcast = null;
-        }
-        r.curFilter = null;
-        r.curReceiver = null;
-        r.curApp = null;
-        r.curAppLastProcessState = ActivityManager.PROCESS_STATE_UNKNOWN;
-        r.curFilteredExtras = null;
-        r.mWasReceiverAppStopped = false;
-        mPendingBroadcast = null;
-
-        r.resultCode = resultCode;
-        r.resultData = resultData;
-        r.resultExtras = resultExtras;
-        if (resultAbort && (r.intent.getFlags()&Intent.FLAG_RECEIVER_NO_ABORT) == 0) {
-            r.resultAbort = resultAbort;
-        } else {
-            r.resultAbort = false;
-        }
-
-        // If we want to wait behind services *AND* we're finishing the head/
-        // active broadcast on its queue
-        if (waitForServices && r.curComponent != null && r.queue.isDelayBehindServices()
-                && ((BroadcastQueueImpl) r.queue).getActiveBroadcastLocked() == r) {
-            ActivityInfo nextReceiver;
-            if (r.nextReceiver < r.receivers.size()) {
-                Object obj = r.receivers.get(r.nextReceiver);
-                nextReceiver = (obj instanceof ActivityInfo) ? (ActivityInfo)obj : null;
-            } else {
-                nextReceiver = null;
-            }
-            // Don't do this if the next receive is in the same process as the current one.
-            if (receiver == null || nextReceiver == null
-                    || receiver.applicationInfo.uid != nextReceiver.applicationInfo.uid
-                    || !receiver.processName.equals(nextReceiver.processName)) {
-                // In this case, we are ready to process the next receiver for the current broadcast,
-                // but are on a queue that would like to wait for services to finish before moving
-                // on.  If there are background services currently starting, then we will go into a
-                // special state where we hold off on continuing this broadcast until they are done.
-                if (mService.mServices.hasBackgroundServicesLocked(r.userId)) {
-                    Slog.i(TAG, "Delay finish: " + r.curComponent.flattenToShortString());
-                    r.state = BroadcastRecord.WAITING_SERVICES;
-                    return false;
-                }
-            }
-        }
-
-        r.curComponent = null;
-
-        // We will process the next receiver right now if this is finishing
-        // an app receiver (which is always asynchronous) or after we have
-        // come back from calling a receiver.
-        final boolean doNext = (state == BroadcastRecord.APP_RECEIVE)
-                || (state == BroadcastRecord.CALL_DONE_RECEIVE);
-        if (doNext) {
-            processNextBroadcastLocked(/* fromMsg= */ false, /* skipOomAdj= */ true);
-        }
-        return doNext;
-    }
-
-    public void backgroundServicesFinishedLocked(int userId) {
-        BroadcastRecord br = mDispatcher.getActiveBroadcastLocked();
-        if (br != null) {
-            if (br.userId == userId && br.state == BroadcastRecord.WAITING_SERVICES) {
-                Slog.i(TAG, "Resuming delayed broadcast");
-                br.curComponent = null;
-                br.state = BroadcastRecord.IDLE;
-                processNextBroadcastLocked(false, false);
-            }
-        }
-    }
-
-    public void performReceiveLocked(BroadcastRecord r, ProcessRecord app, IIntentReceiver receiver,
-            Intent intent, int resultCode, String data, Bundle extras,
-            boolean ordered, boolean sticky, boolean shareIdentity, int sendingUser,
-            int receiverUid, int callingUid, String callingPackage,
-            long dispatchDelay, long receiveDelay, int priority,
-            int receiverProcessState) throws RemoteException {
-        // If the broadcaster opted-in to sharing their identity, then expose package visibility for
-        // the receiver.
-        if (shareIdentity) {
-            mService.mPackageManagerInt.grantImplicitAccess(sendingUser, intent,
-                    UserHandle.getAppId(receiverUid), callingUid, true);
-        }
-        // Send the intent to the receiver asynchronously using one-way binder calls.
-        if (app != null) {
-            final IApplicationThread thread = app.getThread();
-            if (thread != null) {
-                // If we have an app thread, do the call through that so it is
-                // correctly ordered with other one-way calls.
-                try {
-                    final boolean assumeDelivered = !ordered;
-                    thread.scheduleRegisteredReceiver(
-                            receiver, intent, resultCode,
-                            data, extras, ordered, sticky, assumeDelivered, sendingUser,
-                            app.mState.getReportedProcState(),
-                            shareIdentity ? callingUid : Process.INVALID_UID,
-                            shareIdentity ? callingPackage : null);
-                } catch (RemoteException ex) {
-                    // Failed to call into the process. It's either dying or wedged. Kill it gently.
-                    synchronized (mService) {
-                        final String msg = "Failed to schedule " + intent + " to " + receiver
-                                + " via " + app + ": " + ex;
-                        Slog.w(TAG, msg);
-                        app.killLocked("Can't deliver broadcast", ApplicationExitInfo.REASON_OTHER,
-                                ApplicationExitInfo.SUBREASON_UNDELIVERED_BROADCAST, true);
-                    }
-                    throw ex;
-                }
-            } else {
-                // Application has died. Receiver doesn't exist.
-                throw new RemoteException("app.thread must not be null");
-            }
-        } else {
-            receiver.performReceive(intent, resultCode, data, extras, ordered,
-                    sticky, sendingUser);
-        }
-        if (!ordered) {
-            FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED,
-                    receiverUid == -1 ? Process.SYSTEM_UID : receiverUid,
-                    callingUid == -1 ? Process.SYSTEM_UID : callingUid,
-                    intent.getAction(),
-                    BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME,
-                    BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM,
-                    dispatchDelay, receiveDelay, 0 /* finish_delay */,
-                    SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL,
-                    app != null ? app.info.packageName : null, callingPackage,
-                    r.calculateTypeForLogging(), r.getDeliveryGroupPolicy(), r.intent.getFlags(),
-                    priority, r.callerProcState, receiverProcessState);
-        }
-    }
-
-    private void deliverToRegisteredReceiverLocked(BroadcastRecord r,
-            BroadcastFilter filter, boolean ordered, int index) {
-        boolean skip = mSkipPolicy.shouldSkip(r, filter);
-
-        // Filter packages in the intent extras, skipping delivery if none of the packages is
-        // visible to the receiver.
-        Bundle filteredExtras = null;
-        if (!skip && r.filterExtrasForReceiver != null) {
-            final Bundle extras = r.intent.getExtras();
-            if (extras != null) {
-                filteredExtras = r.filterExtrasForReceiver.apply(filter.receiverList.uid, extras);
-                if (filteredExtras == null) {
-                    if (DEBUG_BROADCAST) {
-                        Slog.v(TAG, "Skipping delivery to "
-                                + filter.receiverList.app
-                                + " : receiver is filtered by the package visibility");
-                    }
-                    skip = true;
-                }
-            }
-        }
-
-        if (skip) {
-            r.delivery[index] = BroadcastRecord.DELIVERY_SKIPPED;
-            return;
-        }
-
-        r.delivery[index] = BroadcastRecord.DELIVERY_DELIVERED;
-
-        // If this is not being sent as an ordered broadcast, then we
-        // don't want to touch the fields that keep track of the current
-        // state of ordered broadcasts.
-        if (ordered) {
-            r.curFilter = filter;
-            filter.receiverList.curBroadcast = r;
-            r.state = BroadcastRecord.CALL_IN_RECEIVE;
-            if (filter.receiverList.app != null) {
-                // Bump hosting application to no longer be in background
-                // scheduling class.  Note that we can't do that if there
-                // isn't an app...  but we can only be in that case for
-                // things that directly call the IActivityManager API, which
-                // are already core system stuff so don't matter for this.
-                r.curApp = filter.receiverList.app;
-                r.curAppLastProcessState = r.curApp.mState.getCurProcState();
-                filter.receiverList.app.mReceivers.addCurReceiver(r);
-                mService.enqueueOomAdjTargetLocked(r.curApp);
-                mService.updateOomAdjPendingTargetsLocked(
-                        OOM_ADJ_REASON_START_RECEIVER);
-            }
-        } else if (filter.receiverList.app != null) {
-            mService.mOomAdjuster.unfreezeTemporarily(filter.receiverList.app,
-                    CachedAppOptimizer.UNFREEZE_REASON_START_RECEIVER);
-        }
-
-        try {
-            if (DEBUG_BROADCAST_LIGHT) Slog.i(TAG_BROADCAST,
-                    "Delivering to " + filter + " : " + r);
-            final boolean isInFullBackup = (filter.receiverList.app != null)
-                    && filter.receiverList.app.isInFullBackup();
-            final boolean isKilled = (filter.receiverList.app != null)
-                    && filter.receiverList.app.isKilled();
-            if (isInFullBackup || isKilled) {
-                // Skip delivery if full backup in progress
-                // If it's an ordered broadcast, we need to continue to the next receiver.
-                if (ordered) {
-                    skipReceiverLocked(r);
-                }
-            } else {
-                r.receiverTime = SystemClock.uptimeMillis();
-                r.scheduledTime[index] = r.receiverTime;
-                maybeAddBackgroundStartPrivileges(filter.receiverList.app, r);
-                maybeScheduleTempAllowlistLocked(filter.owningUid, r, r.options);
-                maybeReportBroadcastDispatchedEventLocked(r, filter.owningUid);
-                performReceiveLocked(r, filter.receiverList.app, filter.receiverList.receiver,
-                        prepareReceiverIntent(r.intent, filteredExtras), r.resultCode, r.resultData,
-                        r.resultExtras, r.ordered, r.initialSticky, r.shareIdentity, r.userId,
-                        filter.receiverList.uid, r.callingUid, r.callerPackage,
-                        r.dispatchTime - r.enqueueTime,
-                        r.receiverTime - r.dispatchTime, filter.getPriority(),
-                        filter.receiverList.app != null
-                                ? filter.receiverList.app.mState.getCurProcState()
-                                : ActivityManager.PROCESS_STATE_UNKNOWN);
-                // parallel broadcasts are fire-and-forget, not bookended by a call to
-                // finishReceiverLocked(), so we manage their activity-start token here
-                if (filter.receiverList.app != null
-                        && r.mBackgroundStartPrivileges.allowsAny()
-                        && !r.ordered) {
-                    postActivityStartTokenRemoval(filter.receiverList.app, r);
-                }
-            }
-            if (ordered) {
-                r.state = BroadcastRecord.CALL_DONE_RECEIVE;
-            }
-        } catch (RemoteException e) {
-            Slog.w(TAG, "Failure sending broadcast " + r.intent, e);
-            // Clean up ProcessRecord state related to this broadcast attempt
-            if (filter.receiverList.app != null) {
-                filter.receiverList.app.removeBackgroundStartPrivileges(r);
-                if (ordered) {
-                    filter.receiverList.app.mReceivers.removeCurReceiver(r);
-                    // Something wrong, its oom adj could be downgraded, but not in a hurry.
-                    mService.enqueueOomAdjTargetLocked(r.curApp);
-                }
-            }
-            // And BroadcastRecord state related to ordered delivery, if appropriate
-            if (ordered) {
-                r.curFilter = null;
-                filter.receiverList.curBroadcast = null;
-            }
-        }
-    }
-
-    void maybeScheduleTempAllowlistLocked(int uid, BroadcastRecord r,
-            @Nullable BroadcastOptions brOptions) {
-        if (brOptions == null || brOptions.getTemporaryAppAllowlistDuration() <= 0) {
-            return;
-        }
-        long duration = brOptions.getTemporaryAppAllowlistDuration();
-        final @TempAllowListType int type = brOptions.getTemporaryAppAllowlistType();
-        final @ReasonCode int reasonCode = brOptions.getTemporaryAppAllowlistReasonCode();
-        final String reason = brOptions.getTemporaryAppAllowlistReason();
-
-        if (duration > Integer.MAX_VALUE) {
-            duration = Integer.MAX_VALUE;
-        }
-        // XXX ideally we should pause the broadcast until everything behind this is done,
-        // or else we will likely start dispatching the broadcast before we have opened
-        // access to the app (there is a lot of asynchronicity behind this).  It is probably
-        // not that big a deal, however, because the main purpose here is to allow apps
-        // to hold wake locks, and they will be able to acquire their wake lock immediately
-        // it just won't be enabled until we get through this work.
-        StringBuilder b = new StringBuilder();
-        b.append("broadcast:");
-        UserHandle.formatUid(b, r.callingUid);
-        b.append(":");
-        if (r.intent.getAction() != null) {
-            b.append(r.intent.getAction());
-        } else if (r.intent.getComponent() != null) {
-            r.intent.getComponent().appendShortString(b);
-        } else if (r.intent.getData() != null) {
-            b.append(r.intent.getData());
-        }
-        b.append(",reason:");
-        b.append(reason);
-        if (DEBUG_BROADCAST) {
-            Slog.v(TAG, "Broadcast temp allowlist uid=" + uid + " duration=" + duration
-                    + " type=" + type + " : " + b.toString());
-        }
-
-        // Only add to temp allowlist if it's not the APP_FREEZING_DELAYED type. That will be
-        // handled when the broadcast is actually being scheduled on the app thread.
-        if (type != TEMPORARY_ALLOW_LIST_TYPE_APP_FREEZING_DELAYED) {
-            mService.tempAllowlistUidLocked(uid, duration, reasonCode, b.toString(), type,
-                    r.callingUid);
-        }
-    }
-
-    private void processNextBroadcast(boolean fromMsg) {
-        synchronized (mService) {
-            processNextBroadcastLocked(fromMsg, false);
-        }
-    }
-
-    private static Intent prepareReceiverIntent(@NonNull Intent originalIntent,
-            @Nullable Bundle filteredExtras) {
-        final Intent intent = new Intent(originalIntent);
-        if (filteredExtras != null) {
-            intent.replaceExtras(filteredExtras);
-        }
-        return intent;
-    }
-
-    public void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) {
-        BroadcastRecord r;
-
-        if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "processNextBroadcast ["
-                + mQueueName + "]: "
-                + mParallelBroadcasts.size() + " parallel broadcasts; "
-                + mDispatcher.describeStateLocked());
-
-        mService.updateCpuStats();
-
-        if (fromMsg) {
-            mBroadcastsScheduled = false;
-        }
-
-        // First, deliver any non-serialized broadcasts right away.
-        while (mParallelBroadcasts.size() > 0) {
-            r = mParallelBroadcasts.remove(0);
-            r.dispatchTime = SystemClock.uptimeMillis();
-            r.dispatchRealTime = SystemClock.elapsedRealtime();
-            r.dispatchClockTime = System.currentTimeMillis();
-            r.mIsReceiverAppRunning = true;
-
-            if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
-                Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
-                    createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_PENDING),
-                    System.identityHashCode(r));
-                Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
-                    createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_DELIVERED),
-                    System.identityHashCode(r));
-            }
-
-            final int N = r.receivers.size();
-            if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Processing parallel broadcast ["
-                    + mQueueName + "] " + r);
-            for (int i=0; i<N; i++) {
-                Object target = r.receivers.get(i);
-                if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
-                        "Delivering non-ordered on [" + mQueueName + "] to registered "
-                        + target + ": " + r);
-                deliverToRegisteredReceiverLocked(r,
-                        (BroadcastFilter) target, false, i);
-            }
-            addBroadcastToHistoryLocked(r);
-            if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Done with parallel broadcast ["
-                    + mQueueName + "] " + r);
-        }
-
-        // Now take care of the next serialized one...
-
-        // If we are waiting for a process to come up to handle the next
-        // broadcast, then do nothing at this point.  Just in case, we
-        // check that the process we're waiting for still exists.
-        if (mPendingBroadcast != null) {
-            if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,
-                    "processNextBroadcast [" + mQueueName + "]: waiting for "
-                    + mPendingBroadcast.curApp);
-
-            boolean isDead;
-            if (mPendingBroadcast.curApp.getPid() > 0) {
-                synchronized (mService.mPidsSelfLocked) {
-                    ProcessRecord proc = mService.mPidsSelfLocked.get(
-                            mPendingBroadcast.curApp.getPid());
-                    isDead = proc == null || proc.mErrorState.isCrashing();
-                }
-            } else {
-                final ProcessRecord proc = mService.mProcessList.getProcessNamesLOSP().get(
-                        mPendingBroadcast.curApp.processName, mPendingBroadcast.curApp.uid);
-                isDead = proc == null || !proc.isPendingStart();
-            }
-            if (!isDead) {
-                // It's still alive, so keep waiting
-                return;
-            } else {
-                Slog.w(TAG, "pending app  ["
-                        + mQueueName + "]" + mPendingBroadcast.curApp
-                        + " died before responding to broadcast");
-                mPendingBroadcast.state = BroadcastRecord.IDLE;
-                mPendingBroadcast.nextReceiver = mPendingBroadcastRecvIndex;
-                mPendingBroadcast = null;
-            }
-        }
-
-        boolean looped = false;
-
-        do {
-            final long now = SystemClock.uptimeMillis();
-            r = mDispatcher.getNextBroadcastLocked(now);
-
-            if (r == null) {
-                // No more broadcasts are deliverable right now, so all done!
-                mDispatcher.scheduleDeferralCheckLocked(false);
-                synchronized (mService.mAppProfiler.mProfilerLock) {
-                    mService.mAppProfiler.scheduleAppGcsLPf();
-                }
-                if (looped && !skipOomAdj) {
-                    // If we had finished the last ordered broadcast, then
-                    // make sure all processes have correct oom and sched
-                    // adjustments.
-                    mService.updateOomAdjPendingTargetsLocked(
-                            OOM_ADJ_REASON_START_RECEIVER);
-                }
-
-                // when we have no more ordered broadcast on this queue, stop logging
-                if (mService.mUserController.mBootCompleted && mLogLatencyMetrics) {
-                    mLogLatencyMetrics = false;
-                }
-
-                return;
-            }
-
-            boolean forceReceive = false;
-
-            // Ensure that even if something goes awry with the timeout
-            // detection, we catch "hung" broadcasts here, discard them,
-            // and continue to make progress.
-            //
-            // This is only done if the system is ready so that early-stage receivers
-            // don't get executed with timeouts; and of course other timeout-
-            // exempt broadcasts are ignored.
-            int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
-            if (mService.mProcessesReady && !r.timeoutExempt && r.dispatchTime > 0) {
-                if ((numReceivers > 0) &&
-                        (now > r.dispatchTime + (2 * mConstants.TIMEOUT * numReceivers))) {
-                    Slog.w(TAG, "Hung broadcast ["
-                            + mQueueName + "] discarded after timeout failure:"
-                            + " now=" + now
-                            + " dispatchTime=" + r.dispatchTime
-                            + " startTime=" + r.receiverTime
-                            + " intent=" + r.intent
-                            + " numReceivers=" + numReceivers
-                            + " nextReceiver=" + r.nextReceiver
-                            + " state=" + r.state);
-                    broadcastTimeoutLocked(false); // forcibly finish this broadcast
-                    forceReceive = true;
-                    r.state = BroadcastRecord.IDLE;
-                }
-            }
-
-            if (r.state != BroadcastRecord.IDLE) {
-                if (DEBUG_BROADCAST) Slog.d(TAG_BROADCAST,
-                        "processNextBroadcast("
-                        + mQueueName + ") called when not idle (state="
-                        + r.state + ")");
-                return;
-            }
-
-            // Is the current broadcast is done for any reason?
-            if (r.receivers == null || r.nextReceiver >= numReceivers
-                    || r.resultAbort || forceReceive) {
-                // Send the final result if requested
-                if (r.resultTo != null) {
-                    boolean sendResult = true;
-
-                    // if this was part of a split/deferral complex, update the refcount and only
-                    // send the completion when we clear all of them
-                    if (r.splitToken != 0) {
-                        int newCount = mSplitRefcounts.get(r.splitToken) - 1;
-                        if (newCount == 0) {
-                            // done!  clear out this record's bookkeeping and deliver
-                            if (DEBUG_BROADCAST_DEFERRAL) {
-                                Slog.i(TAG_BROADCAST,
-                                        "Sending broadcast completion for split token "
-                                        + r.splitToken + " : " + r.intent.getAction());
-                            }
-                            mSplitRefcounts.delete(r.splitToken);
-                        } else {
-                            // still have some split broadcast records in flight; update refcount
-                            // and hold off on the callback
-                            if (DEBUG_BROADCAST_DEFERRAL) {
-                                Slog.i(TAG_BROADCAST,
-                                        "Result refcount now " + newCount + " for split token "
-                                        + r.splitToken + " : " + r.intent.getAction()
-                                        + " - not sending completion yet");
-                            }
-                            sendResult = false;
-                            mSplitRefcounts.put(r.splitToken, newCount);
-                        }
-                    }
-                    if (sendResult) {
-                        if (r.callerApp != null) {
-                            mService.mOomAdjuster.unfreezeTemporarily(
-                                    r.callerApp,
-                                    CachedAppOptimizer.UNFREEZE_REASON_FINISH_RECEIVER);
-                        }
-                        try {
-                            if (DEBUG_BROADCAST) {
-                                Slog.i(TAG_BROADCAST, "Finishing broadcast [" + mQueueName + "] "
-                                        + r.intent.getAction() + " app=" + r.callerApp);
-                            }
-                            if (r.dispatchTime == 0) {
-                                // The dispatch time here could be 0, in case it's a parallel
-                                // broadcast but it has a result receiver. Set it to now.
-                                r.dispatchTime = now;
-                            }
-                            r.mIsReceiverAppRunning = true;
-                            performReceiveLocked(r, r.resultToApp, r.resultTo,
-                                    new Intent(r.intent), r.resultCode,
-                                    r.resultData, r.resultExtras, false, false, r.shareIdentity,
-                                    r.userId, r.callingUid, r.callingUid, r.callerPackage,
-                                    r.dispatchTime - r.enqueueTime,
-                                    now - r.dispatchTime, 0,
-                                    r.resultToApp != null
-                                            ? r.resultToApp.mState.getCurProcState()
-                                            : ActivityManager.PROCESS_STATE_UNKNOWN);
-                            logBootCompletedBroadcastCompletionLatencyIfPossible(r);
-                            // Set this to null so that the reference
-                            // (local and remote) isn't kept in the mBroadcastHistory.
-                            r.resultTo = null;
-                        } catch (RemoteException e) {
-                            r.resultTo = null;
-                            Slog.w(TAG, "Failure ["
-                                    + mQueueName + "] sending broadcast result of "
-                                    + r.intent, e);
-                        }
-                    }
-                }
-
-                if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Cancelling BROADCAST_TIMEOUT_MSG");
-                cancelBroadcastTimeoutLocked();
-
-                if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,
-                        "Finished with ordered broadcast " + r);
-
-                // ... and on to the next...
-                addBroadcastToHistoryLocked(r);
-                if (r.intent.getComponent() == null && r.intent.getPackage() == null
-                        && (r.intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
-                    // This was an implicit broadcast... let's record it for posterity.
-                    mService.addBroadcastStatLocked(r.intent.getAction(), r.callerPackage,
-                            r.manifestCount, r.manifestSkipCount, r.finishTime-r.dispatchTime);
-                }
-                mDispatcher.retireBroadcastLocked(r);
-                r = null;
-                looped = true;
-                continue;
-            }
-
-            // Check whether the next receiver is under deferral policy, and handle that
-            // accordingly.  If the current broadcast was already part of deferred-delivery
-            // tracking, we know that it must now be deliverable as-is without re-deferral.
-            if (!r.deferred) {
-                final int receiverUid = r.getReceiverUid(r.receivers.get(r.nextReceiver));
-                if (mDispatcher.isDeferringLocked(receiverUid)) {
-                    if (DEBUG_BROADCAST_DEFERRAL) {
-                        Slog.i(TAG_BROADCAST, "Next receiver in " + r + " uid " + receiverUid
-                                + " at " + r.nextReceiver + " is under deferral");
-                    }
-                    // If this is the only (remaining) receiver in the broadcast, "splitting"
-                    // doesn't make sense -- just defer it as-is and retire it as the
-                    // currently active outgoing broadcast.
-                    BroadcastRecord defer;
-                    if (r.nextReceiver + 1 == numReceivers) {
-                        if (DEBUG_BROADCAST_DEFERRAL) {
-                            Slog.i(TAG_BROADCAST, "Sole receiver of " + r
-                                    + " is under deferral; setting aside and proceeding");
-                        }
-                        defer = r;
-                        mDispatcher.retireBroadcastLocked(r);
-                    } else {
-                        // Nontrivial case; split out 'uid's receivers to a new broadcast record
-                        // and defer that, then loop and pick up continuing delivery of the current
-                        // record (now absent those receivers).
-
-                        // The split operation is guaranteed to match at least at 'nextReceiver'
-                        defer = r.splitRecipientsLocked(receiverUid, r.nextReceiver);
-                        if (DEBUG_BROADCAST_DEFERRAL) {
-                            Slog.i(TAG_BROADCAST, "Post split:");
-                            Slog.i(TAG_BROADCAST, "Original broadcast receivers:");
-                            for (int i = 0; i < r.receivers.size(); i++) {
-                                Slog.i(TAG_BROADCAST, "  " + r.receivers.get(i));
-                            }
-                            Slog.i(TAG_BROADCAST, "Split receivers:");
-                            for (int i = 0; i < defer.receivers.size(); i++) {
-                                Slog.i(TAG_BROADCAST, "  " + defer.receivers.get(i));
-                            }
-                        }
-                        // Track completion refcount as well if relevant
-                        if (r.resultTo != null) {
-                            int token = r.splitToken;
-                            if (token == 0) {
-                                // first split of this record; refcount for 'r' and 'deferred'
-                                r.splitToken = defer.splitToken = nextSplitTokenLocked();
-                                mSplitRefcounts.put(r.splitToken, 2);
-                                if (DEBUG_BROADCAST_DEFERRAL) {
-                                    Slog.i(TAG_BROADCAST,
-                                            "Broadcast needs split refcount; using new token "
-                                            + r.splitToken);
-                                }
-                            } else {
-                                // new split from an already-refcounted situation; increment count
-                                final int curCount = mSplitRefcounts.get(token);
-                                if (DEBUG_BROADCAST_DEFERRAL) {
-                                    if (curCount == 0) {
-                                        Slog.wtf(TAG_BROADCAST,
-                                                "Split refcount is zero with token for " + r);
-                                    }
-                                }
-                                mSplitRefcounts.put(token, curCount + 1);
-                                if (DEBUG_BROADCAST_DEFERRAL) {
-                                    Slog.i(TAG_BROADCAST, "New split count for token " + token
-                                            + " is " + (curCount + 1));
-                                }
-                            }
-                        }
-                    }
-                    mDispatcher.addDeferredBroadcast(receiverUid, defer);
-                    r = null;
-                    looped = true;
-                    continue;
-                }
-            }
-        } while (r == null);
-
-        // Get the next receiver...
-        int recIdx = r.nextReceiver++;
-
-        // Keep track of when this receiver started, and make sure there
-        // is a timeout message pending to kill it if need be.
-        r.receiverTime = SystemClock.uptimeMillis();
-        r.scheduledTime[recIdx] = r.receiverTime;
-        if (recIdx == 0) {
-            r.dispatchTime = r.receiverTime;
-            r.dispatchRealTime = SystemClock.elapsedRealtime();
-            r.dispatchClockTime = System.currentTimeMillis();
-
-            if (mLogLatencyMetrics) {
-                FrameworkStatsLog.write(
-                        FrameworkStatsLog.BROADCAST_DISPATCH_LATENCY_REPORTED,
-                        r.dispatchClockTime - r.enqueueClockTime);
-            }
-
-            if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
-                Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
-                    createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_PENDING),
-                    System.identityHashCode(r));
-                Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
-                    createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_DELIVERED),
-                    System.identityHashCode(r));
-            }
-            if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Processing ordered broadcast ["
-                    + mQueueName + "] " + r);
-        }
-        if (! mPendingBroadcastTimeoutMessage) {
-            long timeoutTime = r.receiverTime + mConstants.TIMEOUT;
-            if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
-                    "Submitting BROADCAST_TIMEOUT_MSG ["
-                    + mQueueName + "] for " + r + " at " + timeoutTime);
-            setBroadcastTimeoutLocked(timeoutTime);
-        }
-
-        final BroadcastOptions brOptions = r.options;
-        final Object nextReceiver = r.receivers.get(recIdx);
-
-        if (nextReceiver instanceof BroadcastFilter) {
-            // Simple case: this is a registered receiver who gets
-            // a direct call.
-            BroadcastFilter filter = (BroadcastFilter)nextReceiver;
-            if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
-                    "Delivering ordered ["
-                    + mQueueName + "] to registered "
-                    + filter + ": " + r);
-            r.mIsReceiverAppRunning = true;
-            deliverToRegisteredReceiverLocked(r, filter, r.ordered, recIdx);
-            if ((r.curReceiver == null && r.curFilter == null) || !r.ordered) {
-                // The receiver has already finished, so schedule to
-                // process the next one.
-                if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Quick finishing ["
-                        + mQueueName + "]: ordered=" + r.ordered
-                        + " curFilter=" + r.curFilter
-                        + " curReceiver=" + r.curReceiver);
-                r.state = BroadcastRecord.IDLE;
-                scheduleBroadcastsLocked();
-            } else {
-                if (filter.receiverList != null) {
-                    maybeAddBackgroundStartPrivileges(filter.receiverList.app, r);
-                    // r is guaranteed ordered at this point, so we know finishReceiverLocked()
-                    // will get a callback and handle the activity start token lifecycle.
-                }
-            }
-            return;
-        }
-
-        // Hard case: need to instantiate the receiver, possibly
-        // starting its application process to host it.
-
-        final ResolveInfo info =
-            (ResolveInfo)nextReceiver;
-        final ComponentName component = new ComponentName(
-                info.activityInfo.applicationInfo.packageName,
-                info.activityInfo.name);
-        final int receiverUid = info.activityInfo.applicationInfo.uid;
-
-        final String targetProcess = info.activityInfo.processName;
-        final ProcessRecord app = mService.getProcessRecordLocked(targetProcess,
-                info.activityInfo.applicationInfo.uid);
-
-        boolean skip = mSkipPolicy.shouldSkip(r, info);
-
-        // Filter packages in the intent extras, skipping delivery if none of the packages is
-        // visible to the receiver.
-        Bundle filteredExtras = null;
-        if (!skip && r.filterExtrasForReceiver != null) {
-            final Bundle extras = r.intent.getExtras();
-            if (extras != null) {
-                filteredExtras = r.filterExtrasForReceiver.apply(receiverUid, extras);
-                if (filteredExtras == null) {
-                    if (DEBUG_BROADCAST) {
-                        Slog.v(TAG, "Skipping delivery to "
-                                + info.activityInfo.packageName + " / " + receiverUid
-                                + " : receiver is filtered by the package visibility");
-                    }
-                    skip = true;
-                }
-            }
-        }
-
-        if (skip) {
-            if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
-                    "Skipping delivery of ordered [" + mQueueName + "] "
-                    + r + " for reason described above");
-            r.delivery[recIdx] = BroadcastRecord.DELIVERY_SKIPPED;
-            r.curFilter = null;
-            r.state = BroadcastRecord.IDLE;
-            r.manifestSkipCount++;
-            scheduleBroadcastsLocked();
-            return;
-        }
-        r.manifestCount++;
-
-        r.delivery[recIdx] = BroadcastRecord.DELIVERY_DELIVERED;
-        r.state = BroadcastRecord.APP_RECEIVE;
-        r.curComponent = component;
-        r.curReceiver = info.activityInfo;
-        r.curFilteredExtras = filteredExtras;
-        if (DEBUG_MU && r.callingUid > UserHandle.PER_USER_RANGE) {
-            Slog.v(TAG_MU, "Updated broadcast record activity info for secondary user, "
-                    + info.activityInfo + ", callingUid = " + r.callingUid + ", uid = "
-                    + receiverUid);
-        }
-        final boolean isActivityCapable =
-                (brOptions != null && brOptions.getTemporaryAppAllowlistDuration() > 0);
-        maybeScheduleTempAllowlistLocked(receiverUid, r, brOptions);
-
-        // Report that a component is used for explicit broadcasts.
-        if (r.intent.getComponent() != null && r.curComponent != null
-                && !TextUtils.equals(r.curComponent.getPackageName(), r.callerPackage)) {
-            mService.mUsageStatsService.reportEvent(
-                    r.curComponent.getPackageName(), r.userId, Event.APP_COMPONENT_USED);
-        }
-
-        try {
-            mService.mPackageManagerInt.notifyComponentUsed(
-                    r.curComponent.getPackageName(), r.userId, r.callerPackage, r.toString());
-        } catch (IllegalArgumentException e) {
-            Slog.w(TAG, "Failed trying to unstop package "
-                    + r.curComponent.getPackageName() + ": " + e);
-        }
-
-        // Is this receiver's application already running?
-        if (app != null && app.getThread() != null && !app.isKilled()) {
-            try {
-                app.addPackage(info.activityInfo.packageName,
-                        info.activityInfo.applicationInfo.longVersionCode, mService.mProcessStats);
-                maybeAddBackgroundStartPrivileges(app, r);
-                r.mIsReceiverAppRunning = true;
-                processCurBroadcastLocked(r, app);
-                return;
-            } catch (RemoteException e) {
-                final String msg = "Failed to schedule " + r.intent + " to " + info
-                        + " via " + app + ": " + e;
-                Slog.w(TAG, msg);
-                app.killLocked("Can't deliver broadcast", ApplicationExitInfo.REASON_OTHER,
-                        ApplicationExitInfo.SUBREASON_UNDELIVERED_BROADCAST, true);
-            } catch (RuntimeException e) {
-                Slog.wtf(TAG, "Failed sending broadcast to "
-                        + r.curComponent + " with " + r.intent, e);
-                // If some unexpected exception happened, just skip
-                // this broadcast.  At this point we are not in the call
-                // from a client, so throwing an exception out from here
-                // will crash the entire system instead of just whoever
-                // sent the broadcast.
-                logBroadcastReceiverDiscardLocked(r);
-                finishReceiverLocked(r, r.resultCode, r.resultData,
-                        r.resultExtras, r.resultAbort, false);
-                scheduleBroadcastsLocked();
-                // We need to reset the state if we failed to start the receiver.
-                r.state = BroadcastRecord.IDLE;
-                return;
-            }
-
-            // If a dead object exception was thrown -- fall through to
-            // restart the application.
-        }
-
-        // Registered whether we're bringing this package out of a stopped state
-        r.mWasReceiverAppStopped =
-                (info.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0;
-        // Not running -- get it started, to be executed when the app comes up.
-        if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
-                "Need to start app ["
-                + mQueueName + "] " + targetProcess + " for broadcast " + r);
-        r.curApp = mService.startProcessLocked(targetProcess,
-                info.activityInfo.applicationInfo, true,
-                r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND,
-                new HostingRecord(HostingRecord.HOSTING_TYPE_BROADCAST, r.curComponent,
-                        r.intent.getAction(), r.getHostingRecordTriggerType()),
-                isActivityCapable ? ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE : ZYGOTE_POLICY_FLAG_EMPTY,
-                (r.intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false);
-        r.curAppLastProcessState = ActivityManager.PROCESS_STATE_NONEXISTENT;
-        if (r.curApp == null) {
-            // Ah, this recipient is unavailable.  Finish it if necessary,
-            // and mark the broadcast record as ready for the next.
-            Slog.w(TAG, "Unable to launch app "
-                    + info.activityInfo.applicationInfo.packageName + "/"
-                    + receiverUid + " for broadcast "
-                    + r.intent + ": process is bad");
-            logBroadcastReceiverDiscardLocked(r);
-            finishReceiverLocked(r, r.resultCode, r.resultData,
-                    r.resultExtras, r.resultAbort, false);
-            scheduleBroadcastsLocked();
-            r.state = BroadcastRecord.IDLE;
-            return;
-        }
-
-        maybeAddBackgroundStartPrivileges(r.curApp, r);
-        mPendingBroadcast = r;
-        mPendingBroadcastRecvIndex = recIdx;
-    }
-
-    @Nullable
-    private String getTargetPackage(BroadcastRecord r) {
-        if (r.intent == null) {
-            return null;
-        }
-        if (r.intent.getPackage() != null) {
-            return r.intent.getPackage();
-        } else if (r.intent.getComponent() != null) {
-            return r.intent.getComponent().getPackageName();
-        }
-        return null;
-    }
-
-    static void logBootCompletedBroadcastCompletionLatencyIfPossible(BroadcastRecord r) {
-        // Only log after last receiver.
-        // In case of split BOOT_COMPLETED broadcast, make sure only call this method on the
-        // last BroadcastRecord of the split broadcast which has non-null resultTo.
-        final int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
-        if (r.nextReceiver < numReceivers) {
-            return;
-        }
-        final String action = r.intent.getAction();
-        int event = 0;
-        if (Intent.ACTION_LOCKED_BOOT_COMPLETED.equals(action)) {
-            event = BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__LOCKED_BOOT_COMPLETED;
-        } else if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
-            event = BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__BOOT_COMPLETED;
-        }
-        if (event != 0) {
-            final int dispatchLatency = (int)(r.dispatchTime - r.enqueueTime);
-            final int completeLatency = (int)
-                    (SystemClock.uptimeMillis() - r.enqueueTime);
-            final int dispatchRealLatency = (int)(r.dispatchRealTime - r.enqueueRealTime);
-            final int completeRealLatency = (int)
-                    (SystemClock.elapsedRealtime() - r.enqueueRealTime);
-            int userType = FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__TYPE_UNKNOWN;
-            // This method is called very infrequently, no performance issue we call
-            // LocalServices.getService() here.
-            final UserManagerInternal umInternal = LocalServices.getService(
-                    UserManagerInternal.class);
-            final UserInfo userInfo =
-                    (umInternal != null) ? umInternal.getUserInfo(r.userId) : null;
-            if (userInfo != null) {
-                userType = UserJourneyLogger.getUserTypeForStatsd(userInfo.userType);
-            }
-            Slog.i(TAG_BROADCAST,
-                    "BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED action:"
-                            + action
-                            + " dispatchLatency:" + dispatchLatency
-                            + " completeLatency:" + completeLatency
-                            + " dispatchRealLatency:" + dispatchRealLatency
-                            + " completeRealLatency:" + completeRealLatency
-                            + " receiversSize:" + numReceivers
-                            + " userId:" + r.userId
-                            + " userType:" + (userInfo != null? userInfo.userType : null));
-            FrameworkStatsLog.write(
-                    BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED,
-                    event,
-                    dispatchLatency,
-                    completeLatency,
-                    dispatchRealLatency,
-                    completeRealLatency,
-                    r.userId,
-                    userType);
-        }
-    }
-
-    private void maybeReportBroadcastDispatchedEventLocked(BroadcastRecord r, int targetUid) {
-        if (r.options == null || r.options.getIdForResponseEvent() <= 0) {
-            return;
-        }
-        final String targetPackage = getTargetPackage(r);
-        // Ignore non-explicit broadcasts
-        if (targetPackage == null) {
-            return;
-        }
-        mService.mUsageStatsService.reportBroadcastDispatched(
-                r.callingUid, targetPackage, UserHandle.of(r.userId),
-                r.options.getIdForResponseEvent(), SystemClock.elapsedRealtime(),
-                mService.getUidStateLocked(targetUid));
-    }
-
-    private void maybeAddBackgroundStartPrivileges(ProcessRecord proc, BroadcastRecord r) {
-        if (r == null || proc == null || !r.mBackgroundStartPrivileges.allowsAny()) {
-            return;
-        }
-        String msgToken = (proc.toShortString() + r.toString()).intern();
-        // first, if there exists a past scheduled request to remove this token, drop
-        // that request - we don't want the token to be swept from under our feet...
-        mHandler.removeCallbacksAndMessages(msgToken);
-        // ...then add the token
-        proc.addOrUpdateBackgroundStartPrivileges(r, r.mBackgroundStartPrivileges);
-    }
-
-    final void setBroadcastTimeoutLocked(long timeoutTime) {
-        if (! mPendingBroadcastTimeoutMessage) {
-            Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, this);
-            mHandler.sendMessageAtTime(msg, timeoutTime);
-            mPendingBroadcastTimeoutMessage = true;
-        }
-    }
-
-    final void cancelBroadcastTimeoutLocked() {
-        if (mPendingBroadcastTimeoutMessage) {
-            mHandler.removeMessages(BROADCAST_TIMEOUT_MSG, this);
-            mPendingBroadcastTimeoutMessage = false;
-        }
-    }
-
-    final void broadcastTimeoutLocked(boolean fromMsg) {
-        if (fromMsg) {
-            mPendingBroadcastTimeoutMessage = false;
-        }
-
-        if (mDispatcher.isEmpty() || mDispatcher.getActiveBroadcastLocked() == null) {
-            return;
-        }
-        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastTimeoutLocked()");
-        try {
-            long now = SystemClock.uptimeMillis();
-            BroadcastRecord r = mDispatcher.getActiveBroadcastLocked();
-            if (fromMsg) {
-                if (!mService.mProcessesReady) {
-                    // Only process broadcast timeouts if the system is ready; some early
-                    // broadcasts do heavy work setting up system facilities
-                    return;
-                }
-
-                // If the broadcast is generally exempt from timeout tracking, we're done
-                if (r.timeoutExempt) {
-                    if (DEBUG_BROADCAST) {
-                        Slog.i(TAG_BROADCAST, "Broadcast timeout but it's exempt: "
-                                + r.intent.getAction());
-                    }
-                    return;
-                }
-                long timeoutTime = r.receiverTime + mConstants.TIMEOUT;
-                if (timeoutTime > now) {
-                    // We can observe premature timeouts because we do not cancel and reset the
-                    // broadcast timeout message after each receiver finishes.  Instead, we set up
-                    // an initial timeout then kick it down the road a little further as needed
-                    // when it expires.
-                    if (DEBUG_BROADCAST) {
-                        Slog.v(TAG_BROADCAST,
-                                "Premature timeout ["
-                                + mQueueName + "] @ " + now
-                                + ": resetting BROADCAST_TIMEOUT_MSG for "
-                                + timeoutTime);
-                    }
-                    setBroadcastTimeoutLocked(timeoutTime);
-                    return;
-                }
-            }
-
-            if (r.state == BroadcastRecord.WAITING_SERVICES) {
-                // In this case the broadcast had already finished, but we had decided to wait
-                // for started services to finish as well before going on.  So if we have actually
-                // waited long enough time timeout the broadcast, let's give up on the whole thing
-                // and just move on to the next.
-                Slog.i(TAG, "Waited long enough for: " + (r.curComponent != null
-                        ? r.curComponent.flattenToShortString() : "(null)"));
-                r.curComponent = null;
-                r.state = BroadcastRecord.IDLE;
-                processNextBroadcastLocked(false, false);
-                return;
-            }
-
-            // If the receiver app is being debugged we quietly ignore unresponsiveness, just
-            // tidying up and moving on to the next broadcast without crashing or ANRing this
-            // app just because it's stopped at a breakpoint.
-            final boolean debugging = (r.curApp != null && r.curApp.isDebugging());
-
-            long timeoutDurationMs = now - r.receiverTime;
-            Slog.w(TAG, "Timeout of broadcast " + r + " - curFilter=" + r.curFilter
-                    + " curReceiver=" + r.curReceiver + ", started " + timeoutDurationMs
-                    + "ms ago");
-            r.receiverTime = now;
-            if (!debugging) {
-                r.anrCount++;
-            }
-
-            ProcessRecord app = null;
-            Object curReceiver;
-            if (r.nextReceiver > 0) {
-                curReceiver = r.receivers.get(r.nextReceiver - 1);
-                r.delivery[r.nextReceiver - 1] = BroadcastRecord.DELIVERY_TIMEOUT;
-            } else {
-                curReceiver = r.curReceiver;
-            }
-            Slog.w(TAG, "Receiver during timeout of " + r + " : " + curReceiver);
-            logBroadcastReceiverDiscardLocked(r);
-            TimeoutRecord timeoutRecord = TimeoutRecord.forBroadcastReceiver(r.intent,
-                    timeoutDurationMs);
-            if (curReceiver != null && curReceiver instanceof BroadcastFilter) {
-                BroadcastFilter bf = (BroadcastFilter) curReceiver;
-                if (bf.receiverList.pid != 0
-                        && bf.receiverList.pid != ActivityManagerService.MY_PID) {
-                    timeoutRecord.mLatencyTracker.waitingOnPidLockStarted();
-                    synchronized (mService.mPidsSelfLocked) {
-                        timeoutRecord.mLatencyTracker.waitingOnPidLockEnded();
-                        app = mService.mPidsSelfLocked.get(
-                                bf.receiverList.pid);
-                    }
-                }
-            } else {
-                app = r.curApp;
-            }
-
-            if (mPendingBroadcast == r) {
-                mPendingBroadcast = null;
-            }
-
-            // Move on to the next receiver.
-            finishReceiverLocked(r, r.resultCode, r.resultData,
-                    r.resultExtras, r.resultAbort, false);
-            scheduleBroadcastsLocked();
-
-            // The ANR should only be triggered if we have a process record (app is non-null)
-            if (!debugging && app != null) {
-                mService.appNotResponding(app, timeoutRecord);
-            }
-
-        } finally {
-            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-        }
-
-    }
-
-    private final void addBroadcastToHistoryLocked(BroadcastRecord original) {
-        if (original.callingUid < 0) {
-            // This was from a registerReceiver() call; ignore it.
-            return;
-        }
-        original.finishTime = SystemClock.uptimeMillis();
-
-        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
-            Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
-                createBroadcastTraceTitle(original, BroadcastRecord.DELIVERY_DELIVERED),
-                System.identityHashCode(original));
-        }
-
-        mService.notifyBroadcastFinishedLocked(original);
-        mHistory.addBroadcastToHistoryLocked(original);
-    }
-
-    public boolean cleanupDisabledPackageReceiversLocked(
-            String packageName, Set<String> filterByClasses, int userId) {
-        boolean didSomething = false;
-        for (int i = mParallelBroadcasts.size() - 1; i >= 0; i--) {
-            didSomething |= mParallelBroadcasts.get(i).cleanupDisabledPackageReceiversLocked(
-                    packageName, filterByClasses, userId, true);
-        }
-
-        didSomething |= mDispatcher.cleanupDisabledPackageReceiversLocked(packageName,
-                filterByClasses, userId, true);
-
-        return didSomething;
-    }
-
-    final void logBroadcastReceiverDiscardLocked(BroadcastRecord r) {
-        final int logIndex = r.nextReceiver - 1;
-        if (logIndex >= 0 && logIndex < r.receivers.size()) {
-            Object curReceiver = r.receivers.get(logIndex);
-            if (curReceiver instanceof BroadcastFilter) {
-                BroadcastFilter bf = (BroadcastFilter) curReceiver;
-                EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_FILTER,
-                        bf.owningUserId, System.identityHashCode(r),
-                        r.intent.getAction(), logIndex, System.identityHashCode(bf));
-            } else {
-                ResolveInfo ri = (ResolveInfo) curReceiver;
-                EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_APP,
-                        UserHandle.getUserId(ri.activityInfo.applicationInfo.uid),
-                        System.identityHashCode(r), r.intent.getAction(), logIndex, ri.toString());
-            }
-        } else {
-            if (logIndex < 0) Slog.w(TAG,
-                    "Discarding broadcast before first receiver is invoked: " + r);
-            EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_APP,
-                    -1, System.identityHashCode(r),
-                    r.intent.getAction(),
-                    r.nextReceiver,
-                    "NONE");
-        }
-    }
-
-    private String createBroadcastTraceTitle(BroadcastRecord record, int state) {
-        return formatSimple("Broadcast %s from %s (%s) %s",
-                state == BroadcastRecord.DELIVERY_PENDING ? "in queue" : "dispatched",
-                record.callerPackage == null ? "" : record.callerPackage,
-                record.callerApp == null ? "process unknown" : record.callerApp.toShortString(),
-                record.intent == null ? "" : record.intent.getAction());
-    }
-
-    public boolean isIdleLocked() {
-        return mParallelBroadcasts.isEmpty() && mDispatcher.isIdle()
-                && (mPendingBroadcast == null);
-    }
-
-    public boolean isBeyondBarrierLocked(long barrierTime) {
-        // If nothing active, we're beyond barrier
-        if (isIdleLocked()) return true;
-
-        // Check if parallel broadcasts are beyond barrier
-        for (int i = 0; i < mParallelBroadcasts.size(); i++) {
-            if (mParallelBroadcasts.get(i).enqueueTime <= barrierTime) {
-                return false;
-            }
-        }
-
-        // Check if pending broadcast is beyond barrier
-        final BroadcastRecord pending = getPendingBroadcastLocked();
-        if ((pending != null) && pending.enqueueTime <= barrierTime) {
-            return false;
-        }
-
-        return mDispatcher.isBeyondBarrier(barrierTime);
-    }
-
-    public boolean isDispatchedLocked(Intent intent) {
-        if (isIdleLocked()) return true;
-
-        for (int i = 0; i < mParallelBroadcasts.size(); i++) {
-            if (intent.filterEquals(mParallelBroadcasts.get(i).intent)) {
-                return false;
-            }
-        }
-
-        final BroadcastRecord pending = getPendingBroadcastLocked();
-        if ((pending != null) && intent.filterEquals(pending.intent)) {
-            return false;
-        }
-
-        return mDispatcher.isDispatched(intent);
-    }
-
-    public void waitForIdle(PrintWriter pw) {
-        waitFor(() -> isIdleLocked(), pw, "idle");
-    }
-
-    public void waitForBarrier(PrintWriter pw) {
-        final long barrierTime = SystemClock.uptimeMillis();
-        waitFor(() -> isBeyondBarrierLocked(barrierTime), pw, "barrier");
-    }
-
-    public void waitForDispatched(Intent intent, PrintWriter pw) {
-        waitFor(() -> isDispatchedLocked(intent), pw, "dispatch");
-    }
-
-    private void waitFor(BooleanSupplier condition, PrintWriter pw, String conditionName) {
-        long lastPrint = 0;
-        while (true) {
-            synchronized (mService) {
-                if (condition.getAsBoolean()) {
-                    final String msg = "Queue [" + mQueueName + "] reached " + conditionName
-                            + " condition";
-                    Slog.v(TAG, msg);
-                    if (pw != null) {
-                        pw.println(msg);
-                        pw.flush();
-                    }
-                    return;
-                }
-            }
-
-            // Print at most every second
-            final long now = SystemClock.uptimeMillis();
-            if (now >= lastPrint + 1000) {
-                lastPrint = now;
-                final String msg = "Queue [" + mQueueName + "] waiting for " + conditionName
-                        + " condition; state is " + describeStateLocked();
-                Slog.v(TAG, msg);
-                if (pw != null) {
-                    pw.println(msg);
-                    pw.flush();
-                }
-            }
-
-            // Push through any deferrals to try meeting our condition
-            cancelDeferrals();
-            SystemClock.sleep(100);
-        }
-    }
-
-    // Used by wait-for-broadcast-idle : fast-forward all current deferrals to
-    // be immediately deliverable.
-    public void cancelDeferrals() {
-        synchronized (mService) {
-            mDispatcher.cancelDeferralsLocked();
-            scheduleBroadcastsLocked();
-        }
-    }
-
-    public String describeStateLocked() {
-        return mParallelBroadcasts.size() + " parallel; "
-                + mDispatcher.describeStateLocked();
-    }
-
-    @NeverCompile
-    public void dumpDebug(ProtoOutputStream proto, long fieldId) {
-        long token = proto.start(fieldId);
-        proto.write(BroadcastQueueProto.QUEUE_NAME, mQueueName);
-        int N;
-        N = mParallelBroadcasts.size();
-        for (int i = N - 1; i >= 0; i--) {
-            mParallelBroadcasts.get(i).dumpDebug(proto, BroadcastQueueProto.PARALLEL_BROADCASTS);
-        }
-        mDispatcher.dumpDebug(proto, BroadcastQueueProto.ORDERED_BROADCASTS);
-        if (mPendingBroadcast != null) {
-            mPendingBroadcast.dumpDebug(proto, BroadcastQueueProto.PENDING_BROADCAST);
-        }
-        mHistory.dumpDebug(proto);
-        proto.end(token);
-    }
-
-    @NeverCompile
-    public boolean dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args,
-            int opti, boolean dumpConstants, boolean dumpHistory, boolean dumpAll,
-            String dumpPackage, boolean needSep) {
-        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
-        if (!mParallelBroadcasts.isEmpty() || !mDispatcher.isEmpty()
-                || mPendingBroadcast != null) {
-            boolean printed = false;
-            for (int i = mParallelBroadcasts.size() - 1; i >= 0; i--) {
-                BroadcastRecord br = mParallelBroadcasts.get(i);
-                if (dumpPackage != null && !dumpPackage.equals(br.callerPackage)) {
-                    continue;
-                }
-                if (!printed) {
-                    if (needSep) {
-                        pw.println();
-                    }
-                    needSep = true;
-                    printed = true;
-                    pw.println("  Active broadcasts [" + mQueueName + "]:");
-                }
-                pw.println("  Active Broadcast " + mQueueName + " #" + i + ":");
-                br.dump(pw, "    ", sdf);
-            }
-
-            mDispatcher.dumpLocked(pw, dumpPackage, mQueueName, sdf);
-
-            if (dumpPackage == null || (mPendingBroadcast != null
-                    && dumpPackage.equals(mPendingBroadcast.callerPackage))) {
-                pw.println();
-                pw.println("  Pending broadcast [" + mQueueName + "]:");
-                if (mPendingBroadcast != null) {
-                    mPendingBroadcast.dump(pw, "    ", sdf);
-                } else {
-                    pw.println("    (null)");
-                }
-                needSep = true;
-            }
-        }
-        if (dumpConstants) {
-            mConstants.dump(new IndentingPrintWriter(pw));
-        }
-        if (dumpHistory) {
-            needSep = mHistory.dumpLocked(pw, dumpPackage, mQueueName, sdf, dumpAll, needSep);
-        }
-        return needSep;
-    }
-}
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 98263df..4422608 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -20,6 +20,9 @@
 import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
 import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE;
 
+import static com.android.internal.util.FrameworkStatsLog.BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED;
+import static com.android.internal.util.FrameworkStatsLog.BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__BOOT_COMPLETED;
+import static com.android.internal.util.FrameworkStatsLog.BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__LOCKED_BOOT_COMPLETED;
 import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED;
 import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD;
 import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_UNKNOWN;
@@ -59,6 +62,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.content.pm.UserInfo;
 import android.os.Bundle;
 import android.os.BundleMerger;
 import android.os.Handler;
@@ -85,9 +89,12 @@
 import com.android.internal.os.SomeArgs;
 import com.android.internal.os.TimeoutRecord;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.LocalServices;
 import com.android.server.am.BroadcastProcessQueue.BroadcastConsumer;
 import com.android.server.am.BroadcastProcessQueue.BroadcastPredicate;
 import com.android.server.am.BroadcastRecord.DeliveryState;
+import com.android.server.pm.UserJourneyLogger;
+import com.android.server.pm.UserManagerInternal;
 import com.android.server.utils.AnrTimer;
 
 import dalvik.annotation.optimization.NeverCompile;
@@ -2120,7 +2127,7 @@
         r.nextReceiver = r.receivers.size();
         mHistory.onBroadcastFinishedLocked(r);
 
-        BroadcastQueueImpl.logBootCompletedBroadcastCompletionLatencyIfPossible(r);
+        logBootCompletedBroadcastCompletionLatencyIfPossible(r);
 
         if (r.intent.getComponent() == null && r.intent.getPackage() == null
                 && (r.intent.getFlags() & Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
@@ -2216,6 +2223,59 @@
         return null;
     }
 
+    private void logBootCompletedBroadcastCompletionLatencyIfPossible(BroadcastRecord r) {
+        // Only log after last receiver.
+        // In case of split BOOT_COMPLETED broadcast, make sure only call this method on the
+        // last BroadcastRecord of the split broadcast which has non-null resultTo.
+        final int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
+        if (r.nextReceiver < numReceivers) {
+            return;
+        }
+        final String action = r.intent.getAction();
+        int event = 0;
+        if (Intent.ACTION_LOCKED_BOOT_COMPLETED.equals(action)) {
+            event = BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__LOCKED_BOOT_COMPLETED;
+        } else if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
+            event = BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__BOOT_COMPLETED;
+        }
+        if (event != 0) {
+            final int dispatchLatency = (int) (r.dispatchTime - r.enqueueTime);
+            final int completeLatency = (int) (SystemClock.uptimeMillis() - r.enqueueTime);
+            final int dispatchRealLatency = (int) (r.dispatchRealTime - r.enqueueRealTime);
+            final int completeRealLatency = (int)
+                    (SystemClock.elapsedRealtime() - r.enqueueRealTime);
+            int userType =
+                    FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__TYPE_UNKNOWN;
+            // This method is called very infrequently, no performance issue we call
+            // LocalServices.getService() here.
+            final UserManagerInternal umInternal = LocalServices.getService(
+                    UserManagerInternal.class);
+            final UserInfo userInfo =
+                    (umInternal != null) ? umInternal.getUserInfo(r.userId) : null;
+            if (userInfo != null) {
+                userType = UserJourneyLogger.getUserTypeForStatsd(userInfo.userType);
+            }
+            Slog.i(TAG, "BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED action:"
+                    + action
+                    + " dispatchLatency:" + dispatchLatency
+                    + " completeLatency:" + completeLatency
+                    + " dispatchRealLatency:" + dispatchRealLatency
+                    + " completeRealLatency:" + completeRealLatency
+                    + " receiversSize:" + numReceivers
+                    + " userId:" + r.userId
+                    + " userType:" + (userInfo != null ? userInfo.userType : null));
+            FrameworkStatsLog.write(
+                    BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED,
+                    event,
+                    dispatchLatency,
+                    completeLatency,
+                    dispatchRealLatency,
+                    completeRealLatency,
+                    r.userId,
+                    userType);
+        }
+    }
+
     @Override
     @NeverCompile
     public void dumpDebug(@NonNull ProtoOutputStream proto, long fieldId) {
diff --git a/services/core/java/com/android/server/am/ConnectionRecord.java b/services/core/java/com/android/server/am/ConnectionRecord.java
index 4ef31bf..f2b9b25 100644
--- a/services/core/java/com/android/server/am/ConnectionRecord.java
+++ b/services/core/java/com/android/server/am/ConnectionRecord.java
@@ -17,6 +17,7 @@
 package com.android.server.am;
 
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ProcessList.UNKNOWN_ADJ;
 
 import android.annotation.Nullable;
 import android.app.IServiceConnection;
@@ -37,7 +38,7 @@
 /**
  * Description of a single binding to a service.
  */
-final class ConnectionRecord {
+final class ConnectionRecord implements OomAdjusterModernImpl.Connection{
     final AppBindRecord binding;    // The application/service binding.
     final ActivityServiceConnectionsHolder<ConnectionRecord> activity;  // If non-null, the owning activity.
     final IServiceConnection conn;  // The client connection.
@@ -127,6 +128,14 @@
         aliasComponent = _aliasComponent;
     }
 
+    @Override
+    public void computeHostOomAdjLSP(OomAdjuster oomAdjuster, ProcessRecord host,
+            ProcessRecord client, long now, ProcessRecord topApp, boolean doingAll,
+            int oomAdjReason, int cachedAdj) {
+        oomAdjuster.computeServiceHostOomAdjLSP(this, host, client, now, topApp, doingAll, false,
+                false, oomAdjReason, UNKNOWN_ADJ, false, false);
+    }
+
     public long getFlags() {
         return flags;
     }
diff --git a/services/core/java/com/android/server/am/ContentProviderConnection.java b/services/core/java/com/android/server/am/ContentProviderConnection.java
index 825b938..3988277 100644
--- a/services/core/java/com/android/server/am/ContentProviderConnection.java
+++ b/services/core/java/com/android/server/am/ContentProviderConnection.java
@@ -18,6 +18,7 @@
 
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROVIDER;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ProcessList.UNKNOWN_ADJ;
 
 import android.annotation.UserIdInt;
 import android.os.Binder;
@@ -32,7 +33,8 @@
 /**
  * Represents a link between a content provider and client.
  */
-public final class ContentProviderConnection extends Binder {
+public final class ContentProviderConnection extends Binder implements
+        OomAdjusterModernImpl.Connection {
     public final ContentProviderRecord provider;
     public final ProcessRecord client;
     public final String clientPackage;
@@ -72,6 +74,14 @@
         createTime = SystemClock.elapsedRealtime();
     }
 
+    @Override
+    public void computeHostOomAdjLSP(OomAdjuster oomAdjuster, ProcessRecord host,
+            ProcessRecord client, long now, ProcessRecord topApp, boolean doingAll,
+            int oomAdjReason, int cachedAdj) {
+        oomAdjuster.computeProviderHostOomAdjLSP(this, host, client, now, topApp, doingAll, false,
+                false, oomAdjReason, UNKNOWN_ADJ, false, false);
+    }
+
     public void startAssociationIfNeeded() {
         // If we don't already have an active association, create one...  but only if this
         // is an association between two different processes.
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 31328ae..cd6964e 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -587,7 +587,7 @@
     }
 
     @GuardedBy({"mService", "mProcLock"})
-    private void performUpdateOomAdjLSP(@OomAdjReason int oomAdjReason) {
+    protected void performUpdateOomAdjLSP(@OomAdjReason int oomAdjReason) {
         final ProcessRecord topApp = mService.getTopApp();
         // Clear any pending ones because we are doing a full update now.
         mPendingProcessSet.clear();
@@ -913,7 +913,7 @@
     }
 
     @GuardedBy("mService")
-    private void performUpdateOomAdjPendingTargetsLocked(@OomAdjReason int oomAdjReason) {
+    protected void performUpdateOomAdjPendingTargetsLocked(@OomAdjReason int oomAdjReason) {
         final ProcessRecord topApp = mService.getTopApp();
 
         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
@@ -941,7 +941,7 @@
      * must have called {@link collectReachableProcessesLocked} on it.
      */
     @GuardedBy({"mService", "mProcLock"})
-    protected void updateOomAdjInnerLSP(@OomAdjReason int oomAdjReason, final ProcessRecord topApp,
+    private void updateOomAdjInnerLSP(@OomAdjReason int oomAdjReason, final ProcessRecord topApp,
             ArrayList<ProcessRecord> processes, ActiveUids uids, boolean potentialCycles,
             boolean startProfiling) {
         final boolean fullUpdate = processes == null;
@@ -1775,12 +1775,11 @@
             state.setAdjSeq(mAdjSeq);
             state.setCurrentSchedulingGroup(SCHED_GROUP_BACKGROUND);
             state.setCurProcState(PROCESS_STATE_CACHED_EMPTY);
+            state.setCurRawProcState(PROCESS_STATE_CACHED_EMPTY);
             state.setCurAdj(CACHED_APP_MAX_ADJ);
             state.setCurRawAdj(CACHED_APP_MAX_ADJ);
             state.setCompletedAdjSeq(state.getAdjSeq());
             state.setCurCapability(PROCESS_CAPABILITY_NONE);
-            onProcessStateChanged(app, prevProcState);
-            onProcessOomAdjChanged(app, prevAppAdj);
             return false;
         }
 
@@ -1843,8 +1842,6 @@
             state.setCurRawProcState(state.getCurProcState());
             state.setCurAdj(state.getMaxAdj());
             state.setCompletedAdjSeq(state.getAdjSeq());
-            onProcessStateChanged(app, prevProcState);
-            onProcessOomAdjChanged(app, prevAppAdj);
             // if curAdj is less than prevAppAdj, then this process was promoted
             return state.getCurAdj() < prevAppAdj || state.getCurProcState() < prevProcState;
         }
@@ -2561,7 +2558,7 @@
     }
 
     @GuardedBy({"mService", "mProcLock"})
-    protected boolean computeServiceHostOomAdjLSP(ConnectionRecord cr, ProcessRecord app,
+    public boolean computeServiceHostOomAdjLSP(ConnectionRecord cr, ProcessRecord app,
             ProcessRecord client, long now, ProcessRecord topApp, boolean doingAll,
             boolean cycleReEval, boolean computeClients, int oomAdjReason, int cachedAdj,
             boolean couldRecurse, boolean dryRun) {
@@ -2991,7 +2988,11 @@
         return updated;
     }
 
-    protected boolean computeProviderHostOomAdjLSP(ContentProviderConnection conn,
+    /**
+     * Computes the impact on {@code app} the provider connections from {@code client} has.
+     */
+    @GuardedBy({"mService", "mProcLock"})
+    public boolean computeProviderHostOomAdjLSP(ContentProviderConnection conn,
             ProcessRecord app, ProcessRecord client, long now, ProcessRecord topApp,
             boolean doingAll, boolean cycleReEval, boolean computeClients, int oomAdjReason,
             int cachedAdj, boolean couldRecurse, boolean dryRun) {
@@ -3572,7 +3573,7 @@
         int initialCapability =  PROCESS_CAPABILITY_NONE;
         boolean initialCached = true;
         final ProcessStateRecord state = app.mState;
-        final int prevProcState = state.getCurRawProcState();
+        final int prevProcState = state.getCurProcState();
         final int prevAdj = state.getCurRawAdj();
         // If the process has been marked as foreground, it is starting as the top app (with
         // Zygote#START_AS_TOP_APP_ARG), so boost the thread priority of its default UI thread.
diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
index 1bf779a..dd75bc0 100644
--- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
+++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
@@ -38,7 +38,6 @@
 import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
 import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
 
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_UID_OBSERVERS;
 import static com.android.server.am.ActivityManagerService.TAG_UID_OBSERVERS;
 import static com.android.server.am.ProcessList.BACKUP_APP_ADJ;
@@ -67,8 +66,10 @@
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal.OomAdjReason;
 import android.content.pm.ServiceInfo;
+import android.os.IBinder;
 import android.os.SystemClock;
 import android.os.Trace;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Slog;
 
@@ -80,6 +81,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 
 /**
@@ -275,6 +277,7 @@
                 mProcessRecordNodes[i] = new LinkedProcessRecordList(type);
             }
             mLastNode = new ProcessRecordNode[size];
+            resetLastNodes();
         }
 
         int size() {
@@ -285,7 +288,7 @@
         void reset() {
             for (int i = 0; i < mProcessRecordNodes.length; i++) {
                 mProcessRecordNodes[i].reset();
-                mLastNode[i] = null;
+                setLastNodeToHead(i);
             }
         }
 
@@ -509,26 +512,118 @@
     }
 
     /**
-     * A helper consumer for collecting processes that have not been reached yet. To avoid object
-     * allocations every OomAdjuster update, the results will be stored in
-     * {@link UnreachedProcessCollector#processList}. The process list reader is responsible
-     * for setting it before usage, as well as, clearing the reachable state of each process in the
-     * list.
+     * A {@link Connection} represents any connection between two processes that can cause a
+     * change in importance in the host process based on the client process and connection state.
      */
-    private static class UnreachedProcessCollector implements Consumer<ProcessRecord> {
-        public ArrayList<ProcessRecord> processList = null;
+    public interface Connection {
+        /**
+         * Compute the impact this connection has on the host's importance values.
+         */
+        void computeHostOomAdjLSP(OomAdjuster oomAdjuster, ProcessRecord host, ProcessRecord client,
+                long now, ProcessRecord topApp, boolean doingAll, int oomAdjReason, int cachedAdj);
+    }
+
+    /**
+     * A helper consumer for marking and collecting reachable processes.
+     */
+    private static class ReachableCollectingConsumer implements
+            BiConsumer<Connection, ProcessRecord> {
+        ArrayList<ProcessRecord> mReachables = null;
+
+        public void init(ArrayList<ProcessRecord> reachables) {
+            mReachables = reachables;
+        }
+
         @Override
-        public void accept(ProcessRecord process) {
-            if (process.mState.isReachable()) {
+        public void accept(Connection unused, ProcessRecord host) {
+            if (host.mState.isReachable()) {
                 return;
             }
-            process.mState.setReachable(true);
-            processList.add(process);
+            host.mState.setReachable(true);
+            mReachables.add(host);
         }
     }
 
-    private final UnreachedProcessCollector mUnreachedProcessCollector =
-            new UnreachedProcessCollector();
+    private final ReachableCollectingConsumer mReachableCollectingConsumer =
+            new ReachableCollectingConsumer();
+
+    /**
+     * A helper consumer for computing the importance of a connection from a client.
+     * Connections for clients marked reachable will be ignored.
+     */
+    private class ComputeConnectionIgnoringReachableClientsConsumer implements
+            BiConsumer<Connection, ProcessRecord> {
+        public OomAdjusterArgs args = null;
+
+        @Override
+        public void accept(Connection conn, ProcessRecord client) {
+            final ProcessRecord host = args.mApp;
+            final ProcessRecord topApp = args.mTopApp;
+            final long now = args.mNow;
+            final @OomAdjReason int oomAdjReason = args.mOomAdjReason;
+
+            if (client.mState.isReachable()) return;
+
+            conn.computeHostOomAdjLSP(OomAdjusterModernImpl.this, host, client, now, topApp, false,
+                    oomAdjReason, UNKNOWN_ADJ);
+        }
+    }
+
+    private final ComputeConnectionIgnoringReachableClientsConsumer
+            mComputeConnectionIgnoringReachableClientsConsumer =
+            new ComputeConnectionIgnoringReachableClientsConsumer();
+
+    /**
+     * A helper consumer for computing host process importance from a connection from a client app.
+     */
+    private class ComputeHostConsumer implements BiConsumer<Connection, ProcessRecord> {
+        public OomAdjusterArgs args = null;
+
+        @Override
+        public void accept(Connection conn, ProcessRecord host) {
+            final ProcessRecord client = args.mApp;
+            final int cachedAdj = args.mCachedAdj;
+            final ProcessRecord topApp = args.mTopApp;
+            final long now = args.mNow;
+            final @OomAdjReason int oomAdjReason = args.mOomAdjReason;
+            final boolean fullUpdate = args.mFullUpdate;
+
+            final int prevProcState = host.mState.getCurProcState();
+            final int prevAdj = host.mState.getCurRawAdj();
+
+            conn.computeHostOomAdjLSP(OomAdjusterModernImpl.this, host, client, now, topApp,
+                    fullUpdate, oomAdjReason, cachedAdj);
+
+            updateProcStateSlotIfNecessary(host, prevProcState);
+            updateAdjSlotIfNecessary(host, prevAdj);
+        }
+    }
+    private final ComputeHostConsumer mComputeHostConsumer = new ComputeHostConsumer();
+
+    /**
+     * A helper consumer for computing all connections from an app.
+     */
+    private class ComputeConnectionsConsumer implements Consumer<OomAdjusterArgs> {
+        @Override
+        public void accept(OomAdjusterArgs args) {
+            final ProcessRecord app = args.mApp;
+            final ActiveUids uids = args.mUids;
+
+            // This process was updated in some way, mark that it was last calculated this sequence.
+            app.mState.setCompletedAdjSeq(mAdjSeq);
+            if (uids != null) {
+                final UidRecord uidRec = app.getUidRecord();
+
+                if (uidRec != null) {
+                    uids.put(uidRec.getUid(), uidRec);
+                }
+            }
+            mComputeHostConsumer.args = args;
+            forEachConnectionLSP(app, mComputeHostConsumer);
+        }
+    }
+    private final ComputeConnectionsConsumer mComputeConnectionsConsumer =
+            new ComputeConnectionsConsumer();
 
     OomAdjusterModernImpl(ActivityManagerService service, ProcessList processList,
             ActiveUids activeUids) {
@@ -617,6 +712,12 @@
         }
     }
 
+    private void updateAdjSlot(ProcessRecord app, int prevRawAdj) {
+        final int slot = adjToSlot(app.mState.getCurRawAdj());
+        final int prevSlot = adjToSlot(prevRawAdj);
+        mProcessRecordAdjNodes.moveAppTo(app, prevSlot, slot);
+    }
+
     private void updateProcStateSlotIfNecessary(ProcessRecord app, int prevProcState) {
         if (app.mState.getCurProcState() != prevProcState) {
             final int slot = processStateToSlot(app.mState.getCurProcState());
@@ -627,359 +728,247 @@
         }
     }
 
+    private void updateProcStateSlot(ProcessRecord app, int prevProcState) {
+        final int slot = processStateToSlot(app.mState.getCurProcState());
+        final int prevSlot = processStateToSlot(prevProcState);
+        mProcessRecordProcStateNodes.moveAppTo(app, prevSlot, slot);
+    }
+
     @Override
-    protected boolean performUpdateOomAdjLSP(ProcessRecord app, @OomAdjReason int oomAdjReason) {
+    protected void performUpdateOomAdjLSP(@OomAdjReason int oomAdjReason) {
         final ProcessRecord topApp = mService.getTopApp();
+        // Clear any pending ones because we are doing a full update now.
+        mPendingProcessSet.clear();
+        mService.mAppProfiler.mHasPreviousProcess = mService.mAppProfiler.mHasHomeProcess = false;
 
         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
         mService.mOomAdjProfiler.oomAdjStarted();
-        mAdjSeq++;
 
-        final ProcessStateRecord state = app.mState;
-        final int oldAdj = state.getCurRawAdj();
-        final int cachedAdj = oldAdj >= CACHED_APP_MIN_ADJ
-                ? oldAdj : UNKNOWN_ADJ;
-
-        final ActiveUids uids = mTmpUidRecords;
-        final ArraySet<ProcessRecord> targetProcesses = mTmpProcessSet;
-        final ArrayList<ProcessRecord> reachableProcesses = mTmpProcessList;
-        final long now = SystemClock.uptimeMillis();
-        final long nowElapsed = SystemClock.elapsedRealtime();
-
-        uids.clear();
-        targetProcesses.clear();
-        targetProcesses.add(app);
-        reachableProcesses.clear();
-
-        // Find out all reachable processes from this app.
-        collectReachableProcessesLocked(targetProcesses, reachableProcesses, uids);
-
-        // Copy all of the reachable processes into the target process set.
-        targetProcesses.addAll(reachableProcesses);
-        reachableProcesses.clear();
-
-        final boolean result = performNewUpdateOomAdjLSP(oomAdjReason,
-                topApp, targetProcesses, uids, false, now, cachedAdj);
-
-        reachableProcesses.addAll(targetProcesses);
-        assignCachedAdjIfNecessary(reachableProcesses);
-        for (int  i = uids.size() - 1; i >= 0; i--) {
-            final UidRecord uidRec = uids.valueAt(i);
-            uidRec.forEachProcess(this::updateAppUidRecIfNecessaryLSP);
-        }
-        updateUidsLSP(uids, nowElapsed);
-        for (int i = 0, size = targetProcesses.size(); i < size; i++) {
-            applyOomAdjLSP(targetProcesses.valueAt(i), false, now, nowElapsed, oomAdjReason);
-        }
-        targetProcesses.clear();
-        reachableProcesses.clear();
+        fullUpdateLSP(oomAdjReason);
 
         mService.mOomAdjProfiler.oomAdjEnded();
         Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-        return result;
     }
 
     @GuardedBy({"mService", "mProcLock"})
     @Override
-    protected void updateOomAdjInnerLSP(@OomAdjReason int oomAdjReason, final ProcessRecord topApp,
-            ArrayList<ProcessRecord> processes, ActiveUids uids, boolean potentialCycles,
-            boolean startProfiling) {
-        final boolean fullUpdate = processes == null;
-        final ArrayList<ProcessRecord> activeProcesses = fullUpdate
-                ? mProcessList.getLruProcessesLOSP() : processes;
-        ActiveUids activeUids = uids;
-        if (activeUids == null) {
-            final int numUids = mActiveUids.size();
-            activeUids = mTmpUidRecords;
-            activeUids.clear();
-            for (int i = 0; i < numUids; i++) {
-                UidRecord uidRec = mActiveUids.valueAt(i);
-                activeUids.put(uidRec.getUid(), uidRec);
-            }
-        }
-
-        if (startProfiling) {
-            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
-            mService.mOomAdjProfiler.oomAdjStarted();
-        }
-        final long now = SystemClock.uptimeMillis();
-        final long nowElapsed = SystemClock.elapsedRealtime();
-        final long oldTime = now - mConstants.mMaxEmptyTimeMillis;
-        final int numProc = activeProcesses.size();
-
-        mAdjSeq++;
-        if (fullUpdate) {
-            mNewNumServiceProcs = 0;
-            mNewNumAServiceProcs = 0;
-        }
-
-        final ArraySet<ProcessRecord> targetProcesses = mTmpProcessSet;
-        targetProcesses.clear();
-        if (!fullUpdate) {
-            targetProcesses.addAll(activeProcesses);
-        }
-
-        performNewUpdateOomAdjLSP(oomAdjReason, topApp, targetProcesses, activeUids,
-                fullUpdate, now, UNKNOWN_ADJ);
-
-        // TODO: b/319163103 - optimize cache adj assignment to not require the whole lru list.
-        assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
-        postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime);
-        targetProcesses.clear();
-
-        if (startProfiling) {
-            mService.mOomAdjProfiler.oomAdjEnded();
-            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-        }
-        return;
-    }
-
-    /**
-     * Perform the oom adj update on the given {@code targetProcesses}.
-     *
-     * <p>Note: The expectation to the given {@code targetProcesses} is, the caller
-     * must have called {@link collectReachableProcessesLocked} on it.
-     */
-    private boolean performNewUpdateOomAdjLSP(@OomAdjReason int oomAdjReason,
-            ProcessRecord topApp,  ArraySet<ProcessRecord> targetProcesses, ActiveUids uids,
-            boolean fullUpdate, long now, int cachedAdj) {
-
-        final ArrayList<ProcessRecord> clientProcesses = mTmpProcessList2;
-        clientProcesses.clear();
-
-        // We'll need to collect the upstream processes of the target apps here, because those
-        // processes would potentially impact the procstate/adj via bindings.
-        if (!fullUpdate) {
-            collectExcludedClientProcessesLocked(targetProcesses, clientProcesses);
-
-            for (int i = 0, size = targetProcesses.size(); i < size; i++) {
-                final ProcessRecord app = targetProcesses.valueAt(i);
-                app.mState.resetCachedInfo();
-                final UidRecord uidRec = app.getUidRecord();
-                if (uidRec != null) {
-                    if (DEBUG_UID_OBSERVERS) {
-                        Slog.i(TAG_UID_OBSERVERS, "Starting update of " + uidRec);
-                    }
-                    uidRec.reset();
-                }
-            }
-        } else {
-            final ArrayList<ProcessRecord> lru = mProcessList.getLruProcessesLOSP();
-            for (int i = 0, size = lru.size(); i < size; i++) {
-                final ProcessRecord app = lru.get(i);
-                app.mState.resetCachedInfo();
-                final UidRecord uidRec = app.getUidRecord();
-                if (uidRec != null) {
-                    if (DEBUG_UID_OBSERVERS) {
-                        Slog.i(TAG_UID_OBSERVERS, "Starting update of " + uidRec);
-                    }
-                    uidRec.reset();
-                }
-            }
-        }
-
-        updateNewOomAdjInnerLSP(oomAdjReason, topApp, targetProcesses, clientProcesses, uids,
-                cachedAdj, now, fullUpdate);
-
-        clientProcesses.clear();
-
+    protected boolean performUpdateOomAdjLSP(ProcessRecord app, @OomAdjReason int oomAdjReason) {
+        mPendingProcessSet.add(app);
+        performUpdateOomAdjPendingTargetsLocked(oomAdjReason);
         return true;
     }
 
-    /**
-     * Collect the client processes from the given {@code apps}, the result will be returned in the
-     * given {@code clientProcesses}, which will <em>NOT</em> include the processes from the given
-     * {@code apps}.
-     */
     @GuardedBy("mService")
-    private void collectExcludedClientProcessesLocked(ArraySet<ProcessRecord> apps,
-            ArrayList<ProcessRecord> clientProcesses) {
-        // Mark all of the provided apps as reachable to avoid including them in the client list.
-        final int appsSize = apps.size();
-        for (int i = 0; i < appsSize; i++) {
-            final ProcessRecord app = apps.valueAt(i);
-            app.mState.setReachable(true);
-        }
+    @Override
+    protected void performUpdateOomAdjPendingTargetsLocked(@OomAdjReason int oomAdjReason) {
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
+        mService.mOomAdjProfiler.oomAdjStarted();
 
-        clientProcesses.clear();
-        mUnreachedProcessCollector.processList = clientProcesses;
-        for (int i = 0; i < appsSize; i++) {
-            final ProcessRecord app = apps.valueAt(i);
-            app.forEachClient(mUnreachedProcessCollector);
+        synchronized (mProcLock) {
+            partialUpdateLSP(oomAdjReason, mPendingProcessSet);
         }
-        mUnreachedProcessCollector.processList = null;
+        mPendingProcessSet.clear();
 
-        // Reset the temporary bits.
-        for (int i = clientProcesses.size() - 1; i >= 0; i--) {
-            clientProcesses.get(i).mState.setReachable(false);
-        }
-        for (int i = 0, size = apps.size(); i < size; i++) {
-            final ProcessRecord app = apps.valueAt(i);
-            app.mState.setReachable(false);
-        }
+        mService.mOomAdjProfiler.oomAdjEnded();
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
     }
 
+    /**
+     * Perform a full update on the entire process list.
+     */
     @GuardedBy({"mService", "mProcLock"})
-    private void updateNewOomAdjInnerLSP(@OomAdjReason int oomAdjReason, final ProcessRecord topApp,
-            ArraySet<ProcessRecord> targetProcesses, ArrayList<ProcessRecord> clientProcesses,
-            ActiveUids uids, int cachedAdj, long now, boolean fullUpdate) {
-        mTmpOomAdjusterArgs.update(topApp, now, cachedAdj, oomAdjReason, uids, fullUpdate);
-
-        mProcessRecordProcStateNodes.resetLastNodes();
-        mProcessRecordAdjNodes.resetLastNodes();
-
-        final int procStateTarget = mProcessRecordProcStateNodes.size() - 1;
-        final int adjTarget = mProcessRecordAdjNodes.size() - 1;
+    private void fullUpdateLSP(@OomAdjReason int oomAdjReason) {
+        final ProcessRecord topApp = mService.getTopApp();
+        final long now = SystemClock.uptimeMillis();
+        final long nowElapsed = SystemClock.elapsedRealtime();
+        final long oldTime = now - mConstants.mMaxEmptyTimeMillis;
 
         mAdjSeq++;
-        // All apps to be updated will be moved to the lowest slot.
-        if (fullUpdate) {
-            // Move all the process record node to the lowest slot, we'll do recomputation on all of
-            // them. Use the processes from the lru list, because the scanning order matters here.
-            final ArrayList<ProcessRecord> lruList = mProcessList.getLruProcessesLOSP();
-            for (int i = procStateTarget; i >= 0; i--) {
-                mProcessRecordProcStateNodes.reset(i);
-                // Force the last node to the head since we'll recompute all of them.
-                mProcessRecordProcStateNodes.setLastNodeToHead(i);
+
+        mNewNumServiceProcs = 0;
+        mNewNumAServiceProcs = 0;
+
+        // Clear the priority queues.
+        mProcessRecordProcStateNodes.reset();
+        mProcessRecordAdjNodes.reset();
+
+        final ArrayList<ProcessRecord> lru = mProcessList.getLruProcessesLOSP();
+        for (int i = lru.size() - 1; i >= 0; i--) {
+            final ProcessRecord app = lru.get(i);
+            final int prevProcState = app.mState.getCurProcState();
+            final int prevAdj = app.mState.getCurRawAdj();
+            app.mState.resetCachedInfo();
+            final UidRecord uidRec = app.getUidRecord();
+            if (uidRec != null) {
+                if (DEBUG_UID_OBSERVERS) {
+                    Slog.i(TAG_UID_OBSERVERS, "Starting update of " + uidRec);
+                }
+                uidRec.reset();
             }
-            // enqueue the targets in the reverse order of the lru list.
-            for (int i = lruList.size() - 1; i >= 0; i--) {
-                mProcessRecordProcStateNodes.append(lruList.get(i), procStateTarget);
-            }
-            // Do the same to the adj nodes.
-            for (int i = adjTarget; i >= 0; i--) {
-                mProcessRecordAdjNodes.reset(i);
-                // Force the last node to the head since we'll recompute all of them.
-                mProcessRecordAdjNodes.setLastNodeToHead(i);
-            }
-            for (int i = lruList.size() - 1; i >= 0; i--) {
-                mProcessRecordAdjNodes.append(lruList.get(i), adjTarget);
-            }
-        } else {
-            // Move the target processes to the lowest slot.
-            for (int i = 0, size = targetProcesses.size(); i < size; i++) {
-                final ProcessRecord app = targetProcesses.valueAt(i);
-                final int procStateSlot = processStateToSlot(app.mState.getCurProcState());
-                final int adjSlot = adjToSlot(app.mState.getCurRawAdj());
-                mProcessRecordProcStateNodes.moveAppTo(app, procStateSlot, procStateTarget);
-                mProcessRecordAdjNodes.moveAppTo(app, adjSlot, adjTarget);
-            }
-            // Move the "lastNode" to head to make sure we scan all nodes in this slot.
-            mProcessRecordProcStateNodes.setLastNodeToHead(procStateTarget);
-            mProcessRecordAdjNodes.setLastNodeToHead(adjTarget);
+
+            // Compute initial values, the procState and adj priority queues will be populated here.
+            computeOomAdjLSP(app, UNKNOWN_ADJ, topApp, true, now, false, false, oomAdjReason,
+                    false);
+            updateProcStateSlot(app, prevProcState);
+            updateAdjSlot(app, prevAdj);
         }
 
-        // All apps to be updated have been moved to the lowest slot.
-        // Do an initial pass of the computation.
-        mProcessRecordProcStateNodes.forEachNewNode(mProcessRecordProcStateNodes.size() - 1,
-                this::computeInitialOomAdjLSP);
-
-        if (!fullUpdate) {
-            // We didn't update the client processes with the computeInitialOomAdjLSP
-            // because they don't need to do so. But they'll be playing vital roles in
-            // computing the bindings. So include them into the scan list below.
-            for (int i = 0, size = clientProcesses.size(); i < size; i++) {
-                mProcessRecordProcStateNodes.moveAppToTail(clientProcesses.get(i));
-            }
-            // We don't update the adj list since we're resetting it below.
-        }
-
-        // Now nodes are set into their slots, without factoring in the bindings.
-        // The nodes between the `lastNode` pointer and the TAIL should be the new nodes.
-        //
-        // The whole rationale here is that, the bindings from client to host app, won't elevate
-        // the host app's procstate/adj higher than the client app's state (BIND_ABOVE_CLIENT
-        // is a special case here, but client app's raw adj is still no less than the host app's).
-        // Therefore, starting from the top to the bottom, for each slot, scan all of the new nodes,
-        // check its bindings, elevate its host app's slot if necessary.
-        //
-        // We'd have to do this in two passes: 1) scan procstate node list; 2) scan adj node list.
-        // Because the procstate and adj are not always in sync - there are cases where
-        // the processes with lower proc state could be getting a higher oom adj score.
-        // And because of this, the procstate and adj node lists are basically two priority heaps.
-        //
-        // As the 2nd pass with the adj node lists potentially includes a significant amount of
-        // duplicated scans as the 1st pass has done, we'll reset the last node pointers for
-        // the adj node list before the 1st pass; so during the 1st pass, if any app's adj slot
-        // gets bumped, we'll only scan those in 2nd pass.
-
+        // Set adj last nodes now, this way a process will only be reevaluated during the adj node
+        // iteration if they adj score changed during the procState node iteration.
         mProcessRecordAdjNodes.resetLastNodes();
+        mTmpOomAdjusterArgs.update(topApp, now, UNKNOWN_ADJ, oomAdjReason, null, true);
+        computeConnectionsLSP();
 
+        assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
+        postUpdateOomAdjInnerLSP(oomAdjReason, mActiveUids, now, nowElapsed, oldTime);
+    }
+
+    /**
+     * Traverse the process graph and update processes based on changes in connection importances.
+     */
+    @GuardedBy({"mService", "mProcLock"})
+    private void computeConnectionsLSP() {
         // 1st pass, scan each slot in the procstate node list.
         for (int i = 0, end = mProcessRecordProcStateNodes.size() - 1; i < end; i++) {
-            mProcessRecordProcStateNodes.forEachNewNode(i, this::computeHostOomAdjLSP);
+            mProcessRecordProcStateNodes.forEachNewNode(i, mComputeConnectionsConsumer);
         }
 
         // 2nd pass, scan each slot in the adj node list.
         for (int i = 0, end = mProcessRecordAdjNodes.size() - 1; i < end; i++) {
-            mProcessRecordAdjNodes.forEachNewNode(i, this::computeHostOomAdjLSP);
+            mProcessRecordAdjNodes.forEachNewNode(i, mComputeConnectionsConsumer);
         }
     }
 
-    @GuardedBy({"mService", "mProcLock"})
-    private void computeInitialOomAdjLSP(OomAdjusterArgs args) {
-        final ProcessRecord app = args.mApp;
-        final int cachedAdj = args.mCachedAdj;
-        final ProcessRecord topApp = args.mTopApp;
-        final long now = args.mNow;
-        final int oomAdjReason = args.mOomAdjReason;
-        final ActiveUids uids = args.mUids;
-        final boolean fullUpdate = args.mFullUpdate;
-
-        if (DEBUG_OOM_ADJ) {
-            Slog.i(TAG, "OOM ADJ initial args app=" + app
-                    + " cachedAdj=" + cachedAdj
-                    + " topApp=" + topApp
-                    + " now=" + now
-                    + " oomAdjReason=" + oomAdjReasonToString(oomAdjReason)
-                    + " fullUpdate=" + fullUpdate);
-        }
-
-        if (uids != null) {
-            final UidRecord uidRec = app.getUidRecord();
-
-            if (uidRec != null) {
-                uids.put(uidRec.getUid(), uidRec);
-            }
-        }
-
-        computeOomAdjLSP(app, cachedAdj, topApp, fullUpdate, now, false, false, oomAdjReason,
-                false);
-    }
-
     /**
-     * @return The proposed change to the schedGroup.
+     * Perform a partial update on the target processes and their reachable processes.
      */
     @GuardedBy({"mService", "mProcLock"})
-    @Override
-    protected int setIntermediateAdjLSP(ProcessRecord app, int adj, int prevRawAppAdj,
-            int schedGroup) {
-        schedGroup = super.setIntermediateAdjLSP(app, adj, prevRawAppAdj, schedGroup);
+    private void partialUpdateLSP(@OomAdjReason int oomAdjReason, ArraySet<ProcessRecord> targets) {
+        final ProcessRecord topApp = mService.getTopApp();
+        final long now = SystemClock.uptimeMillis();
+        final long nowElapsed = SystemClock.elapsedRealtime();
+        final long oldTime = now - mConstants.mMaxEmptyTimeMillis;
 
-        updateAdjSlotIfNecessary(app, prevRawAppAdj);
+        ActiveUids activeUids = mTmpUidRecords;
+        activeUids.clear();
+        mTmpOomAdjusterArgs.update(topApp, now, UNKNOWN_ADJ, oomAdjReason, activeUids, false);
 
-        return schedGroup;
+        mAdjSeq++;
+
+        final ArrayList<ProcessRecord> reachables = mTmpProcessList;
+        reachables.clear();
+
+        for (int i = 0, size = targets.size(); i < size; i++) {
+            final ProcessRecord target = targets.valueAtUnchecked(i);
+            target.mState.resetCachedInfo();
+            target.mState.setReachable(true);
+            reachables.add(target);
+        }
+
+        // Collect all processes that are reachable.
+        // Any process not found in this step will not change in importance during this update.
+        collectAndMarkReachableProcessesLSP(reachables);
+
+        // Initialize the reachable processes based on their own values plus any
+        // connections from processes not found in the previous step. Since those non-reachable
+        // processes cannot change as a part of this update, their current values can be used
+        // right now.
+        mProcessRecordProcStateNodes.resetLastNodes();
+        initReachableStatesLSP(reachables, mTmpOomAdjusterArgs);
+
+        // Set adj last nodes now, this way a process will only be reevaluated during the adj node
+        // iteration if they adj score changed during the procState node iteration.
+        mProcessRecordAdjNodes.resetLastNodes();
+        // Now traverse and compute the connections of processes with changed importance.
+        computeConnectionsLSP();
+
+        boolean unassignedAdj = false;
+        for (int i = 0, size = reachables.size(); i < size; i++) {
+            final ProcessStateRecord state = reachables.get(i).mState;
+            state.setReachable(false);
+            state.setCompletedAdjSeq(mAdjSeq);
+            if (state.getCurAdj() >= UNKNOWN_ADJ) {
+                unassignedAdj = true;
+            }
+        }
+
+        // If all processes have an assigned adj, no need to calculate and assign cached adjs.
+        if (unassignedAdj) {
+            // TODO: b/319163103 - optimize cache adj assignment to not require the whole lru list.
+            assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
+        }
+
+        // Repopulate any uid record that may have changed.
+        for (int i = 0, size = activeUids.size(); i < size; i++) {
+            final UidRecord ur = activeUids.valueAt(i);
+            ur.reset();
+            for (int j = ur.getNumOfProcs() - 1; j >= 0; j--) {
+                final ProcessRecord proc = ur.getProcessRecordByIndex(j);
+                updateAppUidRecIfNecessaryLSP(proc);
+            }
+        }
+
+        postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime);
     }
 
+    /**
+     * Mark all processes reachable from the {@code reachables} processes and add them to the
+     * provided {@code reachables} list (targets excluded).
+     *
+     * Returns true if a cycle exists within the reachable process graph.
+     */
     @GuardedBy({"mService", "mProcLock"})
-    @Override
-    protected void setIntermediateProcStateLSP(ProcessRecord app, int procState,
-            int prevProcState) {
-        super.setIntermediateProcStateLSP(app, procState, prevProcState);
-
-        updateProcStateSlotIfNecessary(app, prevProcState);
+    private void collectAndMarkReachableProcessesLSP(ArrayList<ProcessRecord> reachables) {
+        mReachableCollectingConsumer.init(reachables);
+        for (int i = 0; i < reachables.size(); i++) {
+            ProcessRecord pr = reachables.get(i);
+            forEachConnectionLSP(pr, mReachableCollectingConsumer);
+        }
     }
 
+    /**
+     * Calculate initial importance states for {@code reachables} and update their slot position
+     * if necessary.
+     */
+    private void initReachableStatesLSP(ArrayList<ProcessRecord> reachables, OomAdjusterArgs args) {
+        for (int i = 0, size = reachables.size(); i < size; i++) {
+            final ProcessRecord reachable = reachables.get(i);
+            final int prevProcState = reachable.mState.getCurProcState();
+            final int prevAdj = reachable.mState.getCurRawAdj();
+
+            args.mApp = reachable;
+            computeOomAdjIgnoringReachablesLSP(args);
+
+            updateProcStateSlot(reachable, prevProcState);
+            updateAdjSlot(reachable, prevAdj);
+        }
+    }
+
+    /**
+     * Calculate initial importance states for {@code app}.
+     * Processes not marked reachable cannot change as a part of this update, so connections from
+     * those process can be calculated now.
+     */
     @GuardedBy({"mService", "mProcLock"})
-    private void computeHostOomAdjLSP(OomAdjusterArgs args) {
+    private void computeOomAdjIgnoringReachablesLSP(OomAdjusterArgs args) {
         final ProcessRecord app = args.mApp;
-        final int cachedAdj = args.mCachedAdj;
         final ProcessRecord topApp = args.mTopApp;
         final long now = args.mNow;
         final @OomAdjReason int oomAdjReason = args.mOomAdjReason;
-        final boolean fullUpdate = args.mFullUpdate;
-        final ActiveUids uids = args.mUids;
 
+        computeOomAdjLSP(app, UNKNOWN_ADJ, topApp, false, now, false, false, oomAdjReason, false);
+
+        mComputeConnectionIgnoringReachableClientsConsumer.args = args;
+        forEachClientConnectionLSP(app, mComputeConnectionIgnoringReachableClientsConsumer);
+    }
+
+    /**
+     * Stream the connections with {@code app} as a client to
+     * {@code connectionConsumer}.
+     */
+    @GuardedBy({"mService", "mProcLock"})
+    private static void forEachConnectionLSP(ProcessRecord app,
+            BiConsumer<Connection, ProcessRecord> connectionConsumer) {
         final ProcessServiceRecord psr = app.mServices;
         for (int i = psr.numberOfConnections() - 1; i >= 0; i--) {
             ConnectionRecord cr = psr.getConnectionAt(i);
@@ -987,16 +976,14 @@
                     ? cr.binding.service.isolationHostProc : cr.binding.service.app;
             if (service == null || service == app
                     || (service.mState.getMaxAdj() >= SYSTEM_ADJ
-                            && service.mState.getMaxAdj() < FOREGROUND_APP_ADJ)
+                    && service.mState.getMaxAdj() < FOREGROUND_APP_ADJ)
                     || (service.mState.getCurAdj() <= FOREGROUND_APP_ADJ
-                            && service.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND
-                            && service.mState.getCurProcState() <= PROCESS_STATE_TOP)
+                    && service.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND
+                    && service.mState.getCurProcState() <= PROCESS_STATE_TOP)
                     || (service.isSdkSandbox && cr.binding.attributedClient != null)) {
                 continue;
             }
-
-            computeServiceHostOomAdjLSP(cr, service, app, now, topApp, fullUpdate, false, false,
-                    oomAdjReason, cachedAdj, false, false);
+            connectionConsumer.accept(cr, service);
         }
 
         for (int i = psr.numberOfSdkSandboxConnections() - 1; i >= 0; i--) {
@@ -1004,15 +991,13 @@
             final ProcessRecord service = cr.binding.service.app;
             if (service == null || service == app
                     || (service.mState.getMaxAdj() >= SYSTEM_ADJ
-                            && service.mState.getMaxAdj() < FOREGROUND_APP_ADJ)
+                    && service.mState.getMaxAdj() < FOREGROUND_APP_ADJ)
                     || (service.mState.getCurAdj() <= FOREGROUND_APP_ADJ
-                            && service.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND
-                            && service.mState.getCurProcState() <= PROCESS_STATE_TOP)) {
+                    && service.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND
+                    && service.mState.getCurProcState() <= PROCESS_STATE_TOP)) {
                 continue;
             }
-
-            computeServiceHostOomAdjLSP(cr, service, app, now, topApp, fullUpdate, false, false,
-                    oomAdjReason, cachedAdj, false, false);
+            connectionConsumer.accept(cr, service);
         }
 
         final ProcessProviderRecord ppr = app.mProviders;
@@ -1021,15 +1006,51 @@
             ProcessRecord provider = cpc.provider.proc;
             if (provider == null || provider == app
                     || (provider.mState.getMaxAdj() >= ProcessList.SYSTEM_ADJ
-                            && provider.mState.getMaxAdj() < FOREGROUND_APP_ADJ)
+                    && provider.mState.getMaxAdj() < FOREGROUND_APP_ADJ)
                     || (provider.mState.getCurAdj() <= FOREGROUND_APP_ADJ
-                            && provider.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND
-                            && provider.mState.getCurProcState() <= PROCESS_STATE_TOP)) {
+                    && provider.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND
+                    && provider.mState.getCurProcState() <= PROCESS_STATE_TOP)) {
                 continue;
             }
+            connectionConsumer.accept(cpc, provider);
+        }
+    }
 
-            computeProviderHostOomAdjLSP(cpc, provider, app, now, topApp, fullUpdate, false, false,
-                    oomAdjReason, cachedAdj, false, false);
+    /**
+     * Stream the connections from clients with {@code app} as the host to {@code
+     * connectionConsumer}.
+     */
+    @GuardedBy({"mService", "mProcLock"})
+    private static void forEachClientConnectionLSP(ProcessRecord app,
+            BiConsumer<Connection, ProcessRecord> connectionConsumer) {
+        final ProcessServiceRecord psr = app.mServices;
+
+        for (int i = psr.numberOfRunningServices() - 1; i >= 0; i--) {
+            final ServiceRecord s = psr.getRunningServiceAt(i);
+            final ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections =
+                    s.getConnections();
+            for (int j = serviceConnections.size() - 1; j >= 0; j--) {
+                final ArrayList<ConnectionRecord> clist = serviceConnections.valueAt(j);
+                for (int k = clist.size() - 1; k >= 0; k--) {
+                    final ConnectionRecord cr = clist.get(k);
+                    final ProcessRecord client;
+                    if (app.isSdkSandbox && cr.binding.attributedClient != null) {
+                        client = cr.binding.attributedClient;
+                    } else {
+                        client = cr.binding.client;
+                    }
+                    connectionConsumer.accept(cr, client);
+                }
+            }
+        }
+
+        final ProcessProviderRecord ppr = app.mProviders;
+        for (int i = ppr.numberOfProviders() - 1; i >= 0; i--) {
+            final ContentProviderRecord cpr = ppr.getProviderAt(i);
+            for (int j = cpr.connections.size() - 1; j >= 0; j--) {
+                final ContentProviderConnection conn = cpr.connections.get(j);
+                connectionConsumer.accept(conn, conn.client);
+            }
         }
     }
 }
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
index 2ef433c..0aa1a69 100644
--- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -718,9 +718,7 @@
                         mService.mContext, mApp.info.packageName, mApp.info.flags);
             }
         }
-        for (BroadcastQueue queue : mService.mBroadcastQueues) {
-            queue.onApplicationProblemLocked(mApp);
-        }
+        mService.getBroadcastQueue().onApplicationProblemLocked(mApp);
     }
 
     @GuardedBy("mService")
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 89c8994..a1fdd50 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -208,7 +208,7 @@
 
     // Number of levels we have available for different service connection group importance
     // levels.
-    static final int CACHED_APP_IMPORTANCE_LEVELS = 5;
+    public static final int CACHED_APP_IMPORTANCE_LEVELS = 5;
 
     // The B list of SERVICE_ADJ -- these are the old and decrepit
     // services that aren't as shiny and interesting as the ones in the A list.
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 7009bd0..7356588 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -69,10 +69,8 @@
 import com.android.server.wm.WindowProcessListener;
 
 import java.io.PrintWriter;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
-import java.util.function.Consumer;
 
 /**
  * Full information about a particular process that
@@ -1659,34 +1657,4 @@
                 && !mOptRecord.shouldNotFreeze()
                 && mState.getCurAdj() >= ProcessList.FREEZER_CUTOFF_ADJ;
     }
-
-    /**
-     * Traverses all client processes and feed them to consumer.
-     */
-    @GuardedBy("mProcLock")
-    void forEachClient(@NonNull Consumer<ProcessRecord> consumer) {
-        for (int i = mServices.numberOfRunningServices() - 1; i >= 0; i--) {
-            final ServiceRecord s = mServices.getRunningServiceAt(i);
-            final ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections =
-                    s.getConnections();
-            for (int j = serviceConnections.size() - 1; j >= 0; j--) {
-                final ArrayList<ConnectionRecord> clist = serviceConnections.valueAt(j);
-                for (int k = clist.size() - 1; k >= 0; k--) {
-                    final ConnectionRecord cr = clist.get(k);
-                    if (isSdkSandbox && cr.binding.attributedClient != null) {
-                        consumer.accept(cr.binding.attributedClient);
-                    } else {
-                        consumer.accept(cr.binding.client);
-                    }
-                }
-            }
-        }
-        for (int i = mProviders.numberOfProviders() - 1; i >= 0; i--) {
-            final ContentProviderRecord cpr = mProviders.getProviderAt(i);
-            for (int j = cpr.connections.size() - 1; j >= 0; j--) {
-                final ContentProviderConnection conn = cpr.connections.get(j);
-                consumer.accept(conn.client);
-            }
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java
index 562beaf..3d695bc 100644
--- a/services/core/java/com/android/server/am/ProcessServiceRecord.java
+++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java
@@ -541,7 +541,7 @@
     private void removeSdkSandboxConnectionIfNecessary(ConnectionRecord connection) {
         final ProcessRecord attributedClient = connection.binding.attributedClient;
         if (attributedClient != null && connection.binding.service.isSdkSandbox) {
-            if (attributedClient.mServices.mSdkSandboxConnections == null) {
+            if (attributedClient.mServices.mSdkSandboxConnections != null) {
                 attributedClient.mServices.mSdkSandboxConnections.remove(connection);
             }
         }
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 3abfe082..13a1807 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -1419,7 +1419,8 @@
 
     private boolean allowBiometricUnlockForPrivateProfile() {
         return android.os.Flags.allowPrivateProfile()
-                && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace();
+                && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()
+                && android.multiuser.Flags.enablePrivateSpaceFeatures();
     }
 
     /**
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 68b4e3f..7ee2a7a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -149,11 +149,7 @@
             return proto.getBytes();
         }
 
-        @android.annotation.EnforcePermission(
-                anyOf = {
-                        android.Manifest.permission.USE_BIOMETRIC_INTERNAL,
-                        android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION
-                })
+        @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
         public List<FaceSensorPropertiesInternal> getSensorPropertiesInternal(
                 String opPackageName) {
@@ -297,29 +293,6 @@
                     restricted, statsClient, isKeyguard);
         }
 
-        @android.annotation.EnforcePermission(
-                android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION)
-        @Override // Binder call
-        public long authenticateInBackground(final IBinder token, final long operationId,
-                final IFaceServiceReceiver receiver, final FaceAuthenticateOptions options) {
-            // TODO(b/152413782): If the sensor supports face detect and the device is encrypted or
-            //  lockdown, something wrong happened. See similar path in FingerprintService.
-
-            super.authenticateInBackground_enforcePermission();
-
-            final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
-            if (provider == null) {
-                Slog.w(TAG, "Null provider for authenticate");
-                return -1;
-            }
-            options.setSensorId(provider.first);
-
-            return provider.second.scheduleAuthenticate(token, operationId,
-                    0 /* cookie */, new ClientMonitorCallbackConverter(receiver), options,
-                    false /* restricted */, BiometricsProtoEnums.CLIENT_UNKNOWN /* statsClient */,
-                    true /* allowBackgroundAuthentication */);
-        }
-
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
         public long detectFace(final IBinder token,
@@ -583,11 +556,7 @@
             return provider.getEnrolledFaces(sensorId, userId);
         }
 
-        @android.annotation.EnforcePermission(
-                anyOf = {
-                        android.Manifest.permission.USE_BIOMETRIC_INTERNAL,
-                        android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION
-                })
+        @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
         public boolean hasEnrolledFaces(int sensorId, int userId, String opPackageName) {
             super.hasEnrolledFaces_enforcePermission();
diff --git a/services/core/java/com/android/server/clipboard/ArcClipboardMonitor.java b/services/core/java/com/android/server/clipboard/ArcClipboardMonitor.java
new file mode 100644
index 0000000..413020e
--- /dev/null
+++ b/services/core/java/com/android/server/clipboard/ArcClipboardMonitor.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 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.clipboard;
+
+import android.annotation.Nullable;
+import android.content.ClipData;
+
+import com.android.server.LocalServices;
+
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+
+public class ArcClipboardMonitor implements Consumer<ClipData> {
+    private static final String TAG = "ArcClipboardMonitor";
+
+    public interface ArcClipboardBridge {
+        /**
+         * Called when a clipboard content is updated.
+         */
+        void onPrimaryClipChanged(ClipData data);
+
+        /**
+         * Passes the callback to set a new clipboard content with a uid.
+         */
+        void setHandler(BiConsumer<ClipData, Integer> setAndroidClipboard);
+    }
+
+    private ArcClipboardBridge mBridge;
+    private BiConsumer<ClipData, Integer> mAndroidClipboardSetter;
+
+    ArcClipboardMonitor(final BiConsumer<ClipData, Integer> setAndroidClipboard) {
+        mAndroidClipboardSetter = setAndroidClipboard;
+        LocalServices.addService(ArcClipboardMonitor.class, this);
+    }
+
+    @Override
+    public void accept(final @Nullable ClipData clip) {
+        if (mBridge != null) {
+            mBridge.onPrimaryClipChanged(clip);
+        }
+    }
+
+    /**
+     * Sets the other end of the clipboard bridge.
+     */
+    public void setClipboardBridge(ArcClipboardBridge bridge) {
+        mBridge = bridge;
+        mBridge.setHandler(mAndroidClipboardSetter);
+    }
+}
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 49f6070..4c3020f 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -151,7 +151,7 @@
     private final ContentCaptureManagerInternal mContentCaptureInternal;
     private final AutofillManagerInternal mAutofillInternal;
     private final IBinder mPermissionOwner;
-    private final Consumer<ClipData> mEmulatorClipboardMonitor;
+    private final Consumer<ClipData> mClipboardMonitor;
     private final Handler mWorkerHandler;
 
     @GuardedBy("mLock")
@@ -192,7 +192,7 @@
         final IBinder permOwner = mUgmInternal.newUriPermissionOwner("clipboard");
         mPermissionOwner = permOwner;
         if (Build.IS_EMULATOR) {
-            mEmulatorClipboardMonitor = new EmulatorClipboardMonitor((clip) -> {
+            mClipboardMonitor = new EmulatorClipboardMonitor((clip) -> {
                 synchronized (mLock) {
                     Clipboard clipboard = getClipboardLocked(0, DEVICE_ID_DEFAULT);
                     if (clipboard != null) {
@@ -201,8 +201,12 @@
                     }
                 }
             });
+        } else if (Build.IS_ARC) {
+            mClipboardMonitor = new ArcClipboardMonitor((clip, uid) -> {
+                setPrimaryClipInternal(clip, uid);
+            });
         } else {
-            mEmulatorClipboardMonitor = (clip) -> {};
+            mClipboardMonitor = (clip) -> {};
         }
 
         updateConfig();
@@ -937,7 +941,7 @@
     private void setPrimaryClipInternalLocked(
             @Nullable ClipData clip, int uid, int deviceId, @Nullable String sourcePackage) {
         if (deviceId == DEVICE_ID_DEFAULT) {
-            mEmulatorClipboardMonitor.accept(clip);
+            mClipboardMonitor.accept(clip);
         }
 
         final int userId = UserHandle.getUserId(uid);
diff --git a/services/core/java/com/android/server/connectivity/TEST_MAPPING b/services/core/java/com/android/server/connectivity/TEST_MAPPING
index f508319..55601bc 100644
--- a/services/core/java/com/android/server/connectivity/TEST_MAPPING
+++ b/services/core/java/com/android/server/connectivity/TEST_MAPPING
@@ -8,6 +8,19 @@
               }
           ],
           "file_patterns": ["VpnIkeV2Utils\\.java", "VpnProfileStore\\.java"]
+      },
+      {
+          "name":"FrameworksVpnTests",
+          "options": [
+              {
+                "exclude-annotation": "com.android.testutils.SkipPresubmit"
+              }
+          ],
+          "file_patterns":[
+              "Vpn\\.java",
+              "VpnIkeV2Utils\\.java",
+              "VpnProfileStore\\.java"
+          ]
       }
   ],
   "presubmit-large": [
@@ -26,10 +39,5 @@
           ],
         "file_patterns": ["Vpn\\.java", "VpnIkeV2Utils\\.java", "VpnProfileStore\\.java"]
       }
-  ],
-  "postsubmit":[
-    {
-      "name":"FrameworksVpnTests"
-    }
   ]
 }
\ No newline at end of file
diff --git a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
index a43f93a..91e560e 100644
--- a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
+++ b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
@@ -20,7 +20,6 @@
 
 import android.annotation.Nullable;
 import android.hardware.display.DisplayManagerInternal;
-import android.os.PowerManager;
 import android.os.Trace;
 
 /**
@@ -110,7 +109,6 @@
         try {
             mDisplayOffloader.stopOffload();
             mIsActive = false;
-            mDisplayPowerController.setBrightnessFromOffload(PowerManager.BRIGHTNESS_INVALID_FLOAT);
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_POWER);
         }
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 2010aca..7f014f6 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -375,7 +375,8 @@
     // information.
     // At the time of this writing, this value is changed within updatePowerState() only, which is
     // limited to the thread used by DisplayControllerHandler.
-    private final BrightnessReason mBrightnessReason = new BrightnessReason();
+    @VisibleForTesting
+    final BrightnessReason mBrightnessReason = new BrightnessReason();
     private final BrightnessReason mBrightnessReasonTemp = new BrightnessReason();
 
     // Brightness animation ramp rates in brightness units per second
@@ -1379,7 +1380,7 @@
         // Switch to doze auto-brightness mode if needed
         if (mFlags.areAutoBrightnessModesEnabled() && mAutomaticBrightnessController != null
                 && !mAutomaticBrightnessController.isInIdleMode()) {
-            mAutomaticBrightnessController.switchMode(mPowerRequest.policy == POLICY_DOZE
+            mAutomaticBrightnessController.switchMode(Display.isDozeState(state)
                     ? AUTO_BRIGHTNESS_MODE_DOZE : AUTO_BRIGHTNESS_MODE_DEFAULT);
         }
 
@@ -1434,7 +1435,9 @@
         float currentBrightnessSetting = mDisplayBrightnessController.getCurrentBrightness();
         // Apply auto-brightness.
         int brightnessAdjustmentFlags = 0;
-        if (Float.isNaN(brightnessState)) {
+        // AutomaticBrightnessStrategy has higher priority than OffloadBrightnessStrategy
+        if (Float.isNaN(brightnessState)
+                || mBrightnessReasonTemp.getReason() == BrightnessReason.REASON_OFFLOAD) {
             if (mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()) {
                 brightnessState = mAutomaticBrightnessStrategy.getAutomaticScreenBrightness(
                         mTempBrightnessEvent);
@@ -1455,8 +1458,11 @@
                     if (mScreenOffBrightnessSensorController != null) {
                         mScreenOffBrightnessSensorController.setLightSensorEnabled(false);
                     }
+                    setBrightnessFromOffload(PowerManager.BRIGHTNESS_INVALID_FLOAT);
                 } else {
                     mAutomaticBrightnessStrategy.setAutoBrightnessApplied(false);
+                    // Restore the lower-priority brightness strategy
+                    brightnessState = displayBrightnessState.getBrightness();
                 }
             }
         } else {
@@ -3020,9 +3026,10 @@
                     setDwbcLoggingEnabled(msg.arg1);
                     break;
                 case MSG_SET_BRIGHTNESS_FROM_OFFLOAD:
-                    mDisplayBrightnessController.setBrightnessFromOffload(
-                            Float.intBitsToFloat(msg.arg1));
-                    updatePowerState();
+                    if (mDisplayBrightnessController.setBrightnessFromOffload(
+                            Float.intBitsToFloat(msg.arg1))) {
+                        updatePowerState();
+                    }
                     break;
             }
         }
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
index f6d02db..34d53be 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
@@ -26,6 +26,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.display.BrightnessSynchronizer;
 import com.android.server.display.AutomaticBrightnessController;
 import com.android.server.display.BrightnessMappingStrategy;
 import com.android.server.display.BrightnessSetting;
@@ -175,14 +176,19 @@
 
     /**
      * Sets the brightness from the offload session.
+     * @return Whether the offload brightness has changed
      */
-    public void setBrightnessFromOffload(float brightness) {
+    public boolean setBrightnessFromOffload(float brightness) {
         synchronized (mLock) {
-            if (mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy() != null) {
+            if (mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy() != null
+                    && !BrightnessSynchronizer.floatEquals(mDisplayBrightnessStrategySelector
+                    .getOffloadBrightnessStrategy().getOffloadScreenBrightness(), brightness)) {
                 mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy()
                         .setOffloadScreenBrightness(brightness);
+                return true;
             }
         }
+        return false;
     }
 
     /**
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
index 8e84450..71cc872 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
@@ -133,7 +133,8 @@
         } else if (BrightnessUtils.isValidBrightnessValue(
                 mTemporaryBrightnessStrategy.getTemporaryScreenBrightness())) {
             displayBrightnessStrategy = mTemporaryBrightnessStrategy;
-        } else if (mOffloadBrightnessStrategy != null && BrightnessUtils.isValidBrightnessValue(
+        } else if (mAutomaticBrightnessStrategy.shouldUseAutoBrightness()
+                && mOffloadBrightnessStrategy != null && BrightnessUtils.isValidBrightnessValue(
                 mOffloadBrightnessStrategy.getOffloadScreenBrightness())) {
             displayBrightnessStrategy = mOffloadBrightnessStrategy;
         }
diff --git a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
index d1ca49b..8b54b22 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
@@ -108,7 +108,6 @@
         mIsAutoBrightnessEnabled = shouldUseAutoBrightness()
                 && (targetDisplayState == Display.STATE_ON || autoBrightnessEnabledInDoze)
                 && brightnessReason != BrightnessReason.REASON_OVERRIDE
-                && brightnessReason != BrightnessReason.REASON_OFFLOAD
                 && mAutomaticBrightnessController != null;
         mAutoBrightnessDisabledDueToDisplayOff = shouldUseAutoBrightness()
                 && !(targetDisplayState == Display.STATE_ON || autoBrightnessEnabledInDoze);
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
index a23c08a..d876a38 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
@@ -198,7 +198,7 @@
         addValidationInfo(Constants.MESSAGE_CEC_VERSION,
                 oneByteValidator, ADDR_NOT_UNREGISTERED, ADDR_DIRECT);
         addValidationInfo(Constants.MESSAGE_SET_MENU_LANGUAGE,
-                new AsciiValidator(3), ADDR_NOT_UNREGISTERED, ADDR_BROADCAST);
+                new AsciiValidator(3), ADDR_TV, ADDR_BROADCAST);
 
         ParameterValidator statusRequestValidator = new MinimumOneByteRangeValidator(0x01, 0x03);
         addValidationInfo(Constants.MESSAGE_DECK_CONTROL,
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index c8c66238..d0532b99 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -4665,15 +4665,27 @@
         public void onAudioDeviceVolumeChanged(
                 @NonNull AudioDeviceAttributes audioDevice,
                 @NonNull VolumeInfo volumeInfo) {
+            int localDeviceAddress = mLocalDevice.getDeviceInfo().getLogicalAddress();
 
-            // Do nothing if the System Audio device does not support <Set Audio Volume Level>
+            // We can't send <Set Audio Volume Level> if the System Audio device doesn't support it.
+            // But AudioService has already updated its volume and expects us to set it.
+            // So the best we can do is to send <Give Audio Status>, which triggers
+            // <Report Audio Status>, which should update AudioService with its correct volume.
             if (mSystemAudioDevice.getDeviceFeatures().getSetAudioVolumeLevelSupport()
                     != DeviceFeatures.FEATURE_SUPPORTED) {
+                // Update the volume tracked in AbsoluteVolumeAudioStatusAction
+                // so it correctly processes the next <Report Audio Status>
+                HdmiCecLocalDevice avbDevice = isTvDevice() ? tv() : playback();
+                avbDevice.updateAvbVolume(volumeInfo.getVolumeIndex());
+                // Send <Give Audio Status>
+                sendCecCommand(HdmiCecMessageBuilder.buildGiveAudioStatus(
+                        localDeviceAddress,
+                        mSystemAudioDevice.getLogicalAddress()
+                ));
                 return;
             }
 
             // Send <Set Audio Volume Level> to notify the System Audio device of the volume change
-            int localDeviceAddress = mLocalDevice.getDeviceInfo().getLogicalAddress();
             sendCecCommand(SetAudioVolumeLevelMessage.build(
                             localDeviceAddress,
                             mSystemAudioDevice.getLogicalAddress(),
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
index fc99471..0ca4808 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
@@ -449,7 +449,7 @@
     }
 
     /**
-     * Sends a reliable message rom this client to a nanoapp.
+     * Sends a reliable message from this client to a nanoapp.
      *
      * @param message the message to send
      * @param transactionCallback The callback to use to confirm the delivery of the message for
@@ -473,6 +473,12 @@
             @Nullable IContextHubTransactionCallback transactionCallback) {
         ContextHubServiceUtil.checkPermissions(mContext);
 
+        // Clear the isReliable and messageSequenceNumber fields.
+        // These will be set to true and a real value if the message
+        // is reliable.
+        message.setIsReliable(false);
+        message.setMessageSequenceNumber(0);
+
         @ContextHubTransaction.Result int result;
         if (isRegistered()) {
             int authState = mMessageChannelNanoappIdMap.getOrDefault(
@@ -485,7 +491,9 @@
                 // Return a bland error code for apps targeting old SDKs since they wouldn't be able
                 // to use an error code added in S.
                 return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
-            } else if (authState == AUTHORIZATION_UNKNOWN) {
+            }
+
+            if (authState == AUTHORIZATION_UNKNOWN) {
                 // Only check permissions the first time a nanoapp is queried since nanoapp
                 // permissions don't currently change at runtime. If the host permission changes
                 // later, that'll be checked by onOpChanged.
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 29ea071..c80f988 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -814,7 +814,8 @@
         // storage is locked, instead of when the user is stopped.  This would ensure the flags get
         // reset if CE storage is locked later for a user that allows delayed locking.
         if (android.os.Flags.allowPrivateProfile()
-                && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()) {
+                && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()
+                && android.multiuser.Flags.enablePrivateSpaceFeatures()) {
             UserProperties userProperties = mUserManager.getUserProperties(UserHandle.of(userId));
             if (userProperties != null && userProperties.getAllowStoppingUserWithDelayedLocking()) {
                 return;
diff --git a/services/core/java/com/android/server/net/TEST_MAPPING b/services/core/java/com/android/server/net/TEST_MAPPING
index 8db5905..4fc1a17 100644
--- a/services/core/java/com/android/server/net/TEST_MAPPING
+++ b/services/core/java/com/android/server/net/TEST_MAPPING
@@ -27,11 +27,15 @@
           "exclude-annotation": "androidx.test.filters.FlakyTest"
         }
       ]
-    }
-  ],
-  "postsubmit":[
+    },
     {
-      "name":"FrameworksVpnTests"
+      "name": "FrameworksVpnTests",
+      "options": [
+        {
+          "exclude-annotation": "com.android.testutils.SkipPresubmit"
+        }
+      ],
+      "file_patterns": ["VpnManagerService\\.java"]
     }
   ]
 }
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index f645eaa..3ecc58e 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -942,6 +942,23 @@
         return false;
     }
 
+    protected boolean isPackageOrComponentAllowedWithPermission(ComponentName component,
+            int userId) {
+        if (!(isPackageOrComponentAllowed(component.flattenToString(), userId)
+                || isPackageOrComponentAllowed(component.getPackageName(), userId))) {
+            return false;
+        }
+        return componentHasBindPermission(component, userId);
+    }
+
+    private boolean componentHasBindPermission(ComponentName component, int userId) {
+        ServiceInfo info = getServiceInfo(component, userId);
+        if (info == null) {
+            return false;
+        }
+        return mConfig.bindPermission.equals(info.permission);
+    }
+
     boolean isPackageOrComponentUserSet(String pkgOrComponent, int userId) {
         synchronized (mApproved) {
             ArraySet<String> services = mUserSetServices.get(userId);
@@ -1003,6 +1020,7 @@
                     for (int uid : uidList) {
                         if (isPackageAllowed(pkgName, UserHandle.getUserId(uid))) {
                             anyServicesInvolved = true;
+                            trimApprovedListsForInvalidServices(pkgName, UserHandle.getUserId(uid));
                         }
                     }
                 }
@@ -1135,8 +1153,7 @@
 
         synchronized (mMutex) {
             if (enabled) {
-                if (isPackageOrComponentAllowed(component.flattenToString(), userId)
-                        || isPackageOrComponentAllowed(component.getPackageName(), userId)) {
+                if (isPackageOrComponentAllowedWithPermission(component, userId)) {
                     registerServiceLocked(component, userId);
                 } else {
                     Slog.d(TAG, component + " no longer has permission to be bound");
@@ -1270,6 +1287,33 @@
         return removed;
     }
 
+    private void trimApprovedListsForInvalidServices(String packageName, int userId) {
+        synchronized (mApproved) {
+            final ArrayMap<Boolean, ArraySet<String>> approvedByType = mApproved.get(userId);
+            if (approvedByType == null) {
+                return;
+            }
+            for (int i = 0; i < approvedByType.size(); i++) {
+                final ArraySet<String> approved = approvedByType.valueAt(i);
+                for (int j = approved.size() - 1; j >= 0; j--) {
+                    final String approvedPackageOrComponent = approved.valueAt(j);
+                    if (TextUtils.equals(getPackageName(approvedPackageOrComponent), packageName)) {
+                        final ComponentName component = ComponentName.unflattenFromString(
+                                approvedPackageOrComponent);
+                        if (component != null && !componentHasBindPermission(component, userId)) {
+                            approved.removeAt(j);
+                            if (DEBUG) {
+                                Slog.v(TAG, "Removing " + approvedPackageOrComponent
+                                        + " from approved list; no bind permission found "
+                                        + mConfig.bindPermission);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
     protected String getPackageName(String packageOrComponent) {
         final ComponentName component = ComponentName.unflattenFromString(packageOrComponent);
         if (component != null) {
@@ -1519,8 +1563,7 @@
     void reregisterService(final ComponentName cn, final int userId) {
         // If rebinding a package that died, ensure it still has permission
         // after the rebind delay
-        if (isPackageOrComponentAllowed(cn.getPackageName(), userId)
-                || isPackageOrComponentAllowed(cn.flattenToString(), userId)) {
+        if (isPackageOrComponentAllowedWithPermission(cn, userId)) {
             registerService(cn, userId);
         }
     }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index ba5882c..b98424c 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1204,6 +1204,10 @@
         }
     }
 
+    private static boolean privateSpaceFlagsEnabled() {
+        return allowPrivateProfile() && android.multiuser.Flags.enablePrivateSpaceFeatures();
+    }
+
     private final class SavePolicyFileRunnable implements Runnable {
         @Override
         public void run() {
@@ -2142,7 +2146,7 @@
         }
 
         private boolean isProfileUnavailable(String action) {
-            return allowPrivateProfile() ?
+            return privateSpaceFlagsEnabled() ?
                     action.equals(Intent.ACTION_PROFILE_UNAVAILABLE) :
                     action.equals(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
         }
@@ -2744,7 +2748,7 @@
         filter.addAction(Intent.ACTION_USER_REMOVED);
         filter.addAction(Intent.ACTION_USER_UNLOCKED);
         filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
-        if (allowPrivateProfile()){
+        if (privateSpaceFlagsEnabled()){
             filter.addAction(Intent.ACTION_PROFILE_UNAVAILABLE);
         }
         getContext().registerReceiverAsUser(mIntentReceiver, UserHandle.ALL, filter, null, null);
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index bc86c82..289faf4 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -35,6 +35,8 @@
 import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
 import static com.android.internal.util.Preconditions.checkArgument;
 
+import static java.util.Objects.requireNonNull;
+
 import android.annotation.DrawableRes;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -536,36 +538,40 @@
     public boolean updateAutomaticZenRule(String ruleId, AutomaticZenRule automaticZenRule,
             @ConfigChangeOrigin int origin, String reason, int callingUid) {
         requirePublicOrigin("updateAutomaticZenRule", origin);
-        ZenModeConfig newConfig;
+        if (ruleId == null) {
+            throw new IllegalArgumentException("ruleId cannot be null");
+        }
         synchronized (mConfigLock) {
             if (mConfig == null) return false;
             if (DEBUG) {
                 Log.d(TAG, "updateAutomaticZenRule zenRule=" + automaticZenRule
                         + " reason=" + reason);
             }
-            newConfig = mConfig.copy();
-            ZenModeConfig.ZenRule rule;
-            if (ruleId == null) {
-                throw new IllegalArgumentException("Rule doesn't exist");
-            } else {
-                rule = newConfig.automaticRules.get(ruleId);
-                if (rule == null || !canManageAutomaticZenRule(rule)) {
-                    throw new SecurityException(
-                            "Cannot update rules not owned by your condition provider");
-                }
+            ZenModeConfig.ZenRule oldRule = mConfig.automaticRules.get(ruleId);
+            if (oldRule == null || !canManageAutomaticZenRule(oldRule)) {
+                throw new SecurityException(
+                        "Cannot update rules not owned by your condition provider");
             }
+            ZenModeConfig newConfig = mConfig.copy();
+            ZenModeConfig.ZenRule newRule = requireNonNull(newConfig.automaticRules.get(ruleId));
             if (!Flags.modesApi()) {
-                if (rule.enabled != automaticZenRule.isEnabled()) {
-                    dispatchOnAutomaticRuleStatusChanged(mConfig.user, rule.getPkg(), ruleId,
+                if (newRule.enabled != automaticZenRule.isEnabled()) {
+                    dispatchOnAutomaticRuleStatusChanged(mConfig.user, newRule.getPkg(), ruleId,
                             automaticZenRule.isEnabled()
                                     ? AUTOMATIC_RULE_STATUS_ENABLED
                                     : AUTOMATIC_RULE_STATUS_DISABLED);
                 }
             }
 
-            populateZenRule(rule.pkg, automaticZenRule, rule, origin, /* isNew= */ false);
+            boolean updated = populateZenRule(newRule.pkg, automaticZenRule, newRule,
+                    origin, /* isNew= */ false);
+            if (Flags.modesApi() && !updated) {
+                // Bail out so we don't have the side effects of updating a rule (i.e. dropping
+                // condition) when no changes happen.
+                return true;
+            }
             return setConfigLocked(newConfig, origin, reason,
-                    rule.component, true, callingUid);
+                    newRule.component, true, callingUid);
         }
     }
 
@@ -1073,31 +1079,67 @@
         return null;
     }
 
-    private void populateZenRule(String pkg, AutomaticZenRule automaticZenRule, ZenRule rule,
+    /**
+     * Populates a {@code ZenRule} with the content of the {@link AutomaticZenRule}. Can be used for
+     * both rule creation or update (distinguished by the {@code isNew} parameter. The change is
+     * applied differently depending on the origin; for example app-provided changes might be
+     * ignored (if the rule was previously customized by the user), while user-provided changes
+     * update the user-modified bitmasks for any modifications.
+     *
+     * <p>Returns {@code true} if the rule was modified. Note that this is not equivalent to
+     * {@link ZenRule#equals} or {@link AutomaticZenRule#equals}, for various reasons:
+     * <ul>
+     *     <li>some metadata-related fields are not considered
+     *     <li>some fields (like {@code condition} are always reset, and ignored for this result
+     *     <li>an app may provide changes that are not actually applied, as described above
+     * </ul>
+     */
+    private boolean populateZenRule(String pkg, AutomaticZenRule azr, ZenRule rule,
                          @ConfigChangeOrigin int origin, boolean isNew) {
         if (Flags.modesApi()) {
+            boolean modified = false;
             // These values can always be edited by the app, so we apply changes immediately.
             if (isNew) {
                 rule.id = ZenModeConfig.newRuleId();
                 rule.creationTime = mClock.millis();
-                rule.component = automaticZenRule.getOwner();
+                rule.component = azr.getOwner();
                 rule.pkg = pkg;
+                modified = true;
             }
 
             rule.condition = null;
-            rule.conditionId = automaticZenRule.getConditionId();
-            if (rule.enabled != automaticZenRule.isEnabled()) {
-                rule.snoozing = false;
+            if (!Objects.equals(rule.conditionId, azr.getConditionId())) {
+                rule.conditionId = azr.getConditionId();
+                modified = true;
             }
-            rule.enabled = automaticZenRule.isEnabled();
-            rule.configurationActivity = automaticZenRule.getConfigurationActivity();
-            rule.allowManualInvocation = automaticZenRule.isManualInvocationAllowed();
-            rule.iconResName =
-                    drawableResIdToResName(rule.pkg, automaticZenRule.getIconResId());
-            rule.triggerDescription = automaticZenRule.getTriggerDescription();
-            rule.type = automaticZenRule.getType();
+            if (rule.enabled != azr.isEnabled()) {
+                rule.enabled = azr.isEnabled();
+                rule.snoozing = false;
+                modified = true;
+            }
+            if (!Objects.equals(rule.configurationActivity, azr.getConfigurationActivity())) {
+                rule.configurationActivity = azr.getConfigurationActivity();
+                modified = true;
+            }
+            if (rule.allowManualInvocation != azr.isManualInvocationAllowed()) {
+                rule.allowManualInvocation = azr.isManualInvocationAllowed();
+                modified = true;
+            }
+            String iconResName = drawableResIdToResName(rule.pkg, azr.getIconResId());
+            if (!Objects.equals(rule.iconResName, iconResName)) {
+                rule.iconResName = iconResName;
+                modified = true;
+            }
+            if (!Objects.equals(rule.triggerDescription, azr.getTriggerDescription())) {
+                rule.triggerDescription = azr.getTriggerDescription();
+                modified = true;
+            }
+            if (rule.type != azr.getType()) {
+                rule.type = azr.getType();
+                modified = true;
+            }
             // TODO: b/310620812 - Remove this once FLAG_MODES_API is inlined.
-            rule.modified = automaticZenRule.isModified();
+            rule.modified = azr.isModified();
 
             // Name is treated differently than other values:
             // App is allowed to update name if the name was not modified by the user (even if
@@ -1107,7 +1149,8 @@
             String previousName = rule.name;
             if (isNew || doesOriginAlwaysUpdateValues(origin)
                     || (rule.userModifiedFields & AutomaticZenRule.FIELD_NAME) == 0) {
-                rule.name = automaticZenRule.getName();
+                rule.name = azr.getName();
+                modified |= !Objects.equals(rule.name, previousName);
             }
 
             // For the remaining values, rules can always have all values updated if:
@@ -1119,50 +1162,56 @@
 
             // For all other values, if updates are not allowed, we discard the update.
             if (!updateValues) {
-                return;
+                return modified;
             }
 
             // Updates the bitmasks if the origin of the change is the user.
             boolean updateBitmask = (origin == UPDATE_ORIGIN_USER);
 
-            if (updateBitmask && !TextUtils.equals(previousName, automaticZenRule.getName())) {
+            if (updateBitmask && !TextUtils.equals(previousName, azr.getName())) {
                 rule.userModifiedFields |= AutomaticZenRule.FIELD_NAME;
             }
             int newZenMode = NotificationManager.zenModeFromInterruptionFilter(
-                    automaticZenRule.getInterruptionFilter(), Global.ZEN_MODE_OFF);
-            if (updateBitmask && rule.zenMode != newZenMode) {
-                rule.userModifiedFields |= AutomaticZenRule.FIELD_INTERRUPTION_FILTER;
+                    azr.getInterruptionFilter(), Global.ZEN_MODE_OFF);
+            if (rule.zenMode != newZenMode) {
+                rule.zenMode = newZenMode;
+                if (updateBitmask) {
+                    rule.userModifiedFields |= AutomaticZenRule.FIELD_INTERRUPTION_FILTER;
+                }
+                modified = true;
             }
 
-            // Updates the values in the ZenRule itself.
-            rule.zenMode = newZenMode;
-
             // Updates the bitmask and values for all policy fields, based on the origin.
-            updatePolicy(rule, automaticZenRule.getZenPolicy(), updateBitmask, isNew);
+            modified |= updatePolicy(rule, azr.getZenPolicy(), updateBitmask, isNew);
 
             // Updates the bitmask and values for all device effect fields, based on the origin.
-            updateZenDeviceEffects(rule, automaticZenRule.getDeviceEffects(),
+            modified |= updateZenDeviceEffects(rule, azr.getDeviceEffects(),
                     origin == UPDATE_ORIGIN_APP, updateBitmask);
+
+            return modified;
         } else {
-            if (rule.enabled != automaticZenRule.isEnabled()) {
+            if (rule.enabled != azr.isEnabled()) {
                 rule.snoozing = false;
             }
-            rule.name = automaticZenRule.getName();
+            rule.name = azr.getName();
             rule.condition = null;
-            rule.conditionId = automaticZenRule.getConditionId();
-            rule.enabled = automaticZenRule.isEnabled();
-            rule.modified = automaticZenRule.isModified();
-            rule.zenPolicy = automaticZenRule.getZenPolicy();
+            rule.conditionId = azr.getConditionId();
+            rule.enabled = azr.isEnabled();
+            rule.modified = azr.isModified();
+            rule.zenPolicy = azr.getZenPolicy();
             rule.zenMode = NotificationManager.zenModeFromInterruptionFilter(
-                    automaticZenRule.getInterruptionFilter(), Global.ZEN_MODE_OFF);
-            rule.configurationActivity = automaticZenRule.getConfigurationActivity();
+                    azr.getInterruptionFilter(), Global.ZEN_MODE_OFF);
+            rule.configurationActivity = azr.getConfigurationActivity();
 
             if (isNew) {
                 rule.id = ZenModeConfig.newRuleId();
                 rule.creationTime = System.currentTimeMillis();
-                rule.component = automaticZenRule.getOwner();
+                rule.component = azr.getOwner();
                 rule.pkg = pkg;
             }
+
+            // Only the MODES_API path cares about the result, so just return whatever here.
+            return true;
         }
     }
 
@@ -1182,16 +1231,19 @@
      * provided {@code ZenRule}, keeping any pre-existing settings from {@code zenRule.zenPolicy}
      * for any unset policy fields in {@code newPolicy}. The user-modified bitmask is updated to
      * reflect the changes being applied (if applicable, i.e. if the update is from the user).
+     *
+     * <p>Returns {@code true} if the policy of the rule was modified.
      */
-    private void updatePolicy(ZenRule zenRule, @Nullable ZenPolicy newPolicy,
+    private boolean updatePolicy(ZenRule zenRule, @Nullable ZenPolicy newPolicy,
             boolean updateBitmask, boolean isNew) {
         if (newPolicy == null) {
             if (isNew) {
                 // Newly created rule with no provided policy; fill in with the default.
                 zenRule.zenPolicy = mDefaultConfig.toZenPolicy();
+                return true;
             }
             // Otherwise, a null policy means no policy changes, so we can stop here.
-            return;
+            return false;
         }
 
         // If oldPolicy is null, we compare against the default policy when determining which
@@ -1272,6 +1324,8 @@
             }
             zenRule.zenPolicyUserModifiedFields = userModifiedFields;
         }
+
+        return !newPolicy.equals(oldPolicy);
     }
 
     /**
@@ -1283,12 +1337,14 @@
      * <p>Apps cannot turn on hidden effects (those tagged as {@code @hide}), so those fields are
      * treated especially: for a new rule, they are blanked out; for an updated rule, previous
      * values are preserved.
+     *
+     * <p>Returns {@code true} if the device effects of the rule were modified.
      */
-    private static void updateZenDeviceEffects(ZenRule zenRule,
+    private static boolean updateZenDeviceEffects(ZenRule zenRule,
             @Nullable ZenDeviceEffects newEffects, boolean isFromApp, boolean updateBitmask) {
         // Same as with ZenPolicy, supplying null effects means keeping the previous ones.
         if (newEffects == null) {
-            return;
+            return false;
         }
 
         ZenDeviceEffects oldEffects = zenRule.zenDeviceEffects != null
@@ -1349,6 +1405,8 @@
             }
             zenRule.zenDeviceEffectsUserModifiedFields = userModifiedFields;
         }
+
+        return !newEffects.equals(oldEffects);
     }
 
     private AutomaticZenRule zenRuleToAutomaticZenRule(ZenRule rule) {
@@ -2505,7 +2563,7 @@
         if (resId == 0) {
             return null;
         }
-        Objects.requireNonNull(packageName);
+        requireNonNull(packageName);
         try {
             final Resources res = mPm.getResourcesForApplication(packageName);
             return res.getResourceName(resId);
diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
index 0abe50f..71800ef 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
+++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
@@ -32,12 +32,13 @@
 import android.app.ondeviceintelligence.IProcessingSignal;
 import android.app.ondeviceintelligence.IResponseCallback;
 import android.app.ondeviceintelligence.IStreamingResponseCallback;
-import android.app.ondeviceintelligence.ITokenCountCallback;
+import android.app.ondeviceintelligence.ITokenInfoCallback;
 import android.app.ondeviceintelligence.OnDeviceIntelligenceManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.ICancellationSignal;
 import android.os.ParcelFileDescriptor;
@@ -47,11 +48,12 @@
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
 import android.service.ondeviceintelligence.IOnDeviceIntelligenceService;
-import android.service.ondeviceintelligence.IOnDeviceTrustedInferenceService;
+import android.service.ondeviceintelligence.IOnDeviceSandboxedInferenceService;
 import android.service.ondeviceintelligence.IRemoteProcessingService;
 import android.service.ondeviceintelligence.IRemoteStorageService;
 import android.service.ondeviceintelligence.IProcessingUpdateStatusCallback;
 import android.service.ondeviceintelligence.OnDeviceIntelligenceService;
+import android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService;
 import android.text.TextUtils;
 import android.util.Slog;
 
@@ -69,7 +71,7 @@
  * This is the system service for handling calls on the
  * {@link android.app.ondeviceintelligence.OnDeviceIntelligenceManager}. This
  * service holds connection references to the underlying remote services i.e. the isolated service
- * {@link  OnDeviceTrustedInferenceService} and a regular
+ * {@link OnDeviceSandboxedInferenceService} and a regular
  * service counter part {@link OnDeviceIntelligenceService}.
  *
  * Note: Both the remote services run under the SYSTEM user, as we cannot have separate instance of
@@ -90,7 +92,7 @@
     protected final Object mLock = new Object();
 
 
-    private RemoteOnDeviceTrustedInferenceService mRemoteInferenceService;
+    private RemoteOnDeviceSandboxedInferenceService mRemoteInferenceService;
     private RemoteOnDeviceIntelligenceService mRemoteOnDeviceIntelligenceService;
     volatile boolean mIsServiceEnabled;
 
@@ -165,7 +167,7 @@
             }
             ensureRemoteIntelligenceServiceInitialized();
             mRemoteOnDeviceIntelligenceService.post(
-                    service -> service.getFeature(id, featureCallback));
+                    service -> service.getFeature(Binder.getCallingUid(), id, featureCallback));
         }
 
         @Override
@@ -185,7 +187,7 @@
             }
             ensureRemoteIntelligenceServiceInitialized();
             mRemoteOnDeviceIntelligenceService.post(
-                    service -> service.listFeatures(listFeaturesCallback));
+                    service -> service.listFeatures(Binder.getCallingUid(), listFeaturesCallback));
         }
 
         @Override
@@ -207,7 +209,8 @@
             }
             ensureRemoteIntelligenceServiceInitialized();
             mRemoteOnDeviceIntelligenceService.post(
-                    service -> service.getFeatureDetails(feature, featureDetailsCallback));
+                    service -> service.getFeatureDetails(Binder.getCallingUid(), feature,
+                            featureDetailsCallback));
         }
 
         @Override
@@ -227,33 +230,35 @@
             }
             ensureRemoteIntelligenceServiceInitialized();
             mRemoteOnDeviceIntelligenceService.post(
-                    service -> service.requestFeatureDownload(feature, cancellationSignal,
+                    service -> service.requestFeatureDownload(Binder.getCallingUid(), feature,
+                            cancellationSignal,
                             downloadCallback));
         }
 
 
         @Override
-        public void requestTokenCount(Feature feature,
+        public void requestTokenInfo(Feature feature,
                 Content request, ICancellationSignal cancellationSignal,
-                ITokenCountCallback tokenCountcallback) throws RemoteException {
+                ITokenInfoCallback tokenInfoCallback) throws RemoteException {
             Slog.i(TAG, "OnDeviceIntelligenceManagerInternal prepareFeatureProcessing");
             Objects.requireNonNull(feature);
             Objects.requireNonNull(request);
-            Objects.requireNonNull(tokenCountcallback);
+            Objects.requireNonNull(tokenInfoCallback);
 
             mContext.enforceCallingOrSelfPermission(
                     Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
             if (!mIsServiceEnabled) {
                 Slog.w(TAG, "Service not available");
-                tokenCountcallback.onFailure(
+                tokenInfoCallback.onFailure(
                         OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
                         "OnDeviceIntelligenceManagerService is unavailable",
                         new PersistableBundle());
             }
-            ensureRemoteTrustedInferenceServiceInitialized();
+            ensureRemoteInferenceServiceInitialized();
             mRemoteInferenceService.post(
-                    service -> service.requestTokenCount(feature, request, cancellationSignal,
-                            tokenCountcallback));
+                    service -> service.requestTokenInfo(Binder.getCallingUid(), feature, request,
+                            cancellationSignal,
+                            tokenInfoCallback));
         }
 
         @Override
@@ -267,7 +272,6 @@
             Slog.i(TAG, "OnDeviceIntelligenceManagerInternal processRequest");
             Objects.requireNonNull(feature);
             Objects.requireNonNull(responseCallback);
-            Objects.requireNonNull(request);
             mContext.enforceCallingOrSelfPermission(
                     Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
             if (!mIsServiceEnabled) {
@@ -277,9 +281,10 @@
                         "OnDeviceIntelligenceManagerService is unavailable",
                         new PersistableBundle());
             }
-            ensureRemoteTrustedInferenceServiceInitialized();
+            ensureRemoteInferenceServiceInitialized();
             mRemoteInferenceService.post(
-                    service -> service.processRequest(feature, request, requestType,
+                    service -> service.processRequest(Binder.getCallingUid(), feature, request,
+                            requestType,
                             cancellationSignal, processingSignal,
                             responseCallback));
         }
@@ -293,7 +298,6 @@
                 IStreamingResponseCallback streamingCallback) throws RemoteException {
             Slog.i(TAG, "OnDeviceIntelligenceManagerInternal processRequestStreaming");
             Objects.requireNonNull(feature);
-            Objects.requireNonNull(request);
             Objects.requireNonNull(streamingCallback);
             mContext.enforceCallingOrSelfPermission(
                     Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
@@ -304,9 +308,10 @@
                         "OnDeviceIntelligenceManagerService is unavailable",
                         new PersistableBundle());
             }
-            ensureRemoteTrustedInferenceServiceInitialized();
+            ensureRemoteInferenceServiceInitialized();
             mRemoteInferenceService.post(
-                    service -> service.processRequestStreaming(feature, request, requestType,
+                    service -> service.processRequestStreaming(Binder.getCallingUid(), feature,
+                            request, requestType,
                             cancellationSignal, processingSignal,
                             streamingCallback));
         }
@@ -346,7 +351,7 @@
                     Bundle processingState,
                     IProcessingUpdateStatusCallback callback) {
                 try {
-                    ensureRemoteTrustedInferenceServiceInitialized();
+                    ensureRemoteInferenceServiceInitialized();
                     mRemoteInferenceService.post(
                             service -> service.updateProcessingState(
                                     processingState, callback));
@@ -363,22 +368,24 @@
         };
     }
 
-    private void ensureRemoteTrustedInferenceServiceInitialized() throws RemoteException {
+    private void ensureRemoteInferenceServiceInitialized() throws RemoteException {
         synchronized (mLock) {
             if (mRemoteInferenceService == null) {
                 String serviceName = mContext.getResources().getString(
-                        R.string.config_defaultOnDeviceTrustedInferenceService);
+                        R.string.config_defaultOnDeviceSandboxedInferenceService);
                 validateService(serviceName, true);
-                mRemoteInferenceService = new RemoteOnDeviceTrustedInferenceService(mContext,
+                mRemoteInferenceService = new RemoteOnDeviceSandboxedInferenceService(mContext,
                         ComponentName.unflattenFromString(serviceName),
                         UserHandle.SYSTEM.getIdentifier());
                 mRemoteInferenceService.setServiceLifecycleCallbacks(
                         new ServiceConnector.ServiceLifecycleCallbacks<>() {
                             @Override
                             public void onConnected(
-                                    @NonNull IOnDeviceTrustedInferenceService service) {
+                                    @NonNull IOnDeviceSandboxedInferenceService service) {
                                 try {
                                     ensureRemoteIntelligenceServiceInitialized();
+                                    mRemoteOnDeviceIntelligenceService.post(
+                                            intelligenceService -> intelligenceService.notifyInferenceServiceConnected());
                                     service.registerRemoteStorageService(
                                             getIRemoteStorageService());
                                 } catch (RemoteException ex) {
@@ -433,7 +440,7 @@
             }
 
             checkServiceRequiresPermission(serviceInfo,
-                    Manifest.permission.BIND_ON_DEVICE_TRUSTED_SERVICE);
+                    Manifest.permission.BIND_ON_DEVICE_SANDBOXED_INFERENCE_SERVICE);
             if (!isIsolatedService(serviceInfo)) {
                 throw new SecurityException(
                         "Call required an isolated service, but the configured service: "
diff --git a/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceTrustedInferenceService.java b/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java
similarity index 73%
rename from services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceTrustedInferenceService.java
rename to services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java
index cc8e788..69ba1d2 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceTrustedInferenceService.java
+++ b/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java
@@ -22,18 +22,18 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.service.ondeviceintelligence.IOnDeviceTrustedInferenceService;
-import android.service.ondeviceintelligence.OnDeviceTrustedInferenceService;
+import android.service.ondeviceintelligence.IOnDeviceSandboxedInferenceService;
+import android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService;
 
 import com.android.internal.infra.ServiceConnector;
 
 
 /**
- * Manages the connection to the remote on-device trusted inference service. Also, handles unbinding
+ * Manages the connection to the remote on-device sand boxed inference service. Also, handles unbinding
  * logic set by the service implementation via a SecureSettings flag.
  */
-public class RemoteOnDeviceTrustedInferenceService extends
-        ServiceConnector.Impl<IOnDeviceTrustedInferenceService> {
+public class RemoteOnDeviceSandboxedInferenceService extends
+        ServiceConnector.Impl<IOnDeviceSandboxedInferenceService> {
     /**
      * Creates an instance of {@link ServiceConnector}
      *
@@ -43,12 +43,12 @@
      *                {@link Context#unbindService unbinding}
      * @param userId  to be used for {@link Context#bindServiceAsUser binding}
      */
-    RemoteOnDeviceTrustedInferenceService(Context context, ComponentName serviceName,
+    RemoteOnDeviceSandboxedInferenceService(Context context, ComponentName serviceName,
             int userId) {
         super(context, new Intent(
-                        OnDeviceTrustedInferenceService.SERVICE_INTERFACE).setComponent(serviceName),
+                        OnDeviceSandboxedInferenceService.SERVICE_INTERFACE).setComponent(serviceName),
                 BIND_FOREGROUND_SERVICE | BIND_INCLUDE_CAPABILITIES, userId,
-                IOnDeviceTrustedInferenceService.Stub::asInterface);
+                IOnDeviceSandboxedInferenceService.Stub::asInterface);
 
         // Bind right away
         connect();
diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java
index 23d48e8..9af2b3f 100644
--- a/services/core/java/com/android/server/pm/BroadcastHelper.java
+++ b/services/core/java/com/android/server/pm/BroadcastHelper.java
@@ -389,7 +389,8 @@
      */
     boolean canLauncherAccessProfile(ComponentName launcherComponent, int userId) {
         if (android.os.Flags.allowPrivateProfile()
-                && Flags.enablePermissionToAccessHiddenProfiles()) {
+                && Flags.enablePermissionToAccessHiddenProfiles()
+                && Flags.enablePrivateSpaceFeatures()) {
             if (mUmInternal.getUserProperties(userId).getProfileApiVisibility()
                     != UserProperties.PROFILE_API_VISIBILITY_HIDDEN) {
                 return true;
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 6b56b85..c7ebb3c 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -584,7 +584,8 @@
             return android.os.Flags.allowPrivateProfile()
                     && Flags.enableHidingProfiles()
                     && Flags.enableLauncherAppsHiddenProfileChecks()
-                    && Flags.enablePermissionToAccessHiddenProfiles();
+                    && Flags.enablePermissionToAccessHiddenProfiles()
+                    && Flags.enablePrivateSpaceFeatures();
         }
 
         @VisibleForTesting // We override it in unit tests
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index ef8453d..29de26e 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -31,6 +31,7 @@
 import static android.content.pm.PackageManager.DELETE_ALL_USERS;
 import static android.content.pm.PackageManager.DELETE_ARCHIVE;
 import static android.content.pm.PackageManager.DELETE_KEEP_DATA;
+import static android.content.pm.PackageManager.INSTALL_UNARCHIVE;
 import static android.content.pm.PackageManager.INSTALL_UNARCHIVE_DRAFT;
 import static android.graphics.drawable.AdaptiveIconDrawable.getExtraInsetFraction;
 import static android.os.PowerExemptionManager.REASON_PACKAGE_UNARCHIVE;
@@ -754,8 +755,9 @@
 
         int draftSessionId;
         try {
-            draftSessionId = Binder.withCleanCallingIdentity(() ->
-                    createDraftSession(packageName, installerPackage, statusReceiver, userId));
+            draftSessionId = Binder.withCleanCallingIdentity(
+                    () -> createDraftSession(packageName, installerPackage, callerPackageName,
+                            statusReceiver, userId));
         } catch (RuntimeException e) {
             if (e.getCause() instanceof IOException) {
                 throw ExceptionUtils.wrap((IOException) e.getCause());
@@ -795,11 +797,18 @@
     }
 
     private int createDraftSession(String packageName, String installerPackage,
+            String callerPackageName,
             IntentSender statusReceiver, int userId) throws IOException {
         PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(
                 PackageInstaller.SessionParams.MODE_FULL_INSTALL);
         sessionParams.setAppPackageName(packageName);
-        sessionParams.installFlags = INSTALL_UNARCHIVE_DRAFT;
+        sessionParams.setAppLabel(
+                mContext.getString(com.android.internal.R.string.unarchival_session_app_label));
+        sessionParams.setAppIcon(
+                getArchivedAppIcon(packageName, UserHandle.of(userId), callerPackageName));
+        // To make sure SessionInfo::isUnarchival returns true for draft sessions,
+        // INSTALL_UNARCHIVE is also set.
+        sessionParams.installFlags = (INSTALL_UNARCHIVE_DRAFT | INSTALL_UNARCHIVE);
 
         int installerUid = mPm.snapshotComputer().getPackageUid(installerPackage, 0, userId);
         // Handles case of repeated unarchival calls for the same package.
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 211b754..4c653f6 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -2833,7 +2833,8 @@
 
     @VisibleForTesting
     boolean areShortcutsSupportedOnHomeScreen(@UserIdInt int userId) {
-        if (!android.os.Flags.allowPrivateProfile() || !Flags.disablePrivateSpaceItemsOnHome()) {
+        if (!android.os.Flags.allowPrivateProfile() || !Flags.disablePrivateSpaceItemsOnHome()
+                || !android.multiuser.Flags.enablePrivateSpaceFeatures()) {
             return true;
         }
         final long start = getStatStartTime();
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 7349755..88e7596 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -21,10 +21,15 @@
 import static android.content.Intent.EXTRA_USER_ID;
 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.pm.PackageManager.FEATURE_AUTOMOTIVE;
+import static android.content.pm.PackageManager.FEATURE_EMBEDDED;
+import static android.content.pm.PackageManager.FEATURE_LEANBACK;
+import static android.content.pm.PackageManager.FEATURE_WATCH;
 import static android.os.UserManager.DEV_CREATE_OVERRIDE_PROPERTY;
 import static android.os.UserManager.DISALLOW_USER_SWITCH;
 import static android.os.UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY;
 import static android.os.UserManager.USER_OPERATION_ERROR_UNKNOWN;
+import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE;
 
 import static com.android.internal.app.SetScreenLockDialogActivity.EXTRA_ORIGIN_USER_ID;
 import static com.android.internal.app.SetScreenLockDialogActivity.LAUNCH_REASON_DISABLE_QUIET_MODE;
@@ -1006,9 +1011,17 @@
         emulateSystemUserModeIfNeeded();
     }
 
+    private boolean doesDeviceHardwareSupportPrivateSpace() {
+        return !mPm.hasSystemFeature(FEATURE_EMBEDDED, 0)
+                && !mPm.hasSystemFeature(FEATURE_WATCH, 0)
+                && !mPm.hasSystemFeature(FEATURE_LEANBACK, 0)
+                && !mPm.hasSystemFeature(FEATURE_AUTOMOTIVE, 0);
+    }
+
     private static boolean isAutoLockForPrivateSpaceEnabled() {
         return android.os.Flags.allowPrivateProfile()
-                && Flags.supportAutolockForPrivateSpace();
+                && Flags.supportAutolockForPrivateSpace()
+                && android.multiuser.Flags.enablePrivateSpaceFeatures();
     }
 
     void systemReady() {
@@ -1052,7 +1065,8 @@
 
     private boolean isAutoLockingPrivateSpaceOnRestartsEnabled() {
         return android.os.Flags.allowPrivateProfile()
-                && android.multiuser.Flags.enablePrivateSpaceAutolockOnRestarts();
+                && android.multiuser.Flags.enablePrivateSpaceAutolockOnRestarts()
+                && android.multiuser.Flags.enablePrivateSpaceFeatures();
     }
 
     /**
@@ -1493,7 +1507,8 @@
     private boolean isProfileHidden(int userId) {
         UserProperties userProperties = getUserPropertiesCopy(userId);
         if (android.os.Flags.allowPrivateProfile()
-                && android.multiuser.Flags.enableHidingProfiles()) {
+                && android.multiuser.Flags.enableHidingProfiles()
+                && android.multiuser.Flags.enablePrivateSpaceFeatures()) {
             return userProperties.getProfileApiVisibility()
                     == UserProperties.PROFILE_API_VISIBILITY_HIDDEN;
         }
@@ -1693,7 +1708,8 @@
                 setQuietModeEnabled(userId, true /* enableQuietMode */, target, callingPackage);
                 return true;
             }
-            if (android.os.Flags.allowPrivateProfile()) {
+            if (android.os.Flags.allowPrivateProfile()
+                    && android.multiuser.Flags.enablePrivateSpaceFeatures()) {
                 final UserProperties userProperties = getUserPropertiesInternal(userId);
                 if (userProperties != null
                         && userProperties.isAuthAlwaysRequiredToDisableQuietMode()) {
@@ -1839,7 +1855,8 @@
         logQuietModeEnabled(userId, enableQuietMode, callingPackage);
 
         // Broadcast generic intents for all profiles
-        if (android.os.Flags.allowPrivateProfile()) {
+        if (android.os.Flags.allowPrivateProfile()
+                && android.multiuser.Flags.enablePrivateSpaceFeatures()) {
             broadcastProfileAvailabilityChanges(profile, parent.getUserHandle(),
                     enableQuietMode, false);
         }
@@ -1852,7 +1869,8 @@
 
     private void stopUserForQuietMode(int userId) throws RemoteException {
         if (android.os.Flags.allowPrivateProfile()
-                && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()) {
+                && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()
+                && android.multiuser.Flags.enablePrivateSpaceFeatures()) {
             // Allow delayed locking since some profile types want to be able to unlock again via
             // biometrics.
             ActivityManager.getService()
@@ -2751,6 +2769,18 @@
     }
 
     @Override
+    public boolean canAddPrivateProfile(@UserIdInt int userId) {
+        checkCreateUsersPermission("canHaveRestrictedProfile");
+        UserInfo parentUserInfo = getUserInfo(userId);
+        return isUserTypeEnabled(USER_TYPE_PROFILE_PRIVATE)
+                && canAddMoreProfilesToUser(USER_TYPE_PROFILE_PRIVATE,
+                    userId, /* allowedToRemoveOne */ false)
+                && (parentUserInfo != null && parentUserInfo.isMain())
+                && doesDeviceHardwareSupportPrivateSpace()
+                && !hasUserRestriction(UserManager.DISALLOW_ADD_PRIVATE_PROFILE, userId);
+    }
+
+    @Override
     public boolean hasRestrictedProfiles(@UserIdInt int userId) {
         checkManageUsersPermission("hasRestrictedProfiles");
         synchronized (mUsersLock) {
@@ -5308,7 +5338,7 @@
         if (!isUserTypeEnabled(userTypeDetails)) {
             throwCheckedUserOperationException(
                     "Cannot add a user of disabled type " + userType + ".",
-                    UserManager.USER_OPERATION_ERROR_MAX_USERS);
+                    UserManager.USER_OPERATION_ERROR_DISABLED_USER);
         }
 
         synchronized (mUsersLock) {
@@ -5341,6 +5371,7 @@
         final boolean isDemo = UserManager.isUserTypeDemo(userType);
         final boolean isManagedProfile = UserManager.isUserTypeManagedProfile(userType);
         final boolean isCommunalProfile = UserManager.isUserTypeCommunalProfile(userType);
+        final boolean isPrivateProfile = UserManager.isUserTypePrivateProfile(userType);
 
         final long ident = Binder.clearCallingIdentity();
         UserInfo userInfo;
@@ -5387,6 +5418,12 @@
                                     + " for user " + parentId,
                             UserManager.USER_OPERATION_ERROR_MAX_USERS);
                 }
+                if (android.multiuser.Flags.blockPrivateSpaceCreation()
+                        && isPrivateProfile && !canAddPrivateProfile(parentId)) {
+                    throwCheckedUserOperationException(
+                            "Cannot add profile of type " + userType + " for user " + parentId,
+                            UserManager.USER_OPERATION_ERROR_PRIVATE_PROFILE);
+                }
                 if (isRestricted && (parentId != UserHandle.USER_SYSTEM)
                         && !isCreationOverrideEnabled()) {
                     throwCheckedUserOperationException(
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index 114daaa..7f9c1cf 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -292,6 +292,7 @@
                 .setName(USER_TYPE_PROFILE_PRIVATE)
                 .setBaseType(FLAG_PROFILE)
                 .setMaxAllowedPerParent(1)
+                .setEnabled(UserManager.isPrivateProfileEnabled() ? 1 : 0)
                 .setLabels(R.string.profile_label_private)
                 .setIconBadge(com.android.internal.R.drawable.ic_private_profile_icon_badge)
                 .setBadgePlain(com.android.internal.R.drawable.ic_private_profile_badge)
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
index 305b087..5c8215e 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
@@ -16,6 +16,10 @@
 
 package com.android.server.pm.verify.domain;
 
+import static android.content.IntentFilter.WILDCARD;
+
+import static com.android.server.pm.verify.domain.DomainVerificationUtils.isValidDomain;
+
 import static java.util.Collections.emptyList;
 import static java.util.Collections.emptySet;
 
@@ -253,9 +257,18 @@
             Map<String, List<UriRelativeFilterGroup>> domainToGroupsMap =
                     pkgState.getUriRelativeFilterGroupMap();
             for (String domain : bundle.keySet()) {
+                if (!isValidDomain(domain)) {
+                    continue;
+                }
                 ArrayList<UriRelativeFilterGroupParcel> parcels =
                         bundle.getParcelableArrayList(domain, UriRelativeFilterGroupParcel.class);
-                domainToGroupsMap.put(domain, UriRelativeFilterGroup.parcelsToGroups(parcels));
+                List<UriRelativeFilterGroup> groups =
+                        UriRelativeFilterGroup.parcelsToGroups(parcels);
+                if (groups == null || groups.isEmpty()) {
+                    domainToGroupsMap.remove(domain);
+                } else {
+                    domainToGroupsMap.put(domain, groups);
+                }
             }
         }
     }
@@ -273,9 +286,11 @@
                 Map<String, List<UriRelativeFilterGroup>> map =
                         pkgState.getUriRelativeFilterGroupMap();
                 for (int i = 0; i < domains.size(); i++) {
-                    List<UriRelativeFilterGroup> groups = map.get(domains.get(i));
-                    bundle.putParcelableList(domains.get(i),
-                            UriRelativeFilterGroup.groupsToParcels(groups));
+                    if (map.containsKey(domains.get(i))) {
+                        List<UriRelativeFilterGroup> groups = map.get(domains.get(i));
+                        bundle.putParcelableList(domains.get(i),
+                                UriRelativeFilterGroup.groupsToParcels(groups));
+                    }
                 }
             }
         }
@@ -285,15 +300,29 @@
     @NonNull
     private List<UriRelativeFilterGroup> getUriRelativeFilterGroups(@NonNull String packageName,
             @NonNull String domain) {
-        List<UriRelativeFilterGroup> groups = Collections.emptyList();
+        List<UriRelativeFilterGroup> groups;
         synchronized (mLock) {
             DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
             if (pkgState != null) {
-                groups = pkgState.getUriRelativeFilterGroupMap().getOrDefault(domain,
-                        Collections.emptyList());
+                Map<String, List<UriRelativeFilterGroup>> groupMap =
+                        pkgState.getUriRelativeFilterGroupMap();
+                groups = groupMap.get(domain);
+                if (groups != null) {
+                    return groups;
+                }
+                int first = domain.indexOf(".");
+                int second = domain.indexOf('.', first + 1);
+                while (first > 0 && second > 0) {
+                    groups = groupMap.get(WILDCARD + domain.substring(first));
+                    if (groups != null) {
+                        return groups;
+                    }
+                    first = second;
+                    second = domain.indexOf('.', second + 1);
+                }
             }
         }
-        return groups;
+        return Collections.emptyList();
     }
 
     @NonNull
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java
index 3fd00c6..b8c4d22 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java
@@ -35,6 +35,9 @@
 
 public final class DomainVerificationUtils {
 
+    public static final int MAX_DOMAIN_LENGTH = 254;
+    public static final int MAX_DOMAIN_LABEL_LENGTH = 63;
+
     private static final ThreadLocal<Matcher> sCachedMatcher = ThreadLocal.withInitial(
             () -> Patterns.DOMAIN_NAME.matcher(""));
 
@@ -108,4 +111,41 @@
         appInfo.targetSdkVersion = pkg.getTargetSdkVersion();
         return appInfo;
     }
+
+    static boolean isValidDomain(String domain) {
+        if (domain.length() > MAX_DOMAIN_LENGTH || domain.equals("*")) {
+            return false;
+        }
+        if (domain.charAt(0) == '*') {
+            if (domain.charAt(1) != '.') {
+                return false;
+            }
+            domain = domain.substring(2);
+        }
+        int labels = 1;
+        int labelStart = -1;
+        for (int i = 0; i < domain.length(); i++) {
+            char c = domain.charAt(i);
+            if (c == '.') {
+                int labelLength = i - labelStart - 1;
+                if (labelLength == 0 || labelLength > MAX_DOMAIN_LABEL_LENGTH) {
+                    return false;
+                }
+                labelStart = i;
+                labels += 1;
+            } else if (!isValidDomainChar(c)) {
+                return false;
+            }
+        }
+        int lastLabelLength = domain.length() - labelStart - 1;
+        if (lastLabelLength == 0 || lastLabelLength > 63) {
+            return false;
+        }
+        return labels > 1;
+    }
+
+    private static boolean isValidDomainChar(char c) {
+        return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
+                || (c >= '0' && c <= '9') || c == '-';
+    }
 }
diff --git a/services/core/java/com/android/server/power/ThermalManagerService.java b/services/core/java/com/android/server/power/ThermalManagerService.java
index 24d7acd..5360788 100644
--- a/services/core/java/com/android/server/power/ThermalManagerService.java
+++ b/services/core/java/com/android/server/power/ThermalManagerService.java
@@ -700,6 +700,8 @@
                     return runOverrideStatus();
                 case "reset":
                     return runReset();
+                case "headroom":
+                    return runHeadroom();
                 default:
                     return handleDefaultCommands(cmd);
             }
@@ -862,6 +864,36 @@
             }
         }
 
+        private int runHeadroom() {
+            final long token = Binder.clearCallingIdentity();
+            try {
+                final PrintWriter pw = getOutPrintWriter();
+                int forecastSecs;
+                try {
+                    forecastSecs = Integer.parseInt(getNextArgRequired());
+                } catch (RuntimeException ex) {
+                    pw.println("Error: " + ex);
+                    return -1;
+                }
+                if (!mHalReady.get()) {
+                    pw.println("Error: thermal HAL is not ready");
+                    return -1;
+                }
+
+                if (forecastSecs < MIN_FORECAST_SEC || forecastSecs > MAX_FORECAST_SEC) {
+                    pw.println(
+                            "Error: forecast second input should be in range [" + MIN_FORECAST_SEC
+                                    + "," + MAX_FORECAST_SEC + "]");
+                    return -1;
+                }
+                float headroom = mTemperatureWatcher.getForecast(forecastSecs);
+                pw.println("Headroom in " + forecastSecs + " seconds: " + headroom);
+                return 0;
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
         @Override
         public void onHelp() {
             final PrintWriter pw = getOutPrintWriter();
@@ -877,6 +909,9 @@
             pw.println("    status code is defined in android.os.Temperature.");
             pw.println("  reset");
             pw.println("    unlocks the thermal status of the device.");
+            pw.println("  headroom FORECAST_SECONDS");
+            pw.println("    gets the thermal headroom forecast in specified seconds, from ["
+                    + MIN_FORECAST_SEC + "," + MAX_FORECAST_SEC + "].");
             pw.println();
         }
     }
diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
index ffce50e..79adcb4 100644
--- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
@@ -349,7 +349,6 @@
             }
         }
 
-        userState.mIAppMap.clear();
         userState.mAdServiceMap = adServiceMap;
     }
 
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index 5b77433..2fc183d 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -532,7 +532,7 @@
             return false;
         }
 
-        if (Flags.keyboardCategoryEnabled()) {
+        if (Flags.keyboardCategoryEnabled() && mVibrationConfig.hasFixedKeyboardAmplitude()) {
             int category = callerInfo.attrs.getCategory();
             if (usage == USAGE_TOUCH && category == CATEGORY_KEYBOARD) {
                 // Keyboard touch has a different user setting.
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
index 9616c28..5175b74 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
@@ -131,19 +131,23 @@
                     (bitmapSize.y - crop.height()) / 2);
             return crop;
         }
+
+        // If any suggested crop is invalid, fallback to case 1
+        for (int i = 0; i < suggestedCrops.size(); i++) {
+            Rect testCrop = suggestedCrops.valueAt(i);
+            if (testCrop == null || testCrop.left < 0 || testCrop.top < 0
+                    || testCrop.right > bitmapSize.x || testCrop.bottom > bitmapSize.y) {
+                Slog.w(TAG, "invalid crop: " + testCrop + " for bitmap size: " + bitmapSize);
+                return getCrop(displaySize, bitmapSize, new SparseArray<>(), rtl);
+            }
+        }
+
         int orientation = getOrientation(displaySize);
 
         // Case 2: if the orientation exists in the suggested crops, adjust the suggested crop
         Rect suggestedCrop = suggestedCrops.get(orientation);
         if (suggestedCrop != null) {
-            if (suggestedCrop.left < 0 || suggestedCrop.top < 0
-                    || suggestedCrop.right > bitmapSize.x || suggestedCrop.bottom > bitmapSize.y) {
-                Slog.w(TAG, "invalid suggested crop: " + suggestedCrop);
-                Rect fullImage = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
-                return getAdjustedCrop(fullImage, bitmapSize, displaySize, true, rtl, ADD);
-            } else {
                 return getAdjustedCrop(suggestedCrop, bitmapSize, displaySize, true, rtl, ADD);
-            }
         }
 
         // Case 3: if we have the 90° rotated orientation in the suggested crops, reuse it and
@@ -247,6 +251,7 @@
         Rect adjustedCrop = new Rect(crop);
         float cropRatio = ((float) crop.width()) / crop.height();
         float screenRatio = ((float) screenSize.x) / screenSize.y;
+        if (cropRatio == screenRatio) return crop;
         if (cropRatio > screenRatio) {
             if (!parallax) {
                 // rotate everything 90 degrees clockwise, compute the result, and rotate back
@@ -276,6 +281,7 @@
                 }
             }
         } else {
+            // TODO (b/281648899) the third case is not always correct, fix that.
             int widthToAdd = mode == REMOVE ? 0
                     : mode == ADD ? (int) (0.5 + crop.height() * screenRatio - crop.width())
                     : (int) (0.5 + crop.height() - crop.width());
@@ -646,6 +652,9 @@
         if (!success) {
             Slog.e(TAG, "Unable to apply new wallpaper");
             wallpaper.getCropFile().delete();
+            wallpaper.mCropHints.clear();
+            wallpaper.cropHint.set(0, 0, 0, 0);
+            wallpaper.mSampleSize = 1f;
         }
 
         if (wallpaper.getCropFile().exists()) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
index 88e9672..0165d65 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
@@ -341,6 +341,7 @@
             } else {
                 wallpaper.cropHint.set(totalCropHint);
             }
+            wallpaper.mSampleSize = parser.getAttributeFloat(null, "sampleSize", 1f);
         } else {
             wallpaper.cropHint.set(totalCropHint);
         }
@@ -493,6 +494,7 @@
             out.attributeInt(null, "totalCropTop", wallpaper.cropHint.top);
             out.attributeInt(null, "totalCropRight", wallpaper.cropHint.right);
             out.attributeInt(null, "totalCropBottom", wallpaper.cropHint.bottom);
+            out.attributeFloat(null, "sampleSize", wallpaper.mSampleSize);
         } else if (!multiCrop()) {
             final DisplayData wpdData =
                     mWallpaperDisplayHelper.getDisplayDataOrCreate(DEFAULT_DISPLAY);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index d30a216..7ba953d 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -658,7 +658,7 @@
 
     boolean mVoiceInteraction;
 
-    private int mPendingRelaunchCount;
+    int mPendingRelaunchCount;
     long mRelaunchStartTime;
 
     // True if we are current in the process of removing this app token from the display
@@ -3988,7 +3988,7 @@
         // If the display does not have running activity, the configuration may need to be
         // updated for restoring original orientation of the display.
         if (next == null) {
-            mRootWindowContainer.ensureVisibilityAndConfig(next, getDisplayId(),
+            mRootWindowContainer.ensureVisibilityAndConfig(null /* starting */, mDisplayContent,
                     true /* deferResume */);
         }
         if (activityRemoved) {
@@ -6463,12 +6463,6 @@
      * state to match that fact.
      */
     void completeResumeLocked() {
-        final boolean wasVisible = mVisibleRequested;
-        setVisibility(true);
-        if (!wasVisible) {
-            // Visibility has changed, so take a note of it so we call the TaskStackChangedListener
-            mTaskSupervisor.mAppVisibilitiesChangedSinceLastPause = true;
-        }
         idle = false;
         results = null;
         if (newIntents != null && newIntents.size() > 0) {
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 6ad056f..2c39c58 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1625,7 +1625,7 @@
         final ActivityRecord currentTop = startedActivityRootTask.topRunningActivity();
         if (currentTop != null && currentTop.shouldUpdateConfigForDisplayChanged()) {
             mRootWindowContainer.ensureVisibilityAndConfig(
-                    currentTop, currentTop.getDisplayId(), false /* deferResume */);
+                    currentTop, currentTop.mDisplayContent, false /* deferResume */);
         }
 
         if (!avoidMoveToFront() && mDoResume && mRootWindowContainer
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 2cda1f5..826e332 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -842,7 +842,7 @@
                 // Deferring resume here because we're going to launch new activity shortly.
                 // We don't want to perform a redundant launch of the same record while ensuring
                 // configurations and trying to resume top activity of focused root task.
-                mRootWindowContainer.ensureVisibilityAndConfig(r, r.getDisplayId(),
+                mRootWindowContainer.ensureVisibilityAndConfig(r, r.mDisplayContent,
                         true /* deferResume */);
             }
 
@@ -1011,7 +1011,8 @@
         if (andResume && readyToResume()) {
             // As part of the process of launching, ActivityThread also performs
             // a resume.
-            rootTask.minimalResumeActivityLocked(r);
+            r.setState(RESUMED, "realStartActivityLocked");
+            r.completeResumeLocked();
         } else {
             // This activity is not starting in the resumed state... which should look like we asked
             // it to pause+stop (but remain visible), and it has done so and reported back the
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 07a03eb..a75d3b6 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1742,30 +1742,21 @@
      *
      * @param starting                  The currently starting activity or {@code null} if there is
      *                                  none.
-     * @param displayId                 The id of the display where operation is executed.
+     * @param displayContent            The display where the operation is executed.
      * @param deferResume               Whether to defer resume while updating config.
-     * @return 'true' if starting activity was kept or wasn't provided, 'false' if it was relaunched
-     * because of configuration update.
      */
-    boolean ensureVisibilityAndConfig(ActivityRecord starting, int displayId, boolean deferResume) {
+    void ensureVisibilityAndConfig(@Nullable ActivityRecord starting,
+            @NonNull DisplayContent displayContent, boolean deferResume) {
         // First ensure visibility without updating the config just yet. We need this to know what
         // activities are affecting configuration now.
         // Passing null here for 'starting' param value, so that visibility of actual starting
         // activity will be properly updated.
         ensureActivitiesVisible(null /* starting */, false /* notifyClients */);
 
-        if (displayId == INVALID_DISPLAY) {
-            // The caller didn't provide a valid display id, skip updating config.
-            return true;
-        }
-
         // Force-update the orientation from the WindowManager, since we need the true configuration
         // to send to the client now.
-        final DisplayContent displayContent = getDisplayContent(displayId);
-        Configuration config = null;
-        if (displayContent != null) {
-            config = displayContent.updateOrientation(starting, true /* forceUpdate */);
-        }
+        final Configuration config =
+                displayContent.updateOrientation(starting, true /* forceUpdate */);
         // Visibilities may change so let the starting activity have a chance to report. Can't do it
         // when visibility is changed in each AppWindowToken because it may trigger wrong
         // configuration push because the visibility of some activities may not be updated yet.
@@ -1773,13 +1764,8 @@
             starting.reportDescendantOrientationChangeIfNeeded();
         }
 
-        if (displayContent != null) {
-            // Update the configuration of the activities on the display.
-            return displayContent.updateDisplayOverrideConfigurationLocked(config, starting,
-                    deferResume);
-        } else {
-            return true;
-        }
+        // Update the configuration of the activities on the display.
+        displayContent.updateDisplayOverrideConfigurationLocked(config, starting, deferResume);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index e16d869..73aa307 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -978,7 +978,6 @@
         }
         effectiveUid = info.applicationInfo.uid;
         mIsEffectivelySystemApp = info.applicationInfo.isSystemApp();
-        stringName = null;
 
         if (info.targetActivity == null) {
             if (_intent != null) {
@@ -1045,6 +1044,7 @@
             updateTaskDescription();
         }
         mSupportsPictureInPicture = info.supportsPictureInPicture();
+        stringName = null;
 
         // Re-adding the task to Recents once updated
         if (inRecents) {
@@ -4948,13 +4948,6 @@
         }
     }
 
-    void minimalResumeActivityLocked(ActivityRecord r) {
-        ProtoLog.v(WM_DEBUG_STATES, "Moving to RESUMED: %s (starting new instance) "
-                + "callers=%s", r, Debug.getCallers(5));
-        r.setState(RESUMED, "minimalResumeActivityLocked");
-        r.completeResumeLocked();
-    }
-
     void checkReadyForSleep() {
         if (shouldSleepActivities() && goToSleepIfPossible(false /* shuttingDown */)) {
             mTaskSupervisor.checkReadyForSleepLocked(true /* allowDelay */);
@@ -5863,7 +5856,7 @@
             }
 
             mRootWindowContainer.ensureVisibilityAndConfig(null /* starting */,
-                    mDisplayContent.mDisplayId, false /* deferResume */);
+                    mDisplayContent, false /* deferResume */);
         } finally {
             if (mTransitionController.isShellTransitionsEnabled()) {
                 mAtmService.continueWindowLayout();
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index a818a72..597e901 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1536,10 +1536,6 @@
 
             next.setState(RESUMED, "resumeTopActivity");
 
-            // Have the window manager re-evaluate the orientation of
-            // the screen based on the new activity order.
-            boolean notUpdated = true;
-
             // Activity should also be visible if set mLaunchTaskBehind to true (see
             // ActivityRecord#shouldBeVisibleIgnoringKeyguard()).
             if (shouldBeVisible(next)) {
@@ -1551,28 +1547,15 @@
                 // result of invisible window resize.
                 // TODO: Remove this once visibilities are set correctly immediately when
                 // starting an activity.
-                notUpdated = !mRootWindowContainer.ensureVisibilityAndConfig(next, getDisplayId(),
+                final int originalRelaunchingCount = next.mPendingRelaunchCount;
+                mRootWindowContainer.ensureVisibilityAndConfig(next, mDisplayContent,
                         false /* deferResume */);
-            }
-
-            if (notUpdated) {
-                // The configuration update wasn't able to keep the existing
-                // instance of the activity, and instead started a new one.
-                // We should be all done, but let's just make sure our activity
-                // is still at the top and schedule another run if something
-                // weird happened.
-                ActivityRecord nextNext = topRunningActivity();
-                ProtoLog.i(WM_DEBUG_STATES, "Activity config changed during resume: "
-                        + "%s, new next: %s", next, nextNext);
-                if (nextNext != next) {
-                    // Do over!
-                    mTaskSupervisor.scheduleResumeTopActivities();
+                if (next.mPendingRelaunchCount > originalRelaunchingCount) {
+                    // The activity is scheduled to relaunch, then ResumeActivityItem will be also
+                    // included (see ActivityRecord#relaunchActivityLocked) if it should resume.
+                    next.completeResumeLocked();
+                    return true;
                 }
-                if (!next.isVisibleRequested() || next.mAppStopped) {
-                    next.setVisibility(true);
-                }
-                next.completeResumeLocked();
-                return true;
             }
 
             try {
@@ -1655,17 +1638,7 @@
                 return true;
             }
 
-            // From this point on, if something goes wrong there is no way
-            // to recover the activity.
-            try {
-                next.completeResumeLocked();
-            } catch (Exception e) {
-                // If any exception gets thrown, toss away this
-                // activity and try the next one.
-                Slog.w(TAG, "Exception thrown during resume of " + next, e);
-                next.finishIfPossible("resume-exception", true /* oomAdj */);
-                return true;
-            }
+            next.completeResumeLocked();
         } else {
             // Whoops, need to restart this activity!
             if (!next.hasBeenLaunched) {
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 7fc61e1..a84a99a 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -573,7 +573,7 @@
 
     // Capture the animation surface control for activity's main window
     static class StartingWindowAnimationAdaptor implements AnimationAdapter {
-        SurfaceControl mAnimationLeash;
+
         @Override
         public boolean getShowWallpaper() {
             return false;
@@ -582,14 +582,10 @@
         @Override
         public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t,
                 int type, @NonNull SurfaceAnimator.OnAnimationFinishedCallback finishCallback) {
-            mAnimationLeash = animationLeash;
         }
 
         @Override
         public void onAnimationCancelled(SurfaceControl animationLeash) {
-            if (mAnimationLeash == animationLeash) {
-                mAnimationLeash = null;
-            }
         }
 
         @Override
@@ -604,9 +600,6 @@
 
         @Override
         public void dump(PrintWriter pw, String prefix) {
-            pw.print(prefix + "StartingWindowAnimationAdaptor mCapturedLeash=");
-            pw.print(mAnimationLeash);
-            pw.println();
         }
 
         @Override
@@ -616,16 +609,16 @@
 
     static SurfaceControl applyStartingWindowAnimation(WindowState window) {
         final SurfaceControl.Transaction t = window.getPendingTransaction();
-        final Rect mainFrame = window.getRelativeFrame();
         final StartingWindowAnimationAdaptor adaptor = new StartingWindowAnimationAdaptor();
         window.startAnimation(t, adaptor, false, ANIMATION_TYPE_STARTING_REVEAL);
-        if (adaptor.mAnimationLeash == null) {
+        final SurfaceControl leash = window.getAnimationLeash();
+        if (leash == null) {
             Slog.e(TAG, "Cannot start starting window animation, the window " + window
                     + " was removed");
             return null;
         }
-        t.setPosition(adaptor.mAnimationLeash, mainFrame.left, mainFrame.top);
-        return adaptor.mAnimationLeash;
+        t.setPosition(leash, window.mSurfacePosition.x, window.mSurfacePosition.y);
+        return leash;
     }
 
     boolean addStartingWindow(Task task, ActivityRecord activity, int launchTheme,
@@ -696,7 +689,9 @@
                 removalInfo.roundedCornerRadius =
                         topActivity.mLetterboxUiController.getRoundedCornersRadius(mainWindow);
                 removalInfo.windowAnimationLeash = applyStartingWindowAnimation(mainWindow);
-                removalInfo.mainFrame = mainWindow.getRelativeFrame();
+                removalInfo.mainFrame = new Rect(mainWindow.getFrame());
+                removalInfo.mainFrame.offsetTo(mainWindow.mSurfacePosition.x,
+                        mainWindow.mSurfacePosition.y);
             }
         }
         try {
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 0b29f96..a6db310f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -49,6 +49,7 @@
 
 import com.android.internal.os.ByteTransferPipe;
 import com.android.internal.protolog.LegacyProtoLogImpl;
+import com.android.internal.protolog.PerfettoProtoLogImpl;
 import com.android.internal.protolog.common.IProtoLog;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.IoThread;
@@ -111,8 +112,13 @@
                 case "logging":
                     IProtoLog instance = ProtoLog.getSingleInstance();
                     int result = 0;
-                    if (instance instanceof LegacyProtoLogImpl) {
-                        result = ((LegacyProtoLogImpl) instance).onShellCommand(this);
+                    if (instance instanceof LegacyProtoLogImpl
+                            || instance instanceof PerfettoProtoLogImpl) {
+                        if (instance instanceof LegacyProtoLogImpl) {
+                            result = ((LegacyProtoLogImpl) instance).onShellCommand(this);
+                        } else {
+                            result = ((PerfettoProtoLogImpl) instance).onShellCommand(this);
+                        }
                         if (result != 0) {
                             pw.println("Not handled, please use "
                                     + "`adb shell dumpsys activity service SystemUIService "
@@ -120,8 +126,7 @@
                         }
                     } else {
                         result = -1;
-                        pw.println("Command not supported. "
-                                + "Only supported when using legacy ProtoLog.");
+                        pw.println("ProtoLog impl doesn't support handling commands");
                     }
                     return result;
                 case "user-rotation":
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 1106a95..46bac16 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -695,7 +695,8 @@
      */
     private boolean mDrawnStateEvaluated;
 
-    private final Point mSurfacePosition = new Point();
+    /** The surface position relative to the parent container. */
+    final Point mSurfacePosition = new Point();
 
     /**
      * A region inside of this window to be excluded from touch.
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
index 0b58543..b32c544 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
@@ -1112,6 +1112,8 @@
         )
         enforceCallingOrSelfAnyPermission(
             "getAllPermissionStates",
+            Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+            Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
             Manifest.permission.GET_RUNTIME_PERMISSIONS
         )
 
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
index 66e0717..c54a94e 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
@@ -83,11 +83,7 @@
         }
 
         val bundle = service.getUriRelativeFilterGroups(PKG_ONE, listOf(DOMAIN_1, DOMAIN_2))
-        assertThat(bundle.keySet()).containsExactlyElementsIn(listOf(DOMAIN_1, DOMAIN_2))
-        assertThat(bundle.getParcelableArrayList(DOMAIN_1, UriRelativeFilterGroup::class.java))
-            .isEmpty()
-        assertThat(bundle.getParcelableArrayList(DOMAIN_2, UriRelativeFilterGroup::class.java))
-            .isEmpty()
+        assertThat(bundle.keySet()).isEmpty()
 
         val pathGroup = UriRelativeFilterGroup(UriRelativeFilterGroup.ACTION_ALLOW)
         pathGroup.addUriRelativeFilter(
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java
index fbb14c3..4409051 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java
@@ -26,7 +26,6 @@
 import static org.mockito.Mockito.when;
 
 import android.hardware.display.DisplayManagerInternal;
-import android.os.PowerManager;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -66,8 +65,6 @@
         mSession.stopOffload();
 
         assertFalse(mSession.isActive());
-        verify(mDisplayPowerController).setBrightnessFromOffload(
-                PowerManager.BRIGHTNESS_INVALID_FLOAT);
 
         // An inactive session shouldn't be stopped again
         mSession.stopOffload();
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index e9315c8..14d8a9c 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -22,6 +22,7 @@
 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -77,6 +78,7 @@
 import com.android.server.am.BatteryStatsService;
 import com.android.server.display.RampAnimator.DualRampAnimator;
 import com.android.server.display.brightness.BrightnessEvent;
+import com.android.server.display.brightness.BrightnessReason;
 import com.android.server.display.brightness.clamper.BrightnessClamperController;
 import com.android.server.display.brightness.clamper.HdrClamper;
 import com.android.server.display.color.ColorDisplayService;
@@ -1559,6 +1561,43 @@
 
         verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
                 eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
+        assertEquals(BrightnessReason.REASON_OFFLOAD, mHolder.dpc.mBrightnessReason.getReason());
+    }
+
+    @Test
+    public void testBrightness_AutomaticHigherPriorityThanOffload() {
+        when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true);
+        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+        float brightness = 0.34f;
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
+                any(BrightnessEvent.class))).thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
+
+        mHolder.dpc.setBrightnessFromOffload(brightness);
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
+        assertEquals(BrightnessReason.REASON_OFFLOAD, mHolder.dpc.mBrightnessReason.getReason());
+
+        // Now automatic brightness becomes available
+        brightness = 0.22f;
+        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
+                any(BrightnessEvent.class))).thenReturn(brightness);
+
+        mHolder.dpc.updateBrightness();
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
+        assertEquals(BrightnessReason.REASON_AUTOMATIC, mHolder.dpc.mBrightnessReason.getReason());
     }
 
     @Test
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
index b99ecf3..14de527 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -46,7 +46,6 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
-import android.os.PowerManager;
 import android.view.Display;
 import android.view.DisplayAddress;
 import android.view.SurfaceControl;
@@ -1229,8 +1228,6 @@
 
         verify(mDisplayOffloader).stopOffload();
         assertFalse(mDisplayOffloadSession.isActive());
-        verify(mMockedDisplayPowerController).setBrightnessFromOffload(
-                PowerManager.BRIGHTNESS_INVALID_FLOAT);
     }
 
     private void initDisplayOffloadSession() {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
index 289d54b..9b6cc0a 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
@@ -393,7 +393,31 @@
         OffloadBrightnessStrategy offloadBrightnessStrategy = mock(OffloadBrightnessStrategy.class);
         when(mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy()).thenReturn(
                 offloadBrightnessStrategy);
-        mDisplayBrightnessController.setBrightnessFromOffload(brightness);
+        boolean brightnessUpdated =
+                mDisplayBrightnessController.setBrightnessFromOffload(brightness);
         verify(offloadBrightnessStrategy).setOffloadScreenBrightness(brightness);
+        assertTrue(brightnessUpdated);
+    }
+
+    @Test
+    public void setBrightnessFromOffload_OffloadStrategyNull() {
+        float brightness = 0.4f;
+        when(mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy()).thenReturn(null);
+        boolean brightnessUpdated =
+                mDisplayBrightnessController.setBrightnessFromOffload(brightness);
+        assertFalse(brightnessUpdated);
+    }
+
+    @Test
+    public void setBrightnessFromOffload_BrightnessUnchanged() {
+        float brightness = 0.4f;
+        OffloadBrightnessStrategy offloadBrightnessStrategy = mock(OffloadBrightnessStrategy.class);
+        when(offloadBrightnessStrategy.getOffloadScreenBrightness()).thenReturn(brightness);
+        when(mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy()).thenReturn(
+                offloadBrightnessStrategy);
+        boolean brightnessUpdated =
+                mDisplayBrightnessController.setBrightnessFromOffload(brightness);
+        verify(offloadBrightnessStrategy, never()).setOffloadScreenBrightness(brightness);
+        assertFalse(brightnessUpdated);
     }
 }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
index 1c681ce..0e89d83 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
@@ -247,6 +247,7 @@
         displayPowerRequest.screenBrightnessOverride = Float.NaN;
         when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN);
         when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN);
+        when(mAutomaticBrightnessStrategy.shouldUseAutoBrightness()).thenReturn(true);
         when(mOffloadBrightnessStrategy.getOffloadScreenBrightness()).thenReturn(0.3f);
         assertEquals(mOffloadBrightnessStrategy, mDisplayBrightnessStrategySelector.selectStrategy(
                 displayPowerRequest, Display.STATE_ON));
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
index ba462e3..a5dc668 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
@@ -188,29 +188,6 @@
     }
 
     @Test
-    public void testAutoBrightnessState_BrightnessReasonIsOffload() {
-        mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
-        int targetDisplayState = Display.STATE_ON;
-        boolean allowAutoBrightnessWhileDozing = false;
-        int brightnessReason = BrightnessReason.REASON_OFFLOAD;
-        int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
-        float lastUserSetBrightness = 0.2f;
-        boolean userSetBrightnessChanged = true;
-        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
-        mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
-                allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
-                userSetBrightnessChanged);
-        verify(mAutomaticBrightnessController)
-                .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
-                        mBrightnessConfiguration,
-                        lastUserSetBrightness,
-                        userSetBrightnessChanged, 0.5f,
-                        false, policy, true);
-        assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
-        assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
-    }
-
-    @Test
     public void testAutoBrightnessState_DisplayIsInDoze_ConfigDoesAllow() {
         mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
         int targetDisplayState = Display.STATE_DOZE;
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
index fb47aa8..9d3caa5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
@@ -98,8 +98,6 @@
     @Rule
     public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
 
-    final BroadcastQueue[] mBroadcastQueues = new BroadcastQueue[1];
-
     @Mock
     AppOpsService mAppOpsService;
     @Mock
@@ -120,6 +118,7 @@
     HandlerThread mHandlerThread;
     TestLooperManager mLooper;
     AtomicInteger mNextPid;
+    BroadcastHistory mEmptyHistory;
 
     /**
      * Map from PID to registered registered runtime receivers.
@@ -137,6 +136,13 @@
                 .acquireLooperManager(mHandlerThread.getLooper()));
         mNextPid = new AtomicInteger(100);
 
+        mConstants = new BroadcastConstants(Settings.Global.BROADCAST_FG_CONSTANTS);
+        mEmptyHistory = new BroadcastHistory(mConstants) {
+            public void addBroadcastToHistoryLocked(BroadcastRecord original) {
+                // Ignored
+            }
+        };
+
         LocalServices.removeServiceForTest(DropBoxManagerInternal.class);
         LocalServices.addService(DropBoxManagerInternal.class, mDropBoxManagerInt);
         LocalServices.removeServiceForTest(PackageManagerInternal.class);
@@ -164,8 +170,6 @@
         mSkipPolicy = spy(new BroadcastSkipPolicy(mAms));
         doReturn(null).when(mSkipPolicy).shouldSkipMessage(any(), any());
         doReturn(false).when(mSkipPolicy).disallowBackgroundStart(any());
-
-        mConstants = new BroadcastConstants(Settings.Global.BROADCAST_FG_CONSTANTS);
     }
 
     public void tearDown() throws Exception {
@@ -213,8 +217,8 @@
         }
 
         @Override
-        public BroadcastQueue[] getBroadcastQueues(ActivityManagerService service) {
-            return mBroadcastQueues;
+        public BroadcastQueue getBroadcastQueue(ActivityManagerService service) {
+            return null;
         }
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index cc6fc80..bcf297f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -118,15 +118,9 @@
         mConstants.DELAY_NORMAL_MILLIS = 10_000;
         mConstants.DELAY_CACHED_MILLIS = 120_000;
 
-        final BroadcastHistory emptyHistory = new BroadcastHistory(mConstants) {
-            public void addBroadcastToHistoryLocked(BroadcastRecord original) {
-                // Ignored
-            }
-        };
-
         mImpl = new BroadcastQueueModernImpl(mAms, mHandlerThread.getThreadHandler(),
-            mConstants, mConstants, mSkipPolicy, emptyHistory);
-        mBroadcastQueues[0] = mImpl;
+                mConstants, mConstants, mSkipPolicy, mEmptyHistory);
+        mAms.setBroadcastQueueForTest(mImpl);
 
         doReturn(1L).when(mQueue1).getRunnableAt();
         doReturn(2L).when(mQueue2).getRunnableAt();
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index e141faf..56e5bd6 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -85,12 +85,8 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.junit.After;
-import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
 import org.mockito.ArgumentMatcher;
 import org.mockito.InOrder;
 import org.mockito.verification.VerificationMode;
@@ -100,7 +96,6 @@
 import java.io.Writer;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -114,18 +109,10 @@
  * Common tests for {@link BroadcastQueue} implementations.
  */
 @MediumTest
-@RunWith(Parameterized.class)
 @SuppressWarnings("GuardedBy")
 public class BroadcastQueueTest extends BaseBroadcastQueueTest {
     private static final String TAG = "BroadcastQueueTest";
 
-    private final Impl mImpl;
-
-    private enum Impl {
-        DEFAULT,
-        MODERN,
-    }
-
     private BroadcastQueue mQueue;
     private UidObserver mUidObserver;
 
@@ -157,15 +144,6 @@
      */
     private List<Pair<Integer, String>> mScheduledBroadcasts = new ArrayList<>();
 
-    @Parameters(name = "impl={0}")
-    public static Collection<Object[]> data() {
-        return Arrays.asList(new Object[][] { {Impl.DEFAULT}, {Impl.MODERN} });
-    }
-
-    public BroadcastQueueTest(Impl impl) {
-        mImpl = impl;
-    }
-
     @Before
     public void setUp() throws Exception {
         super.setUp();
@@ -251,24 +229,9 @@
         }).when(mAms).registerUidObserver(any(), anyInt(),
                 eq(ActivityManager.PROCESS_STATE_TOP), any());
 
-        final BroadcastHistory emptyHistory = new BroadcastHistory(mConstants) {
-            public void addBroadcastToHistoryLocked(BroadcastRecord original) {
-                // Ignored
-            }
-        };
-
-        if (mImpl == Impl.DEFAULT) {
-            mQueue = new BroadcastQueueImpl(mAms, mHandlerThread.getThreadHandler(), TAG,
-                    mConstants, mSkipPolicy, emptyHistory, false,
-                    ProcessList.SCHED_GROUP_DEFAULT);
-        } else if (mImpl == Impl.MODERN) {
-            mQueue = new BroadcastQueueModernImpl(mAms, mHandlerThread.getThreadHandler(),
-                    mConstants, mConstants, mSkipPolicy, emptyHistory);
-        } else {
-            throw new UnsupportedOperationException();
-        }
-        mBroadcastQueues[0] = mQueue;
-
+        mQueue = new BroadcastQueueModernImpl(mAms, mHandlerThread.getThreadHandler(),
+                mConstants, mConstants, mSkipPolicy, mEmptyHistory);
+        mAms.setBroadcastQueueForTest(mQueue);
         mQueue.start(mContext.getContentResolver());
 
         // Set the constants after invoking BroadcastQueue.start() to ensure they don't
@@ -489,10 +452,8 @@
     }
 
     private void assertHealth() {
-        if (mImpl == Impl.MODERN) {
-            // If this fails, it'll throw a clear reason message
-            ((BroadcastQueueModernImpl) mQueue).assertHealthLocked();
-        }
+        // If this fails, it'll throw a clear reason message
+        ((BroadcastQueueModernImpl) mQueue).assertHealthLocked();
     }
 
     private static Map<String, Object> asMap(Bundle bundle) {
@@ -814,18 +775,13 @@
                     .setReportedProcState(ActivityManager.PROCESS_STATE_RECEIVER);
             verify(mAms, times(2)).enqueueOomAdjTargetLocked(eq(receiverApp));
 
-            if ((mImpl == Impl.DEFAULT) && (receiverApp == receiverBlueApp)) {
-                // Nuance: the default implementation doesn't ask for manifest
-                // cold-started apps to be thawed, but the modern stack does
-            } else {
-                // Confirm that app was thawed
-                verify(mAms.mOomAdjuster, atLeastOnce()).unfreezeTemporarily(
-                        eq(receiverApp), eq(OOM_ADJ_REASON_START_RECEIVER));
+            // Confirm that app was thawed
+            verify(mAms.mOomAdjuster, atLeastOnce()).unfreezeTemporarily(
+                    eq(receiverApp), eq(OOM_ADJ_REASON_START_RECEIVER));
 
-                // Confirm that we added package to process
-                verify(receiverApp, atLeastOnce()).addPackage(eq(receiverApp.info.packageName),
-                        anyLong(), any());
-            }
+            // Confirm that we added package to process
+            verify(receiverApp, atLeastOnce()).addPackage(eq(receiverApp.info.packageName),
+                    anyLong(), any());
 
             // Confirm that we've reported package as being used
             verify(mAms, atLeastOnce()).notifyPackageUse(eq(receiverApp.info.packageName),
@@ -868,9 +824,6 @@
      */
     @Test
     public void testWedged_Registered_Ordered() throws Exception {
-        // Legacy stack doesn't detect these ANRs; likely an oversight
-        Assume.assumeTrue(mImpl == Impl.MODERN);
-
         final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
         final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN,
                 ProcessBehavior.WEDGE);
@@ -891,9 +844,6 @@
      */
     @Test
     public void testWedged_Registered_ResultTo() throws Exception {
-        // Legacy stack doesn't detect these ANRs; likely an oversight
-        Assume.assumeTrue(mImpl == Impl.MODERN);
-
         final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
         final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN,
                 ProcessBehavior.WEDGE);
@@ -994,9 +944,7 @@
                 getUidForPackage(PACKAGE_GREEN));
         // Modern queue always kills the target process when broadcast delivery fails, where as
         // the legacy queue leaves the process killing task to AMS
-        if (mImpl == Impl.MODERN) {
-            assertNull(receiverGreenApp);
-        }
+        assertNull(receiverGreenApp);
         final ProcessRecord receiverBlueApp = mAms.getProcessRecordLocked(PACKAGE_BLUE,
                 getUidForPackage(PACKAGE_BLUE));
         verifyScheduleReceiver(receiverBlueApp, airplane);
@@ -1110,10 +1058,8 @@
         waitForIdle();
         // Legacy stack does not remove registered receivers as part of
         // cleanUpDisabledPackageReceiversLocked() call, so verify this only on modern queue.
-        if (mImpl == Impl.MODERN) {
-            verifyScheduleReceiver(never(), callerApp, USER_GUEST);
-            verifyScheduleRegisteredReceiver(never(), callerApp, USER_GUEST);
-        }
+        verifyScheduleReceiver(never(), callerApp, USER_GUEST);
+        verifyScheduleRegisteredReceiver(never(), callerApp, USER_GUEST);
         for (String pkg : new String[] {
                 PACKAGE_GREEN, PACKAGE_BLUE, PACKAGE_YELLOW
         }) {
@@ -1199,9 +1145,6 @@
     @Test
     @RequiresFlagsEnabled(Flags.FLAG_AVOID_REPEATED_BCAST_RE_ENQUEUES)
     public void testRepeatedKillWithoutNotify() throws Exception {
-        // Legacy queue does not handle repeated kills that don't get notified.
-        Assume.assumeTrue(mImpl == Impl.MODERN);
-
         final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
         final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
 
@@ -1227,9 +1170,7 @@
 
         // Modern queue always kills the target process when broadcast delivery fails, where as
         // the legacy queue leaves the process killing task to AMS
-        if (mImpl == Impl.MODERN) {
-            assertNull(receiverGreenApp);
-        }
+        assertNull(receiverGreenApp);
         verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, airplane);
         verifyScheduleReceiver(times(1), receiverYellowApp, airplane);
         verifyScheduleReceiver(times(1), receiverOrangeApp, timezone);
@@ -1273,11 +1214,7 @@
         assertNotEquals(receiverBlueApp, restartedReceiverBlueApp);
         // Legacy queue will always try delivering the broadcast even if the process
         // has been killed.
-        if (mImpl == Impl.MODERN) {
-            verifyScheduleReceiver(never(), receiverBlueApp, airplane);
-        } else {
-            verifyScheduleReceiver(times(1), receiverBlueApp, airplane);
-        }
+        verifyScheduleReceiver(never(), receiverBlueApp, airplane);
         // Verify that the new process receives the broadcast.
         verifyScheduleReceiver(times(1), restartedReceiverBlueApp, airplane);
     }
@@ -1671,9 +1608,6 @@
      */
     @Test
     public void testPrioritized_withDeferrableBroadcasts() throws Exception {
-        // Legacy stack doesn't support deferral
-        Assume.assumeTrue(mImpl == Impl.MODERN);
-
         final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
         final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
         final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
@@ -1834,10 +1768,6 @@
 
     @Test
     public void testReplacePending_withUrgentBroadcast() throws Exception {
-        // The behavior is same with the legacy queue but AMS takes care of finding
-        // the right queue and replacing the broadcast.
-        Assume.assumeTrue(mImpl == Impl.MODERN);
-
         final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
 
         final Intent timeTickFirst = new Intent(Intent.ACTION_TIME_TICK);
@@ -1903,15 +1833,9 @@
 
         waitForIdle();
 
-        if (mImpl == Impl.MODERN) {
-            verifyScheduleRegisteredReceiver(times(2), receiverGreenApp, airplane);
-            verifyScheduleRegisteredReceiver(times(2), receiverBlueApp, airplane);
-            verifyScheduleRegisteredReceiver(times(1), receiverYellowApp, airplane);
-        } else {
-            verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, airplane);
-            verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, airplane);
-            verifyScheduleRegisteredReceiver(never(), receiverYellowApp, airplane);
-        }
+        verifyScheduleRegisteredReceiver(times(2), receiverGreenApp, airplane);
+        verifyScheduleRegisteredReceiver(times(2), receiverBlueApp, airplane);
+        verifyScheduleRegisteredReceiver(times(1), receiverYellowApp, airplane);
     }
 
     @Test
@@ -1931,14 +1855,10 @@
                 withPriority(receiverGreenA, 5))));
 
         waitForIdle();
-        if (mImpl == Impl.DEFAULT) {
-            verifyScheduleRegisteredReceiver(times(2), receiverGreenApp, airplane);
-        } else {
-            // In the modern queue, we don't end up replacing the old broadcast to
-            // avoid creating priority inversion and so the process will receive
-            // both the old and new broadcasts.
-            verifyScheduleRegisteredReceiver(times(3), receiverGreenApp, airplane);
-        }
+        // In the modern queue, we don't end up replacing the old broadcast to
+        // avoid creating priority inversion and so the process will receive
+        // both the old and new broadcasts.
+        verifyScheduleRegisteredReceiver(times(3), receiverGreenApp, airplane);
     }
 
     @Test
@@ -1966,11 +1886,7 @@
 
         verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, timeTick);
         verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, timeTick);
-        if (mImpl == Impl.MODERN) {
-            verifyScheduleRegisteredReceiver(times(2), receiverGreenApp, airplane);
-        } else {
-            verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, airplane);
-        }
+        verifyScheduleRegisteredReceiver(times(2), receiverGreenApp, airplane);
         verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, airplane);
     }
 
@@ -2002,9 +1918,6 @@
 
     @Test
     public void testReplacePendingToCachedProcess_withDeferrableBroadcast() throws Exception {
-        // Legacy stack doesn't support deferral
-        Assume.assumeTrue(mImpl == Impl.MODERN);
-
         final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
         final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
         final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
@@ -2224,16 +2137,9 @@
         }
         waitForIdle();
 
-        final int expectedTimes;
-        switch (mImpl) {
-            // Original stack requested for every single receiver; yikes
-            case DEFAULT: expectedTimes = 64; break;
-            // Modern stack requests once each time we promote a process to
-            // running; we promote "green" twice, and "blue" and "yellow" once
-            case MODERN: expectedTimes = 4; break;
-            default: throw new UnsupportedOperationException();
-        }
-
+        // Modern stack requests once each time we promote a process to
+        // running; we promote "green" twice, and "blue" and "yellow" once
+        final int expectedTimes = 4;
         verify(mAms, times(expectedTimes))
                 .updateOomAdjPendingTargetsLocked(eq(OOM_ADJ_REASON_START_RECEIVER));
     }
@@ -2302,9 +2208,6 @@
      */
     @Test
     public void testDeferralPolicy_UntilActive() throws Exception {
-        // Legacy stack doesn't support deferral
-        Assume.assumeTrue(mImpl == Impl.MODERN);
-
         final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
         final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
         final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
@@ -2350,9 +2253,6 @@
      */
     @Test
     public void testDeferralPolicy_UntilActive_WithMultiProcessUid() throws Exception {
-        // Legacy stack doesn't support deferral
-        Assume.assumeTrue(mImpl == Impl.MODERN);
-
         final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
         final ProcessRecord receiverGreenApp1 = makeActiveProcessRecord(PACKAGE_GREEN);
         final ProcessRecord receiverGreenApp2 = makeActiveProcessRecord(PACKAGE_GREEN,
@@ -2384,9 +2284,6 @@
 
     @Test
     public void testBroadcastDelivery_uidForeground() throws Exception {
-        // Legacy stack doesn't support prioritization to foreground app.
-        Assume.assumeTrue(mImpl == Impl.MODERN);
-
         final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
         final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
         final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
@@ -2418,9 +2315,6 @@
 
     @Test
     public void testPrioritizedBroadcastDelivery_uidForeground() throws Exception {
-        // Legacy stack doesn't support prioritization to foreground app.
-        Assume.assumeTrue(mImpl == Impl.MODERN);
-
         final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
         final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
         final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
index f0efb79..878b945 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
@@ -61,8 +61,6 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.server.am.BroadcastDispatcher.DeferredBootCompletedBroadcastPerUser;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -747,68 +745,6 @@
         }
     }
 
-    /**
-     * Test the class {@link BroadcastDispatcher#DeferredBootCompletedBroadcastPerUser}
-     */
-    @Test
-    public void testDeferBootCompletedBroadcast_dispatcher() {
-        testDeferBootCompletedBroadcast_dispatcher_internal(ACTION_LOCKED_BOOT_COMPLETED, false);
-        testDeferBootCompletedBroadcast_dispatcher_internal(ACTION_BOOT_COMPLETED, false);
-        testDeferBootCompletedBroadcast_dispatcher_internal(ACTION_LOCKED_BOOT_COMPLETED, true);
-        testDeferBootCompletedBroadcast_dispatcher_internal(ACTION_BOOT_COMPLETED, true);
-    }
-
-    private void testDeferBootCompletedBroadcast_dispatcher_internal(String action,
-            boolean isAllUidReady) {
-        final List<ResolveInfo> receivers = createReceiverInfos(PACKAGE_LIST, new int[] {USER0});
-        final BroadcastRecord br = createBroadcastRecord(receivers, USER0, new Intent(action));
-        assertEquals(PACKAGE_LIST.length, br.receivers.size());
-
-        SparseArray<BroadcastRecord> deferred = br.splitDeferredBootCompletedBroadcastLocked(
-                mActivityManagerInternal, DEFER_BOOT_COMPLETED_BROADCAST_ALL);
-        // original BroadcastRecord receivers list is empty now.
-        assertTrue(br.receivers.isEmpty());
-        assertEquals(PACKAGE_LIST.length, deferred.size());
-
-        DeferredBootCompletedBroadcastPerUser deferredPerUser =
-                new DeferredBootCompletedBroadcastPerUser(USER0);
-        deferredPerUser.enqueueBootCompletedBroadcasts(action, deferred);
-
-        if (action.equals(ACTION_LOCKED_BOOT_COMPLETED)) {
-            assertEquals(PACKAGE_LIST.length,
-                    deferredPerUser.mDeferredLockedBootCompletedBroadcasts.size());
-            assertTrue(deferredPerUser.mLockedBootCompletedBroadcastReceived);
-            for (int i = 0; i < PACKAGE_LIST.length; i++) {
-                final int uid = UserHandle.getUid(USER0, getAppId(i));
-                if (!isAllUidReady) {
-                    deferredPerUser.updateUidReady(uid);
-                }
-                BroadcastRecord d = deferredPerUser.dequeueDeferredBootCompletedBroadcast(
-                        isAllUidReady);
-                final ResolveInfo info = (ResolveInfo) d.receivers.get(0);
-                assertEquals(PACKAGE_LIST[i], info.activityInfo.applicationInfo.packageName);
-                assertEquals(uid, info.activityInfo.applicationInfo.uid);
-            }
-            assertEquals(0, deferredPerUser.mUidReadyForLockedBootCompletedBroadcast.size());
-        } else if (action.equals(ACTION_BOOT_COMPLETED)) {
-            assertEquals(PACKAGE_LIST.length,
-                    deferredPerUser.mDeferredBootCompletedBroadcasts.size());
-            assertTrue(deferredPerUser.mBootCompletedBroadcastReceived);
-            for (int i = 0; i < PACKAGE_LIST.length; i++) {
-                final int uid = UserHandle.getUid(USER0, getAppId(i));
-                if (!isAllUidReady) {
-                    deferredPerUser.updateUidReady(uid);
-                }
-                BroadcastRecord d = deferredPerUser.dequeueDeferredBootCompletedBroadcast(
-                        isAllUidReady);
-                final ResolveInfo info = (ResolveInfo) d.receivers.get(0);
-                assertEquals(PACKAGE_LIST[i], info.activityInfo.applicationInfo.packageName);
-                assertEquals(uid, info.activityInfo.applicationInfo.uid);
-            }
-            assertEquals(0, deferredPerUser.mUidReadyForBootCompletedBroadcast.size());
-        }
-    }
-
     private static void cleanupDisabledPackageReceivers(BroadcastRecord record,
             String packageName, int userId) {
         record.cleanupDisabledPackageReceiversLocked(packageName, null /* filterByClasses */,
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 1226f0c..872ac40 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -297,6 +297,10 @@
         } else {
             updateProcessRecordNodes(Arrays.asList(apps));
             if (apps.length == 1) {
+                final ProcessRecord app = apps[0];
+                if (!sService.mProcessList.getLruProcessesLOSP().contains(app)) {
+                    sService.mProcessList.getLruProcessesLOSP().add(app);
+                }
                 sService.mOomAdjuster.updateOomAdjLocked(apps[0], OOM_ADJ_REASON_NONE);
             } else {
                 setProcessesToLru(apps);
@@ -475,7 +479,16 @@
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         updateOomAdj(app);
 
-        assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, CACHED_APP_MIN_ADJ,
+        final int expectedAdj;
+        if (sService.mConstants.ENABLE_NEW_OOMADJ) {
+            // A cached empty process can be at best a level higher than the min cached adj.
+            expectedAdj = sFirstCachedAdj;
+        } else {
+            // This is wrong but legacy behavior is going to be removed and not worth fixing.
+            expectedAdj = CACHED_APP_MIN_ADJ;
+        }
+
+        assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, expectedAdj,
                 SCHED_GROUP_BACKGROUND);
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 7bbcd50..bc7c9a5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -15,6 +15,10 @@
  */
 package com.android.server.pm;
 
+import static android.content.pm.PackageManager.FEATURE_AUTOMOTIVE;
+import static android.content.pm.PackageManager.FEATURE_EMBEDDED;
+import static android.content.pm.PackageManager.FEATURE_LEANBACK;
+import static android.content.pm.PackageManager.FEATURE_WATCH;
 import static android.os.UserManager.DISALLOW_OUTGOING_CALLS;
 import static android.os.UserManager.DISALLOW_SMS;
 import static android.os.UserManager.DISALLOW_USER_SWITCH;
@@ -41,6 +45,7 @@
 import static org.mockito.Mockito.when;
 
 import android.annotation.UserIdInt;
+import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.KeyguardManager;
 import android.content.Context;
@@ -48,6 +53,7 @@
 import android.content.pm.UserInfo;
 import android.multiuser.Flags;
 import android.os.PowerManager;
+import android.os.ServiceSpecificException;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -76,6 +82,7 @@
 import org.junit.Test;
 import org.mockito.Mock;
 import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -120,11 +127,14 @@
 
     private static final String TAG_RESTRICTIONS = "restrictions";
 
+    private static final String PRIVATE_PROFILE_NAME = "TestPrivateProfile";
+
     @Rule
     public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
             .spyStatic(UserManager.class)
             .spyStatic(LocalServices.class)
             .spyStatic(SystemProperties.class)
+            .spyStatic(ActivityManager.class)
             .mockStatic(Settings.Global.class)
             .mockStatic(Settings.Secure.class)
             .build();
@@ -163,6 +173,7 @@
     @Before
     @UiThreadTest // Needed to initialize main handler
     public void setFixtures() {
+        MockitoAnnotations.initMocks(this);
         mSpiedContext = spy(mRealContext);
 
         // Called when WatchedUserStates is constructed
@@ -172,11 +183,12 @@
         when(mDeviceStorageMonitorInternal.isMemoryLow()).thenReturn(false);
         mockGetLocalService(DeviceStorageMonitorInternal.class, mDeviceStorageMonitorInternal);
         when(mSpiedContext.getSystemService(StorageManager.class)).thenReturn(mStorageManager);
-        when(mSpiedContext.getSystemService(KeyguardManager.class)).thenReturn(mKeyguardManager);
+        doReturn(mKeyguardManager).when(mSpiedContext).getSystemService(KeyguardManager.class);
         when(mSpiedContext.getSystemService(PowerManager.class)).thenReturn(mPowerManager);
         mockGetLocalService(LockSettingsInternal.class, mLockSettingsInternal);
         mockGetLocalService(PackageManagerInternal.class, mPackageManagerInternal);
         doNothing().when(mSpiedContext).sendBroadcastAsUser(any(), any(), any());
+        mockIsLowRamDevice(false);
 
         // Must construct UserManagerService in the UiThread
         mTestDir = new File(mRealContext.getDataDir(), "umstest");
@@ -570,9 +582,10 @@
 
     @Test
     public void testAutoLockPrivateProfile() {
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
         UserManagerService mSpiedUms = spy(mUms);
         UserInfo privateProfileUser =
-                mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+                mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
                         USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
         Mockito.doNothing().when(mSpiedUms).setQuietModeEnabledAsync(
                 eq(privateProfileUser.getUserHandle().getIdentifier()), eq(true), any(),
@@ -587,10 +600,11 @@
 
     @Test
     public void testAutoLockOnDeviceLockForPrivateProfile() {
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
         mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
         UserManagerService mSpiedUms = spy(mUms);
         UserInfo privateProfileUser =
-                mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+                mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
                 USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
         mockAutoLockForPrivateSpace(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK);
         Mockito.doNothing().when(mSpiedUms).setQuietModeEnabledAsync(
@@ -606,10 +620,11 @@
 
     @Test
     public void testAutoLockOnDeviceLockForPrivateProfile_keyguardUnlocked() {
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
         mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
         UserManagerService mSpiedUms = spy(mUms);
         UserInfo privateProfileUser =
-                mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+                mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
                 USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
         mockAutoLockForPrivateSpace(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK);
 
@@ -623,10 +638,11 @@
 
     @Test
     public void testAutoLockOnDeviceLockForPrivateProfile_flagDisabled() {
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
         mSetFlagsRule.disableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
         UserManagerService mSpiedUms = spy(mUms);
         UserInfo privateProfileUser =
-                mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+                mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
                 USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
 
         mSpiedUms.tryAutoLockingPrivateSpaceOnKeyguardChanged(true);
@@ -641,13 +657,14 @@
 
     @Test
     public void testAutoLockAfterInactityForPrivateProfile() {
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
         mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
         UserManagerService mSpiedUms = spy(mUms);
         mockAutoLockForPrivateSpace(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY);
         when(mPowerManager.isInteractive()).thenReturn(false);
 
         UserInfo privateProfileUser =
-                mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+                mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
                         USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
         Mockito.doNothing().when(mSpiedUms).scheduleMessageToAutoLockPrivateSpace(
                 eq(privateProfileUser.getUserHandle().getIdentifier()), any(),
@@ -662,6 +679,7 @@
 
     @Test
     public void testSetOrUpdateAutoLockPreference_noPrivateProfile() {
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
         mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
 
         mUms.setOrUpdateAutoLockPreferenceForPrivateProfile(
@@ -675,8 +693,9 @@
 
     @Test
     public void testSetOrUpdateAutoLockPreference() {
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
         mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
-        mUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+        mUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
                         USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
 
         // Set the preference to auto lock on device lock
@@ -733,6 +752,77 @@
         }
     }
 
+    @Test
+    public void testCreatePrivateProfileOnHeadlessSystemUser_shouldAllowCreation() {
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION);
+        UserManagerService mSpiedUms = spy(mUms);
+        int mainUser = mSpiedUms.getMainUserId();
+        doReturn(true).when(mSpiedUms).isHeadlessSystemUserMode();
+        assertThat(mSpiedUms.canAddPrivateProfile(mainUser)).isTrue();
+        assertThat(mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(
+                PRIVATE_PROFILE_NAME, USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null)).isNotNull();
+    }
+
+    @Test
+    public void testCreatePrivateProfileOnSecondaryUser_shouldNotAllowCreation() {
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION);
+        UserInfo user = mUms.createUserWithThrow(generateLongString(), USER_TYPE_FULL_SECONDARY, 0);
+        assertThat(mUms.canAddPrivateProfile(user.id)).isFalse();
+        assertThrows(ServiceSpecificException.class,
+                () -> mUms.createProfileForUserWithThrow(PRIVATE_PROFILE_NAME,
+                        USER_TYPE_PROFILE_PRIVATE, 0, user.id, null));
+    }
+
+    @Test
+    public void testCreatePrivateProfileOnAutoDevices_shouldNotAllowCreation() {
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION);
+        doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_AUTOMOTIVE), anyInt());
+        int mainUser = mUms.getMainUserId();
+        assertThat(mUms.canAddPrivateProfile(0)).isFalse();
+        assertThrows(ServiceSpecificException.class,
+                () -> mUms.createProfileForUserWithThrow(PRIVATE_PROFILE_NAME,
+                        USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null));
+    }
+
+    @Test
+    public void testCreatePrivateProfileOnTV_shouldNotAllowCreation() {
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION);
+        doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_LEANBACK), anyInt());
+        int mainUser = mUms.getMainUserId();
+        assertThat(mUms.canAddPrivateProfile(0)).isFalse();
+        assertThrows(ServiceSpecificException.class,
+                () -> mUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
+                        USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null));
+    }
+
+    @Test
+    public void testCreatePrivateProfileOnEmbedded_shouldNotAllowCreation() {
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION);
+        doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_EMBEDDED), anyInt());
+        int mainUser = mUms.getMainUserId();
+        assertThat(mUms.canAddPrivateProfile(0)).isFalse();
+        assertThrows(ServiceSpecificException.class,
+                () -> mUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
+                        USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null));
+    }
+
+    @Test
+    public void testCreatePrivateProfileOnWatch_shouldNotAllowCreation() {
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION);
+        doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_WATCH), anyInt());
+        int mainUser = mUms.getMainUserId();
+        assertThat(mUms.canAddPrivateProfile(0)).isFalse();
+        assertThrows(ServiceSpecificException.class,
+                () -> mUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
+                        USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null));
+    }
+
     /**
      * Returns true if the user's XML file has Default restrictions
      * @param userId Id of the user.
@@ -800,6 +890,10 @@
                 any(), eq(android.provider.Settings.Global.USER_SWITCHER_ENABLED), anyInt()));
     }
 
+    private void mockIsLowRamDevice(boolean isLowRamDevice) {
+        doReturn(isLowRamDevice).when(ActivityManager::isLowRamDeviceStatic);
+    }
+
     private void mockDeviceDemoMode(boolean enabled) {
         doReturn(enabled ? 1 : 0).when(() -> Settings.Global.getInt(
                 any(), eq(android.provider.Settings.Global.DEVICE_DEMO_MODE), anyInt()));
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 5e5181b..0089d4c 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -109,7 +109,6 @@
     <uses-permission android:name="android.permission.UPDATE_LOCK_TASK_PACKAGES" />
     <uses-permission android:name="android.permission.ACCESS_CONTEXT_HUB" />
     <uses-permission android:name="android.permission.USE_BIOMETRIC_INTERNAL" />
-    <uses-permission android:name="android.permission.USE_BACKGROUND_FACE_AUTHENTICATION" />
     <uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION" />
     <uses-permission android:name="android.permission.MANAGE_ROLE_HOLDERS" />
 
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index cea10ea..ea1a68a 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -825,7 +825,8 @@
         mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
                 /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
         mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
-                android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
+                android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE);
         assertProfileLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* expectLocking= */ true);
         verifyUserUnassignedFromDisplay(TEST_USER_ID1);
@@ -842,7 +843,8 @@
         mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
                 /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
         mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
-                android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
+                android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE);
         assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* allowDelayedLocking= */ true,
                 /* keyEvictedCallback */ null, /* expectLocking= */ false);
@@ -852,19 +854,28 @@
     public void testStopPrivateProfileWithDelayedLocking_flagDisabled() throws Exception {
         mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
                 /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
-        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         mSetFlagsRule.disableFlags(
                 android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
         setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE);
         assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* allowDelayedLocking= */ true,
                 /* keyEvictedCallback */ null, /* expectLocking= */ true);
 
-        mSetFlagsRule.disableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.disableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         mSetFlagsRule.enableFlags(
                 android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
         setUpAndStartProfileInBackground(TEST_USER_ID2, UserManager.USER_TYPE_PROFILE_PRIVATE);
         assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* allowDelayedLocking= */ true,
                 /* keyEvictedCallback */ null, /* expectLocking= */ true);
+
+        mSetFlagsRule.disableFlags(android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
+        setUpAndStartProfileInBackground(TEST_USER_ID3, UserManager.USER_TYPE_PROFILE_PRIVATE);
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* allowDelayedLocking= */ true,
+                /* keyEvictedCallback */ null, /* expectLocking= */ true);
     }
 
     /** Delayed-locking users (as opposed to devices) have no limits on how many can be unlocked. */
@@ -874,7 +885,8 @@
         mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
                 /* maxRunningUsers= */ 1, /* delayUserDataLocking= */ false);
         mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
-                android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
+                android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE);
         setUpAndStartProfileInBackground(TEST_USER_ID2, UserManager.USER_TYPE_PROFILE_MANAGED);
         assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* allowDelayedLocking= */ true,
@@ -890,7 +902,8 @@
         mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
                 /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
         mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
-                android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
+                android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_MANAGED);
         assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* allowDelayedLocking= */ true,
                 /* keyEvictedCallback */ null, /* expectLocking= */ true);
diff --git a/services/tests/servicestests/src/com/android/server/appwidget/ApiCounterTest.kt b/services/tests/servicestests/src/com/android/server/appwidget/ApiCounterTest.kt
new file mode 100644
index 0000000..79766f8
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/appwidget/ApiCounterTest.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 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.appwidget
+
+import android.content.ComponentName
+import com.android.server.appwidget.AppWidgetServiceImpl.ApiCounter
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+class ApiCounterTest {
+    private companion object {
+        const val RESET_INTERVAL_MS = 10L
+        const val MAX_CALLS_PER_INTERVAL = 2
+    }
+
+    private var currentTime = 0L
+
+    private val id =
+        AppWidgetServiceImpl.ProviderId(
+            /* uid= */ 123,
+            ComponentName("com.android.server.appwidget", "FakeProviderClass")
+        )
+    private val counter = ApiCounter(RESET_INTERVAL_MS, MAX_CALLS_PER_INTERVAL) { currentTime }
+
+    @Test
+    fun tryApiCall() {
+        for (i in 0 until MAX_CALLS_PER_INTERVAL) {
+            assertThat(counter.tryApiCall(id)).isTrue()
+        }
+        assertThat(counter.tryApiCall(id)).isFalse()
+        currentTime = 5L
+        assertThat(counter.tryApiCall(id)).isFalse()
+        currentTime = 11L
+        assertThat(counter.tryApiCall(id)).isTrue()
+    }
+
+    @Test
+    fun remove() {
+        for (i in 0 until MAX_CALLS_PER_INTERVAL) {
+            assertThat(counter.tryApiCall(id)).isTrue()
+        }
+        assertThat(counter.tryApiCall(id)).isFalse()
+        // remove should cause the call count to be 0 on the next tryApiCall
+        counter.remove(id)
+        assertThat(counter.tryApiCall(id)).isTrue()
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
index c8a5583d..3aaac2e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
@@ -16,7 +16,6 @@
 
 package com.android.server.biometrics.sensors.face;
 
-import static android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION;
 import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
 import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
 import static android.hardware.face.FaceSensorProperties.TYPE_UNKNOWN;
@@ -235,26 +234,6 @@
     }
 
     @Test
-    public void testAuthenticateInBackground() throws Exception {
-        FaceAuthenticateOptions faceAuthenticateOptions = new FaceAuthenticateOptions.Builder()
-                .build();
-        initService();
-        mFaceService.mServiceWrapper.registerAuthenticators(List.of());
-        waitForRegistration();
-
-        mContext.getTestablePermissions().setPermission(
-                USE_BIOMETRIC_INTERNAL, PackageManager.PERMISSION_DENIED);
-        mContext.getTestablePermissions().setPermission(
-                USE_BACKGROUND_FACE_AUTHENTICATION, PackageManager.PERMISSION_GRANTED);
-
-        final long operationId = 5;
-        mFaceService.mServiceWrapper.authenticateInBackground(mToken, operationId,
-                mFaceServiceReceiver, faceAuthenticateOptions);
-
-        assertThat(faceAuthenticateOptions.getSensorId()).isEqualTo(ID_DEFAULT);
-    }
-
-    @Test
     public void testOptionsForDetect() throws Exception {
         FaceAuthenticateOptions faceAuthenticateOptions = new FaceAuthenticateOptions.Builder()
                 .setOpPackageName(ComponentName.unflattenFromString(OP_PACKAGE_NAME)
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java
index 9ad2652..6731403 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java
@@ -235,7 +235,7 @@
     }
 
     @Test
-    public void adjustOnlyAvbEnabled_audioDeviceVolumeChanged_doesNotSendSetAudioVolumeLevel() {
+    public void adjustOnlyAvbEnabled_audioDeviceVolumeChanged_requestsAndUpdatesAudioStatus() {
         enableAdjustOnlyAbsoluteVolumeBehavior();
 
         mNativeWrapper.clearResultMessages();
@@ -250,7 +250,22 @@
         );
         mTestLooper.dispatchAll();
 
-        assertThat(mNativeWrapper.getResultMessages()).isEmpty();
+        // We can't sent <Set Audio Volume Level> when using adjust-only AVB.
+        // Instead, we send <Give Audio Status>, to get the System Audio device's volume level.
+        // This ensures that we end up with a correct audio status in AudioService, even if it
+        // set it incorrectly because it assumed that we could send <Set Audio Volume Level>
+        assertThat(mNativeWrapper.getResultMessages().size()).isEqualTo(1);
+        assertThat(mNativeWrapper.getResultMessages()).contains(
+                HdmiCecMessageBuilder.buildGiveAudioStatus(getLogicalAddress(),
+                        getSystemAudioDeviceLogicalAddress())
+        );
+
+        // When we receive <Report Audio Status>, we notify AudioService of the volume level.
+        receiveReportAudioStatus(50,
+                true);
+        verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC),
+                eq(50 * STREAM_MUSIC_MAX_VOLUME / AudioStatus.MAX_VOLUME),
+                anyInt());
     }
 
     @Test
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 0aa72d0..98e119c 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
@@ -184,13 +184,13 @@
 
     @Test
     public void isValid_setMenuLanguage() {
-        assertMessageValidity("4F:32:53:50:41").isEqualTo(OK);
+        assertMessageValidity("0F:32:53:50:41").isEqualTo(OK);
         assertMessageValidity("0F:32:45:4E:47:8C:49:D3:48").isEqualTo(OK);
 
-        assertMessageValidity("40:32:53:50:41").isEqualTo(ERROR_DESTINATION);
-        assertMessageValidity("F0:32").isEqualTo(ERROR_SOURCE);
-        assertMessageValidity("4F:32:45:55").isEqualTo(ERROR_PARAMETER_SHORT);
-        assertMessageValidity("4F:32:19:7F:83").isEqualTo(ERROR_PARAMETER);
+        assertMessageValidity("04:32:53:50:41").isEqualTo(ERROR_DESTINATION);
+        assertMessageValidity("40:32").isEqualTo(ERROR_SOURCE);
+        assertMessageValidity("0F:32:45:55").isEqualTo(ERROR_PARAMETER_SHORT);
+        assertMessageValidity("0F:32:19:7F:83").isEqualTo(ERROR_PARAMETER);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 507b3fe..1591a96 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -316,6 +316,10 @@
                 .that(userTypeDetails).isNotNull();
         final UserProperties typeProps = userTypeDetails.getDefaultUserPropertiesReference();
 
+        // Only run the test if private profile creation is enabled on the device
+        assumeTrue("Private profile not enabled on the device",
+                mUserManager.canAddPrivateProfile());
+
         // Test that only one private profile  can be created
         final int mainUserId = mainUser.getIdentifier();
         UserInfo userInfo = createProfileForUser("Private profile1",
@@ -1231,6 +1235,20 @@
 
     @MediumTest
     @Test
+    public void testPrivateProfileCreationRestrictions() {
+        assumeTrue(mUserManager.canAddPrivateProfile());
+        final int mainUserId = ActivityManager.getCurrentUser();
+        try {
+            UserInfo privateProfileInfo = createProfileForUser("Private",
+                            UserManager.USER_TYPE_PROFILE_PRIVATE, mainUserId);
+            assertThat(privateProfileInfo).isNotNull();
+        } catch (Exception e) {
+            fail("Creation of private profile failed due to " + e.getMessage());
+        }
+    }
+
+    @MediumTest
+    @Test
     public void testAddRestrictedProfile() throws Exception {
         if (isAutomotive() || UserManager.isHeadlessSystemUserMode()) return;
         assertWithMessage("There should be no associated restricted profiles before the test")
diff --git a/services/tests/uiservicestests/Android.bp b/services/tests/uiservicestests/Android.bp
index 2f29d10..515898a 100644
--- a/services/tests/uiservicestests/Android.bp
+++ b/services/tests/uiservicestests/Android.bp
@@ -48,6 +48,8 @@
         "notification_flags_lib",
         "platform-test-rules",
         "SettingsLib",
+        "libprotobuf-java-lite",
+        "platformprotoslite",
     ],
 
     libs: [
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index 4dded1d..05b6c90 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -37,6 +37,7 @@
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyLong;
 import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
@@ -885,6 +886,7 @@
             return true;
         });
 
+        mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
         service.addApprovedList("a", 0, true);
 
         service.reregisterService(cn, 0);
@@ -915,6 +917,7 @@
             return true;
         });
 
+        mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
         service.addApprovedList("a", 0, false);
 
         service.reregisterService(cn, 0);
@@ -945,6 +948,7 @@
             return true;
         });
 
+        mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
         service.addApprovedList("a/a", 0, true);
 
         service.reregisterService(cn, 0);
@@ -975,6 +979,7 @@
             return true;
         });
 
+        mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
         service.addApprovedList("a/a", 0, false);
 
         service.reregisterService(cn, 0);
@@ -1152,6 +1157,58 @@
     }
 
     @Test
+    public void testUpgradeAppNoPermissionNoRebind() throws Exception {
+        Context context = spy(getContext());
+        doReturn(true).when(context).bindServiceAsUser(any(), any(), anyInt(), any());
+
+        ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles,
+                mIpm,
+                APPROVAL_BY_COMPONENT);
+
+        List<String> packages = new ArrayList<>();
+        packages.add("package");
+        addExpectedServices(service, packages, 0);
+
+        final ComponentName unapprovedComponent = ComponentName.unflattenFromString("package/C1");
+        final ComponentName approvedComponent = ComponentName.unflattenFromString("package/C2");
+
+        // Both components are approved initially
+        mExpectedPrimaryComponentNames.clear();
+        mExpectedPrimaryPackages.clear();
+        mExpectedPrimaryComponentNames.put(0, "package/C1:package/C2");
+        mExpectedSecondaryComponentNames.clear();
+        mExpectedSecondaryPackages.clear();
+
+        loadXml(service);
+
+        //Component package/C1 loses bind permission
+        when(mIpm.getServiceInfo(any(), anyLong(), anyInt())).thenAnswer(
+                (Answer<ServiceInfo>) invocation -> {
+                    ComponentName invocationCn = invocation.getArgument(0);
+                    if (invocationCn != null) {
+                        ServiceInfo serviceInfo = new ServiceInfo();
+                        serviceInfo.packageName = invocationCn.getPackageName();
+                        serviceInfo.name = invocationCn.getClassName();
+                        if (invocationCn.equals(unapprovedComponent)) {
+                            serviceInfo.permission = "none";
+                        } else {
+                            serviceInfo.permission = service.getConfig().bindPermission;
+                        }
+                        serviceInfo.metaData = null;
+                        return serviceInfo;
+                    }
+                    return null;
+                }
+        );
+
+        // Trigger package update
+        service.onPackagesChanged(false, new String[]{"package"}, new int[]{0});
+
+        assertFalse(service.isComponentEnabledForCurrentProfiles(unapprovedComponent));
+        assertTrue(service.isComponentEnabledForCurrentProfiles(approvedComponent));
+    }
+
+    @Test
     public void testSetPackageOrComponentEnabled() throws Exception {
         for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
             ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index e3ea55a..03f2749 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -14092,7 +14092,8 @@
 
     @Test
     public void testProfileUnavailableIntent() throws RemoteException {
-        mSetFlagsRule.enableFlags(FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.enableFlags(FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         simulateProfileAvailabilityActions(Intent.ACTION_PROFILE_UNAVAILABLE);
         verify(mWorkerHandler).post(any(Runnable.class));
         verify(mSnoozeHelper).clearData(anyInt());
@@ -14101,7 +14102,8 @@
 
     @Test
     public void testManagedProfileUnavailableIntent() throws RemoteException {
-        mSetFlagsRule.disableFlags(FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.disableFlags(FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         simulateProfileAvailabilityActions(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
         verify(mWorkerHandler).post(any(Runnable.class));
         verify(mSnoozeHelper).clearData(anyInt());
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 78fb570..bfc47fd 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -324,7 +324,11 @@
         when(mPermissionHelper.getNotificationPermissionValues(USER_SYSTEM))
                 .thenReturn(appPermissions);
 
-        when(mUserProfiles.getCurrentProfileIds()).thenReturn(IntArray.wrap(new int[] {0}));
+        IntArray currentProfileIds = IntArray.wrap(new int[]{0});
+        if (UserManager.isHeadlessSystemUserMode()) {
+            currentProfileIds.add(UserHandle.getUserId(UID_HEADLESS));
+        }
+        when(mUserProfiles.getCurrentProfileIds()).thenReturn(currentProfileIds);
 
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
                 mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenEnumTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenEnumTest.java
new file mode 100644
index 0000000..f724510
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenEnumTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2024 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.notification;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.AutomaticZenRule;
+import android.provider.Settings;
+import android.service.notification.ZenPolicy;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.os.dnd.ActiveRuleType;
+import com.android.os.dnd.ChannelPolicy;
+import com.android.os.dnd.ConversationType;
+import com.android.os.dnd.PeopleType;
+import com.android.os.dnd.State;
+import com.android.os.dnd.ZenMode;
+
+import com.google.protobuf.Internal;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/** Test to validate that logging enums used in Zen classes match their API definitions. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ZenEnumTest {
+
+    @Test
+    public void testEnum_zenMode() {
+        testEnum(Settings.Global.class, "ZEN_MODE", ZenMode.class, "ZEN_MODE");
+    }
+
+    @Test
+    public void testEnum_activeRuleType() {
+        testEnum(AutomaticZenRule.class, "TYPE", ActiveRuleType.class, "TYPE");
+    }
+
+    @Test
+    public void testEnum_zenPolicyState() {
+        testEnum(ZenPolicy.class, "STATE", State.class, "STATE");
+    }
+
+    @Test
+    public void testEnum_zenPolicyChannelPolicy() {
+        testEnum(ZenPolicy.class, "CHANNEL_POLICY", ChannelPolicy.class, "CHANNEL_POLICY");
+    }
+
+    @Test
+    public void testEnum_zenPolicyConversationType() {
+        testEnum(ZenPolicy.class, "CONVERSATION_SENDERS", ConversationType.class, "CONV");
+    }
+
+    @Test
+    public void testEnum_zenPolicyPeopleType() {
+        testEnum(ZenPolicy.class, "PEOPLE_TYPE", PeopleType.class, "PEOPLE");
+    }
+
+    /**
+     * Verifies that any constants (i.e. {@code public static final int} fields) named {@code
+     * <apiPrefix>_SOMETHING} in {@code apiClass} are present and have the same numerical value
+     * in the enum values defined in {@code loggingProtoEnumClass}.
+     *
+     * <p>Note that <em>extra</em> values in the logging enum are accepted (since we have one of
+     * those, and the main goal of this test is that we don't forget to update the logging enum
+     * if new API enum values are added).
+     */
+    private static void testEnum(Class<?> apiClass, String apiPrefix,
+            Class<? extends Internal.EnumLite> loggingProtoEnumClass,
+            String loggingPrefix) {
+        Map<String, Integer> apiConstants =
+                Arrays.stream(apiClass.getDeclaredFields())
+                        .filter(f -> Modifier.isPublic(f.getModifiers()))
+                        .filter(f -> Modifier.isStatic(f.getModifiers()))
+                        .filter(f -> Modifier.isFinal(f.getModifiers()))
+                        .filter(f -> f.getType().equals(int.class))
+                        .filter(f -> f.getName().startsWith(apiPrefix + "_"))
+                        .collect(Collectors.toMap(
+                                Field::getName,
+                                ZenEnumTest::getStaticFieldIntValue));
+
+        Map<String, Integer> loggingConstants =
+                Arrays.stream(loggingProtoEnumClass.getEnumConstants())
+                        .collect(Collectors.toMap(
+                                v -> v.toString(),
+                                v -> v.getNumber()));
+
+        Map<String, Integer> renamedApiConstants = apiConstants.entrySet().stream()
+                .collect(Collectors.toMap(
+                        entry -> entry.getKey().replace(apiPrefix + "_", loggingPrefix + "_"),
+                        Map.Entry::getValue));
+
+        assertThat(loggingConstants).containsAtLeastEntriesIn(renamedApiConstants);
+    }
+
+    private static int getStaticFieldIntValue(Field f) {
+        try {
+            return f.getInt(null);
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 7c1adbc..c4d2460 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -4808,6 +4808,53 @@
 
     @Test
     @EnableFlags(Flags.FLAG_MODES_API)
+    public void updateAutomaticZenRule_ruleChanged_deactivatesRule() {
+        assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
+        AutomaticZenRule rule = new AutomaticZenRule.Builder("rule", CONDITION_ID)
+                .setConfigurationActivity(new ComponentName(mPkg, "cls"))
+                .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+                .build();
+        String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, UPDATE_ORIGIN_APP, "reason",
+                CUSTOM_PKG_UID);
+        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+                CUSTOM_PKG_UID);
+        assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+
+        AutomaticZenRule updateWithDiff = new AutomaticZenRule.Builder(rule)
+                .setTriggerDescription("Whenever")
+                .build();
+        mZenModeHelper.updateAutomaticZenRule(ruleId, updateWithDiff, UPDATE_ORIGIN_APP, "reason",
+                CUSTOM_PKG_UID);
+
+        assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
+        assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).condition).isNull();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_MODES_API)
+    public void updateAutomaticZenRule_ruleNotChanged_doesNotDeactivateRule() {
+        assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
+        AutomaticZenRule rule = new AutomaticZenRule.Builder("rule", CONDITION_ID)
+                .setConfigurationActivity(new ComponentName(mPkg, "cls"))
+                .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+                .build();
+        String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, UPDATE_ORIGIN_APP, "reason",
+                CUSTOM_PKG_UID);
+        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+                CUSTOM_PKG_UID);
+        assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+
+        AutomaticZenRule updateUnchanged = new AutomaticZenRule.Builder(rule).build();
+        mZenModeHelper.updateAutomaticZenRule(ruleId, updateUnchanged, UPDATE_ORIGIN_APP, "reason",
+                CUSTOM_PKG_UID);
+
+        assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+        assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).condition).isEqualTo(
+                CONDITION_TRUE);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_MODES_API)
     public void removeAutomaticZenRule_propagatesOriginToEffectsApplier() {
         mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
         reset(mDeviceEffectsApplier);
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/GroupedAggregatedLogRecordsTest.java b/services/tests/vibrator/src/com/android/server/vibrator/GroupedAggregatedLogRecordsTest.java
index 038f1db..54ab367 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/GroupedAggregatedLogRecordsTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/GroupedAggregatedLogRecordsTest.java
@@ -135,7 +135,7 @@
         AggregatedLogRecord<TestSingleLogRecord> droppedRecord =
                 records.add(createRecord(GROUP_1, KEY_2, createTime++));
         assertThat(droppedRecord).isNotNull();
-        assertThat(droppedRecord.getLatest()).isEqualTo(mTestRecords.getFirst());
+        assertThat(droppedRecord.getLatest()).isEqualTo(mTestRecords.get(0));
 
         dumpRecords(records);
         assertGroupHeadersWrittenOnce(records, GROUP_1);
@@ -155,7 +155,7 @@
         AggregatedLogRecord<TestSingleLogRecord> droppedRecord =
                 records.add(createRecord(GROUP_1, KEY_1, createTime + AGGREGATION_TIME_LIMIT));
         assertThat(droppedRecord).isNotNull();
-        assertThat(droppedRecord.getLatest()).isEqualTo(mTestRecords.getFirst());
+        assertThat(droppedRecord.getLatest()).isEqualTo(mTestRecords.get(0));
 
         dumpRecords(records);
         assertGroupHeadersWrittenOnce(records, GROUP_1);
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
index f54c7e5..88a9483 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
@@ -604,7 +604,8 @@
     @RequiresFlagsEnabled(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED)
     public void shouldIgnoreVibration_withKeyboardSettingsOff_shouldIgnoreKeyboardVibration() {
         setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_MEDIUM);
-        setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 0);
+        setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 0 /* OFF*/);
+        setHasFixedKeyboardAmplitudeIntensity(true);
 
         // Keyboard touch ignored.
         assertVibrationIgnoredForAttributes(
@@ -628,7 +629,8 @@
     @RequiresFlagsEnabled(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED)
     public void shouldIgnoreVibration_withKeyboardSettingsOn_shouldNotIgnoreKeyboardVibration() {
         setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
-        setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 1);
+        setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 1 /* ON */);
+        setHasFixedKeyboardAmplitudeIntensity(true);
 
         // General touch ignored.
         assertVibrationIgnoredForUsage(USAGE_TOUCH, Vibration.Status.IGNORED_FOR_SETTINGS);
@@ -642,6 +644,25 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED)
+    public void shouldIgnoreVibration_noFixedKeyboardAmplitude_ignoresKeyboardTouchVibration() {
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
+        setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 1 /* ON */);
+        setHasFixedKeyboardAmplitudeIntensity(false);
+
+        // General touch ignored.
+        assertVibrationIgnoredForUsage(USAGE_TOUCH, Vibration.Status.IGNORED_FOR_SETTINGS);
+
+        // Keyboard touch ignored.
+        assertVibrationIgnoredForAttributes(
+                new VibrationAttributes.Builder()
+                        .setUsage(USAGE_TOUCH)
+                        .setCategory(VibrationAttributes.CATEGORY_KEYBOARD)
+                        .build(),
+                Vibration.Status.IGNORED_FOR_SETTINGS);
+    }
+
+    @Test
     public void shouldIgnoreVibrationFromVirtualDevices_defaultDevice_neverIgnored() {
         // Vibrations from the primary device is never ignored.
         for (int usage : ALL_USAGES) {
@@ -953,6 +974,10 @@
         when(mVibrationConfigMock.ignoreVibrationsOnWirelessCharger()).thenReturn(ignore);
     }
 
+    private void setHasFixedKeyboardAmplitudeIntensity(boolean hasFixedAmplitude) {
+        when(mVibrationConfigMock.hasFixedKeyboardAmplitude()).thenReturn(hasFixedAmplitude);
+    }
+
     private void deleteUserSetting(String settingName) {
         Settings.System.putStringForUser(
                 mContextSpy.getContentResolver(), settingName, null, UserHandle.USER_CURRENT);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 09e7b91..45a2ba4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -3147,12 +3147,12 @@
         // By default, activity is visible.
         assertTrue(activity.isVisible());
         assertTrue(activity.isVisibleRequested());
-        assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
         assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
 
         // Request the activity to be visible. Although the activity is already visible, app
         // transition animation should be applied on this activity. This might be unnecessary, but
         // until we verify no logic relies on this behavior, we'll keep this as is.
+        mDisplayContent.prepareAppTransition(0);
         activity.setVisibility(true);
         assertTrue(activity.isVisible());
         assertTrue(activity.isVisibleRequested());
@@ -3167,11 +3167,11 @@
         // By default, activity is visible.
         assertTrue(activity.isVisible());
         assertTrue(activity.isVisibleRequested());
-        assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
         assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
 
         // Request the activity to be invisible. Since the visibility changes, app transition
         // animation should be applied on this activity.
+        mDisplayContent.prepareAppTransition(0);
         activity.setVisibility(false);
         assertTrue(activity.isVisible());
         assertFalse(activity.isVisibleRequested());
@@ -3187,7 +3187,6 @@
         // activity.
         assertFalse(activity.isVisible());
         assertTrue(activity.isVisibleRequested());
-        assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
         assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
 
         // Request the activity to be visible. Since the visibility changes, app transition
@@ -3389,6 +3388,7 @@
         // frozen until the input started.
         mDisplayContent.setImeLayeringTarget(app1);
         mDisplayContent.updateImeInputAndControlTarget(app1);
+        mDisplayContent.computeImeTarget(true /* updateImeTarget */);
         performSurfacePlacementAndWaitForWindowAnimator();
 
         assertEquals(app1, mDisplayContent.getImeInputTarget());
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
index 527ea0d..ce90504 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
@@ -1239,7 +1239,7 @@
         final ActivityRecord activity1 = finishTopActivity(rootTask1);
         assertEquals(DESTROYING, activity1.getState());
         verify(mRootWindowContainer).ensureVisibilityAndConfig(eq(null) /* starting */,
-                eq(display.mDisplayId), anyBoolean());
+                eq(display), anyBoolean());
     }
 
     private ActivityRecord finishTopActivity(Task task) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index da11e6a..649f520 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -835,8 +835,6 @@
                 new TestDisplayContent.Builder(mAtm, 1000, 1500)
                         .setSystemDecorations(true).build();
 
-        doReturn(true).when(mRootWindowContainer)
-                .ensureVisibilityAndConfig(any(), anyInt(), anyBoolean());
         doReturn(true).when(mRootWindowContainer).canStartHomeOnDisplayArea(any(), any(),
                 anyBoolean());
 
diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java
index 8b44579..36adeec 100644
--- a/services/usb/java/com/android/server/usb/UsbHostManager.java
+++ b/services/usb/java/com/android/server/usb/UsbHostManager.java
@@ -380,6 +380,11 @@
             return false;
         }
 
+        if (descriptors == null) {
+            Slog.e(TAG, "Failed to add device as the descriptor is null");
+            return false;
+        }
+
         UsbDescriptorParser parser = new UsbDescriptorParser(deviceAddress, descriptors);
         if (deviceClass == UsbConstants.USB_CLASS_PER_INTERFACE
                 && !checkUsbInterfacesDenyListed(parser)) {
@@ -462,8 +467,7 @@
                 }
 
                 // Tracking
-                addConnectionRecord(deviceAddress, ConnectionRecord.CONNECT,
-                        parser.getRawDescriptors());
+                addConnectionRecord(deviceAddress, ConnectionRecord.CONNECT, descriptors);
 
                 // Stats collection
                 FrameworkStatsLog.write(FrameworkStatsLog.USB_DEVICE_ATTACHED,
diff --git a/telephony/java/android/telephony/ims/ImsService.java b/telephony/java/android/telephony/ims/ImsService.java
index b84ff29..12e04c2 100644
--- a/telephony/java/android/telephony/ims/ImsService.java
+++ b/telephony/java/android/telephony/ims/ImsService.java
@@ -195,7 +195,8 @@
                     // whether or not ImsFeature.FEATURE_EMERGENCY_MMTEL feature is set and should
                     // not be set by users of ImsService.
                     CAPABILITY_SIP_DELEGATE_CREATION,
-                    CAPABILITY_TERMINAL_BASED_CALL_WAITING
+                    CAPABILITY_TERMINAL_BASED_CALL_WAITING,
+                    CAPABILITY_SUPPORTS_SIMULTANEOUS_CALLING
             })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ImsServiceCapability {}
@@ -206,7 +207,9 @@
      */
     private static final Map<Long, String> CAPABILITIES_LOG_MAP = Map.of(
             CAPABILITY_EMERGENCY_OVER_MMTEL, "EMERGENCY_OVER_MMTEL",
-            CAPABILITY_SIP_DELEGATE_CREATION, "SIP_DELEGATE_CREATION");
+            CAPABILITY_SIP_DELEGATE_CREATION, "SIP_DELEGATE_CREATION",
+            CAPABILITY_TERMINAL_BASED_CALL_WAITING, "TERMINAL_BASED_CALL_WAITING",
+            CAPABILITY_SUPPORTS_SIMULTANEOUS_CALLING, "SIMULTANEOUS_CALLING");
 
     /**
      * The intent that must be defined as an intent-filter in the AndroidManifest of the ImsService.
diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp
index 1d71f95..d658d59 100644
--- a/tests/FlickerTests/Android.bp
+++ b/tests/FlickerTests/Android.bp
@@ -63,17 +63,20 @@
     ],
 }
 
-android_library_import {
-    name: "wm-flicker-window-extensions_nodeps",
-    aars: ["libs/window-extensions-release.aar"],
-    sdk_version: "current",
-}
-
 java_library {
     name: "wm-flicker-window-extensions",
     sdk_version: "current",
     static_libs: [
-        "wm-flicker-window-extensions_nodeps",
+        "androidx.window.extensions_extensions-nodeps",
+    ],
+    installable: false,
+}
+
+java_library {
+    name: "wm-flicker-window-extensions-core",
+    sdk_version: "current",
+    static_libs: [
+        "androidx.window.extensions.core_core-nodeps",
     ],
     installable: false,
 }
diff --git a/tests/FlickerTests/libs/window-extensions-release.aar b/tests/FlickerTests/libs/window-extensions-release.aar
deleted file mode 100644
index 918e514..0000000
--- a/tests/FlickerTests/libs/window-extensions-release.aar
+++ /dev/null
Binary files differ
diff --git a/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
index a64996c..d9a4c26 100644
--- a/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
@@ -59,6 +59,7 @@
 import java.io.InputStream;
 import java.io.PrintWriter;
 import java.util.LinkedList;
+import java.util.TreeMap;
 
 /**
  * Test class for {@link ProtoLogImpl}.
@@ -89,7 +90,7 @@
         //noinspection ResultOfMethodCallIgnored
         mFile.delete();
         mProtoLog = new LegacyProtoLogImpl(mFile, mViewerConfigFilename,
-                1024 * 1024, mReader, 1024);
+                1024 * 1024, mReader, 1024, new TreeMap<>());
     }
 
     @After
diff --git a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
index 270f595..548adef 100644
--- a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
@@ -64,6 +64,7 @@
 import java.io.IOException;
 import java.util.List;
 import java.util.Random;
+import java.util.TreeMap;
 
 import perfetto.protos.Protolog;
 import perfetto.protos.ProtologCommon;
@@ -152,7 +153,8 @@
                 .thenAnswer(it -> new ProtoInputStream(mViewerConfigBuilder.build().toByteArray()));
 
         mReader = Mockito.spy(new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider));
-        mProtoLog = new PerfettoProtoLogImpl(viewerConfigInputStreamProvider, mReader);
+        mProtoLog =
+                new PerfettoProtoLogImpl(viewerConfigInputStreamProvider, mReader, new TreeMap<>());
     }
 
     @After
diff --git a/tools/protologtool/src/com/android/protolog/tool/CommandOptions.kt b/tools/protologtool/src/com/android/protolog/tool/CommandOptions.kt
index a359155..2690bc5 100644
--- a/tools/protologtool/src/com/android/protolog/tool/CommandOptions.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/CommandOptions.kt
@@ -25,6 +25,7 @@
         const val READ_LOG_CMD = "read-log"
         private val commands = setOf(TRANSFORM_CALLS_CMD, GENERATE_CONFIG_CMD, READ_LOG_CMD)
 
+        // TODO: This is always the same. I don't think it's required
         private const val PROTOLOG_CLASS_PARAM = "--protolog-class"
         private const val PROTOLOGGROUP_CLASS_PARAM = "--loggroups-class"
         private const val PROTOLOGGROUP_JAR_PARAM = "--loggroups-jar"
diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
index ea2c8da..837dae9 100644
--- a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
@@ -24,11 +24,17 @@
 import com.github.javaparser.ParserConfiguration
 import com.github.javaparser.StaticJavaParser
 import com.github.javaparser.ast.CompilationUnit
+import com.github.javaparser.ast.NodeList
 import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration
+import com.github.javaparser.ast.body.InitializerDeclaration
+import com.github.javaparser.ast.expr.FieldAccessExpr
 import com.github.javaparser.ast.expr.MethodCallExpr
+import com.github.javaparser.ast.expr.NameExpr
 import com.github.javaparser.ast.expr.NullLiteralExpr
+import com.github.javaparser.ast.expr.ObjectCreationExpr
 import com.github.javaparser.ast.expr.SimpleName
 import com.github.javaparser.ast.expr.StringLiteralExpr
+import com.github.javaparser.ast.stmt.BlockStmt
 import java.io.File
 import java.io.FileInputStream
 import java.io.FileNotFoundException
@@ -96,7 +102,8 @@
         outJar.putNextEntry(zipEntry(protologImplPath))
 
         outJar.write(generateProtoLogImpl(protologImplName, command.viewerConfigFilePathArg,
-            command.legacyViewerConfigFilePathArg, command.legacyOutputFilePath).toByteArray())
+            command.legacyViewerConfigFilePathArg, command.legacyOutputFilePath,
+            groups, command.protoLogGroupsClassNameArg).toByteArray())
 
         val executor = newThreadPool()
 
@@ -140,6 +147,8 @@
         viewerConfigFilePath: String,
         legacyViewerConfigFilePath: String?,
         legacyOutputFilePath: String?,
+        groups: Map<String, LogGroup>,
+        protoLogGroupsClassName: String,
     ): String {
         val file = File(PROTOLOG_IMPL_SRC_PATH)
 
@@ -160,7 +169,8 @@
         classNameNode.setId(protoLogImplGenName)
 
         injectConstants(classDeclaration,
-            viewerConfigFilePath, legacyViewerConfigFilePath, legacyOutputFilePath)
+            viewerConfigFilePath, legacyViewerConfigFilePath, legacyOutputFilePath, groups,
+            protoLogGroupsClassName)
 
         return code.toString()
     }
@@ -169,7 +179,9 @@
         classDeclaration: ClassOrInterfaceDeclaration,
         viewerConfigFilePath: String,
         legacyViewerConfigFilePath: String?,
-        legacyOutputFilePath: String?
+        legacyOutputFilePath: String?,
+        groups: Map<String, LogGroup>,
+        protoLogGroupsClassName: String
     ) {
         classDeclaration.fields.forEach { field ->
             field.getAnnotationByClass(ProtoLogToolInjected::class.java)
@@ -197,6 +209,35 @@
                                                 StringLiteralExpr(it)
                                             } ?: NullLiteralExpr())
                                 }
+                                ProtoLogToolInjected.Value.LOG_GROUPS.name -> {
+                                    val initializerBlockStmt = BlockStmt()
+                                    for (group in groups) {
+                                        initializerBlockStmt.addStatement(
+                                            MethodCallExpr()
+                                                    .setName("put")
+                                                    .setArguments(
+                                                        NodeList(StringLiteralExpr(group.key),
+                                                            FieldAccessExpr()
+                                                                    .setScope(
+                                                                        NameExpr(
+                                                                            protoLogGroupsClassName
+                                                                        ))
+                                                                    .setName(group.value.name)))
+                                        )
+                                        group.key
+                                    }
+
+                                    val treeMapCreation = ObjectCreationExpr()
+                                            .setType("TreeMap<String, IProtoLogGroup>")
+                                            .setAnonymousClassBody(NodeList(
+                                                InitializerDeclaration().setBody(
+                                                    initializerBlockStmt
+                                                )
+                                            ))
+
+                                    field.setFinal(true)
+                                    field.variables.first().setInitializer(treeMapCreation)
+                                }
                                 else -> error("Unhandled ProtoLogToolInjected value: $valueName.")
                             }
                         }
diff --git a/tools/streaming_proto/Android.bp b/tools/streaming_proto/Android.bp
index b18bdff..b1b314fc 100644
--- a/tools/streaming_proto/Android.bp
+++ b/tools/streaming_proto/Android.bp
@@ -17,6 +17,7 @@
 // ==========================================================
 // Build the host executable: protoc-gen-javastream
 // ==========================================================
+
 package {
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
@@ -41,6 +42,32 @@
     static_libs: ["libprotoc"],
 }
 
+// ==========================================================
+// Build the host static library: java_streaming_proto_lib
+// ==========================================================
+
+cc_library_host_static {
+    name: "java_streaming_proto_lib",
+    defaults: ["protoc-gen-stream-defaults"],
+    target: {
+        darwin: {
+            cflags: ["-D_DARWIN_UNLIMITED_STREAMS"],
+        },
+    },
+    cflags: [
+        "-Wno-format-y2k",
+        "-DSTATIC_ANDROIDFW_FOR_TOOLS",
+    ],
+
+    srcs: [
+        "java/java_proto_stream_code_generator.cpp",
+    ],
+}
+
+// ==========================================================
+// Build the host executable: protoc-gen-javastream
+// ==========================================================
+
 cc_binary_host {
     name: "protoc-gen-javastream",
     srcs: [
@@ -48,8 +75,13 @@
     ],
 
     defaults: ["protoc-gen-stream-defaults"],
+    static_libs: ["java_streaming_proto_lib"],
 }
 
+// ==========================================================
+// Build the host executable: protoc-gen-cppstream
+// ==========================================================
+
 cc_binary_host {
     name: "protoc-gen-cppstream",
     srcs: [
@@ -60,13 +92,31 @@
 }
 
 // ==========================================================
+// Build the host tests: StreamingProtoTest
+// ==========================================================
+
+cc_test_host {
+    name: "StreamingProtoTest",
+    defaults: ["protoc-gen-stream-defaults"],
+    srcs: [
+        "test/unit/**/*.cpp",
+    ],
+    static_libs: [
+        "java_streaming_proto_lib",
+        "libgmock",
+        "libgtest",
+    ],
+}
+
+// ==========================================================
 // Build the java test
 // ==========================================================
+
 java_library {
-    name: "StreamingProtoTest",
+    name: "StreamingProtoJavaIntegrationTest",
     srcs: [
-        "test/**/*.java",
-        "test/**/*.proto",
+        "test/integration/**/*.java",
+        "test/integration/**/*.proto",
     ],
     proto: {
         type: "stream",
diff --git a/tools/streaming_proto/java/java_proto_stream_code_generator.cpp b/tools/streaming_proto/java/java_proto_stream_code_generator.cpp
new file mode 100644
index 0000000..9d61111
--- /dev/null
+++ b/tools/streaming_proto/java/java_proto_stream_code_generator.cpp
@@ -0,0 +1,339 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+#include "java_proto_stream_code_generator.h"
+
+#include <stdio.h>
+
+#include <iomanip>
+#include <iostream>
+#include <map>
+#include <sstream>
+#include <string>
+
+#include "Errors.h"
+
+using namespace android::stream_proto;
+using namespace google::protobuf::io;
+using namespace std;
+
+/**
+ * If the descriptor gives us a class name, use that. Otherwise make one up from
+ * the filename of the .proto file.
+ */
+static string make_outer_class_name(const FileDescriptorProto& file_descriptor) {
+    string name = file_descriptor.options().java_outer_classname();
+    if (name.size() == 0) {
+        name = to_camel_case(file_base_name(file_descriptor.name()));
+        if (name.size() == 0) {
+            ERRORS.Add(UNKNOWN_FILE, UNKNOWN_LINE,
+                       "Unable to make an outer class name for file: %s",
+                       file_descriptor.name().c_str());
+            name = "Unknown";
+        }
+    }
+    return name;
+}
+
+/**
+ * Figure out the package name that we are generating.
+ */
+static string make_java_package(const FileDescriptorProto& file_descriptor) {
+    if (file_descriptor.options().has_java_package()) {
+        return file_descriptor.options().java_package();
+    } else {
+        return file_descriptor.package();
+    }
+}
+
+/**
+ * Figure out the name of the file we are generating.
+ */
+static string make_file_name(const FileDescriptorProto& file_descriptor, const string& class_name) {
+    string const package = make_java_package(file_descriptor);
+    string result;
+    if (package.size() > 0) {
+        result = replace_string(package, '.', '/');
+        result += '/';
+    }
+
+    result += class_name;
+    result += ".java";
+
+    return result;
+}
+
+static string indent_more(const string& indent) {
+    return indent + INDENT;
+}
+
+/**
+ * Write the constants for an enum.
+ */
+static void write_enum(stringstream& text, const EnumDescriptorProto& enu, const string& indent) {
+    const int N = enu.value_size();
+    text << indent << "// enum " << enu.name() << endl;
+    for (int i = 0; i < N; i++) {
+        const EnumValueDescriptorProto& value = enu.value(i);
+        text << indent << "public static final int " << make_constant_name(value.name()) << " = "
+             << value.number() << ";" << endl;
+    }
+    text << endl;
+}
+
+/**
+ * Write a field.
+ */
+static void write_field(stringstream& text, const FieldDescriptorProto& field,
+                        const string& indent) {
+    string optional_comment =
+            field.label() == FieldDescriptorProto::LABEL_OPTIONAL ? "optional " : "";
+    string repeated_comment =
+            field.label() == FieldDescriptorProto::LABEL_REPEATED ? "repeated " : "";
+    string proto_type = get_proto_type(field);
+    string packed_comment = field.options().packed() ? " [packed=true]" : "";
+    text << indent << "// " << optional_comment << repeated_comment << proto_type << ' '
+         << field.name() << " = " << field.number() << packed_comment << ';' << endl;
+
+    text << indent << "public static final long " << make_constant_name(field.name()) << " = 0x";
+
+    ios::fmtflags fmt(text.flags());
+    text << setfill('0') << setw(16) << hex << get_field_id(field);
+    text.flags(fmt);
+
+    text << "L;" << endl;
+
+    text << endl;
+}
+
+/**
+ * Write a Message constants class.
+ */
+static void write_message(stringstream& text, const DescriptorProto& message,
+                          const string& indent) {
+    int N;
+    const string indented = indent_more(indent);
+
+    text << indent << "// message " << message.name() << endl;
+    text << indent << "public final class " << message.name() << " {" << endl;
+    text << endl;
+
+    // Enums
+    N = message.enum_type_size();
+    for (int i = 0; i < N; i++) {
+        write_enum(text, message.enum_type(i), indented);
+    }
+
+    // Nested classes
+    N = message.nested_type_size();
+    for (int i = 0; i < N; i++) {
+        write_message(text, message.nested_type(i), indented);
+    }
+
+    // Fields
+    N = message.field_size();
+    for (int i = 0; i < N; i++) {
+        write_field(text, message.field(i), indented);
+    }
+
+    text << indent << "}" << endl;
+    text << endl;
+}
+
+/**
+ * Write the contents of a file.
+ *
+ * If there are enums and generate_outer is false, invalid java code will be generated.
+ */
+static void write_file(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor,
+                       const string& filename, bool generate_outer,
+                       const vector<EnumDescriptorProto>& enums,
+                       const vector<DescriptorProto>& messages) {
+    stringstream text;
+
+    string const package_name = make_java_package(file_descriptor);
+    string const outer_class_name = make_outer_class_name(file_descriptor);
+
+    text << "// Generated by protoc-gen-javastream. DO NOT MODIFY." << endl;
+    text << "// source: " << file_descriptor.name() << endl << endl;
+
+    if (package_name.size() > 0) {
+        if (package_name.size() > 0) {
+            text << "package " << package_name << ";" << endl;
+            text << endl;
+        }
+    }
+
+    // This bit of policy is android api rules specific: Raw proto classes
+    // must never be in the API
+    text << "/** @hide */" << endl;
+    //    text << "@android.annotation.TestApi" << endl;
+
+    if (generate_outer) {
+        text << "public final class " << outer_class_name << " {" << endl;
+        text << endl;
+    }
+
+    size_t N;
+    const string indented = generate_outer ? indent_more("") : string();
+
+    N = enums.size();
+    for (size_t i = 0; i < N; i++) {
+        write_enum(text, enums[i], indented);
+    }
+
+    N = messages.size();
+    for (size_t i = 0; i < N; i++) {
+        write_message(text, messages[i], indented);
+    }
+
+    if (generate_outer) {
+        text << "}" << endl;
+    }
+
+    CodeGeneratorResponse::File* file_response = response->add_file();
+    file_response->set_name(filename);
+    file_response->set_content(text.str());
+}
+
+/**
+ * Write one file per class.  Put all of the enums into the "outer" class.
+ */
+static void write_multiple_files(CodeGeneratorResponse* response,
+                                 const FileDescriptorProto& file_descriptor,
+                                 set<string> messages_to_compile) {
+    // If there is anything to put in the outer class file, create one
+    if (file_descriptor.enum_type_size() > 0) {
+        vector<EnumDescriptorProto> enums;
+        int N = file_descriptor.enum_type_size();
+        for (int i = 0; i < N; i++) {
+            auto enum_full_name =
+                    file_descriptor.package() + "." + file_descriptor.enum_type(i).name();
+            if (!messages_to_compile.empty() && !messages_to_compile.count(enum_full_name)) {
+                continue;
+            }
+            enums.push_back(file_descriptor.enum_type(i));
+        }
+
+        vector<DescriptorProto> messages;
+
+        if (messages_to_compile.empty() || !enums.empty()) {
+            write_file(response, file_descriptor,
+                       make_file_name(file_descriptor, make_outer_class_name(file_descriptor)),
+                       true, enums, messages);
+        }
+    }
+
+    // For each of the message types, make a file
+    int N = file_descriptor.message_type_size();
+    for (int i = 0; i < N; i++) {
+        vector<EnumDescriptorProto> enums;
+
+        vector<DescriptorProto> messages;
+
+        auto message_full_name =
+                file_descriptor.package() + "." + file_descriptor.message_type(i).name();
+        if (!messages_to_compile.empty() && !messages_to_compile.count(message_full_name)) {
+            continue;
+        }
+        messages.push_back(file_descriptor.message_type(i));
+
+        if (messages_to_compile.empty() || !messages.empty()) {
+            write_file(response, file_descriptor,
+                       make_file_name(file_descriptor, file_descriptor.message_type(i).name()),
+                       false, enums, messages);
+        }
+    }
+}
+
+static void write_single_file(CodeGeneratorResponse* response,
+                              const FileDescriptorProto& file_descriptor,
+                              set<string> messages_to_compile) {
+    int N;
+
+    vector<EnumDescriptorProto> enums;
+    N = file_descriptor.enum_type_size();
+    for (int i = 0; i < N; i++) {
+        auto enum_full_name = file_descriptor.package() + "." + file_descriptor.enum_type(i).name();
+        if (!messages_to_compile.empty() && !messages_to_compile.count(enum_full_name)) {
+            continue;
+        }
+
+        enums.push_back(file_descriptor.enum_type(i));
+    }
+
+    vector<DescriptorProto> messages;
+    N = file_descriptor.message_type_size();
+    for (int i = 0; i < N; i++) {
+        auto message_full_name =
+                file_descriptor.package() + "." + file_descriptor.message_type(i).name();
+
+        if (!messages_to_compile.empty() && !messages_to_compile.count(message_full_name)) {
+            continue;
+        }
+
+        messages.push_back(file_descriptor.message_type(i));
+    }
+
+    if (messages_to_compile.empty() || !enums.empty() || !messages.empty()) {
+        write_file(response, file_descriptor,
+                   make_file_name(file_descriptor, make_outer_class_name(file_descriptor)), true,
+                   enums, messages);
+    }
+}
+
+static void parse_args_string(stringstream args_string_stream,
+                              set<string>* messages_to_compile_out) {
+    string line;
+    while (getline(args_string_stream, line, ';')) {
+        stringstream line_ss(line);
+        string arg_name;
+        getline(line_ss, arg_name, ':');
+        if (arg_name == "include_filter") {
+            string full_message_name;
+            while (getline(line_ss, full_message_name, ',')) {
+                messages_to_compile_out->insert(full_message_name);
+            }
+        } else {
+            ERRORS.Add(UNKNOWN_FILE, UNKNOWN_LINE, "Unexpected argument '%s'.", arg_name.c_str());
+        }
+    }
+}
+
+CodeGeneratorResponse generate_java_protostream_code(CodeGeneratorRequest request) {
+    CodeGeneratorResponse response;
+
+    set<string> messages_to_compile;
+    auto request_params = request.parameter();
+    if (!request_params.empty()) {
+        parse_args_string(stringstream(request_params), &messages_to_compile);
+    }
+
+    // Build the files we need.
+    const int N = request.proto_file_size();
+    for (int i = 0; i < N; i++) {
+        const FileDescriptorProto& file_descriptor = request.proto_file(i);
+        if (should_generate_for_file(request, file_descriptor.name())) {
+            if (file_descriptor.options().java_multiple_files()) {
+                write_multiple_files(&response, file_descriptor, messages_to_compile);
+            } else {
+                write_single_file(&response, file_descriptor, messages_to_compile);
+            }
+        }
+    }
+
+    return response;
+}
diff --git a/tools/streaming_proto/java/java_proto_stream_code_generator.h b/tools/streaming_proto/java/java_proto_stream_code_generator.h
new file mode 100644
index 0000000..d2492f7
--- /dev/null
+++ b/tools/streaming_proto/java/java_proto_stream_code_generator.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+#ifndef AOSP_MAIN_FRAMEWORKS_BASE_JAVAPROTOSTREAMCODEGENERATOR_H
+#define AOSP_MAIN_FRAMEWORKS_BASE_JAVAPROTOSTREAMCODEGENERATOR_H
+
+#include "stream_proto_utils.h"
+#include "string_utils.h"
+
+using namespace android::stream_proto;
+using namespace google::protobuf::io;
+using namespace std;
+
+CodeGeneratorResponse generate_java_protostream_code(CodeGeneratorRequest request);
+
+#endif // AOSP_MAIN_FRAMEWORKS_BASE_JAVAPROTOSTREAMCODEGENERATOR_H
\ No newline at end of file
diff --git a/tools/streaming_proto/java/main.cpp b/tools/streaming_proto/java/main.cpp
index c9c50a5..5b35504 100644
--- a/tools/streaming_proto/java/main.cpp
+++ b/tools/streaming_proto/java/main.cpp
@@ -1,268 +1,21 @@
-#include "Errors.h"
-#include "stream_proto_utils.h"
-#include "string_utils.h"
-
 #include <stdio.h>
+
 #include <iomanip>
 #include <iostream>
-#include <sstream>
 #include <map>
+#include <sstream>
+#include <string>
+
+#include "Errors.h"
+#include "java_proto_stream_code_generator.h"
+#include "stream_proto_utils.h"
 
 using namespace android::stream_proto;
 using namespace google::protobuf::io;
 using namespace std;
 
 /**
- * If the descriptor gives us a class name, use that. Otherwise make one up from
- * the filename of the .proto file.
- */
-static string
-make_outer_class_name(const FileDescriptorProto& file_descriptor)
-{
-    string name = file_descriptor.options().java_outer_classname();
-    if (name.size() == 0) {
-        name = to_camel_case(file_base_name(file_descriptor.name()));
-        if (name.size() == 0) {
-            ERRORS.Add(UNKNOWN_FILE, UNKNOWN_LINE,
-                    "Unable to make an outer class name for file: %s",
-                    file_descriptor.name().c_str());
-            name = "Unknown";
-        }
-    }
-    return name;
-}
-
-/**
- * Figure out the package name that we are generating.
- */
-static string
-make_java_package(const FileDescriptorProto& file_descriptor) {
-    if (file_descriptor.options().has_java_package()) {
-        return file_descriptor.options().java_package();
-    } else {
-        return file_descriptor.package();
-    }
-}
-
-/**
- * Figure out the name of the file we are generating.
- */
-static string
-make_file_name(const FileDescriptorProto& file_descriptor, const string& class_name)
-{
-    string const package = make_java_package(file_descriptor);
-    string result;
-    if (package.size() > 0) {
-        result = replace_string(package, '.', '/');
-        result += '/';
-    }
-
-    result += class_name;
-    result += ".java";
-
-    return result;
-}
-
-static string
-indent_more(const string& indent)
-{
-    return indent + INDENT;
-}
-
-/**
- * Write the constants for an enum.
- */
-static void
-write_enum(stringstream& text, const EnumDescriptorProto& enu, const string& indent)
-{
-    const int N = enu.value_size();
-    text << indent << "// enum " << enu.name() << endl;
-    for (int i=0; i<N; i++) {
-        const EnumValueDescriptorProto& value = enu.value(i);
-        text << indent << "public static final int "
-                << make_constant_name(value.name())
-                << " = " << value.number() << ";" << endl;
-    }
-    text << endl;
-}
-
-/**
- * Write a field.
- */
-static void
-write_field(stringstream& text, const FieldDescriptorProto& field, const string& indent)
-{
-    string optional_comment = field.label() == FieldDescriptorProto::LABEL_OPTIONAL
-            ? "optional " : "";
-    string repeated_comment = field.label() == FieldDescriptorProto::LABEL_REPEATED
-            ? "repeated " : "";
-    string proto_type = get_proto_type(field);
-    string packed_comment = field.options().packed()
-            ? " [packed=true]" : "";
-    text << indent << "// " << optional_comment << repeated_comment << proto_type << ' '
-            << field.name() << " = " << field.number() << packed_comment << ';' << endl;
-
-    text << indent << "public static final long " << make_constant_name(field.name()) << " = 0x";
-
-    ios::fmtflags fmt(text.flags());
-    text << setfill('0') << setw(16) << hex << get_field_id(field);
-    text.flags(fmt);
-
-    text << "L;" << endl;
-
-    text << endl;
-}
-
-/**
- * Write a Message constants class.
- */
-static void
-write_message(stringstream& text, const DescriptorProto& message, const string& indent)
-{
-    int N;
-    const string indented = indent_more(indent);
-
-    text << indent << "// message " << message.name() << endl;
-    text << indent << "public final class " << message.name() << " {" << endl;
-    text << endl;
-
-    // Enums
-    N = message.enum_type_size();
-    for (int i=0; i<N; i++) {
-        write_enum(text, message.enum_type(i), indented);
-    }
-
-    // Nested classes
-    N = message.nested_type_size();
-    for (int i=0; i<N; i++) {
-        write_message(text, message.nested_type(i), indented);
-    }
-
-    // Fields
-    N = message.field_size();
-    for (int i=0; i<N; i++) {
-        write_field(text, message.field(i), indented);
-    }
-
-    text << indent << "}" << endl;
-    text << endl;
-}
-
-/**
- * Write the contents of a file.
  *
- * If there are enums and generate_outer is false, invalid java code will be generated.
- */
-static void
-write_file(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor,
-        const string& filename, bool generate_outer,
-        const vector<EnumDescriptorProto>& enums, const vector<DescriptorProto>& messages)
-{
-    stringstream text;
-
-    string const package_name = make_java_package(file_descriptor);
-    string const outer_class_name = make_outer_class_name(file_descriptor);
-
-    text << "// Generated by protoc-gen-javastream. DO NOT MODIFY." << endl;
-    text << "// source: " << file_descriptor.name() << endl << endl;
-
-    if (package_name.size() > 0) {
-        if (package_name.size() > 0) {
-            text << "package " << package_name << ";" << endl;
-            text << endl;
-        }
-    }
-
-    // This bit of policy is android api rules specific: Raw proto classes
-    // must never be in the API
-    text << "/** @hide */" << endl;
-//    text << "@android.annotation.TestApi" << endl;
-
-    if (generate_outer) {
-        text << "public final class " << outer_class_name << " {" << endl;
-        text << endl;
-    }
-
-    size_t N;
-    const string indented = generate_outer ? indent_more("") : string();
-    
-    N = enums.size();
-    for (size_t i=0; i<N; i++) {
-        write_enum(text, enums[i], indented);
-    }
-
-    N = messages.size();
-    for (size_t i=0; i<N; i++) {
-        write_message(text, messages[i], indented);
-    }
-
-    if (generate_outer) {
-        text << "}" << endl;
-    }
-
-    CodeGeneratorResponse::File* file_response = response->add_file();
-    file_response->set_name(filename);
-    file_response->set_content(text.str());
-}
-
-/**
- * Write one file per class.  Put all of the enums into the "outer" class.
- */
-static void
-write_multiple_files(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor)
-{
-    // If there is anything to put in the outer class file, create one
-    if (file_descriptor.enum_type_size() > 0) {
-        vector<EnumDescriptorProto> enums;
-        int N = file_descriptor.enum_type_size();
-        for (int i=0; i<N; i++) {
-            enums.push_back(file_descriptor.enum_type(i));
-        }
-
-        vector<DescriptorProto> messages;
-
-        write_file(response, file_descriptor,
-                make_file_name(file_descriptor, make_outer_class_name(file_descriptor)),
-                true, enums, messages);
-    }
-
-    // For each of the message types, make a file
-    int N = file_descriptor.message_type_size();
-    for (int i=0; i<N; i++) {
-        vector<EnumDescriptorProto> enums;
-
-        vector<DescriptorProto> messages;
-        messages.push_back(file_descriptor.message_type(i));
-
-        write_file(response, file_descriptor,
-                make_file_name(file_descriptor, file_descriptor.message_type(i).name()),
-                false, enums, messages);
-    }
-}
-
-static void
-write_single_file(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor)
-{
-    int N;
-
-    vector<EnumDescriptorProto> enums;
-    N = file_descriptor.enum_type_size();
-    for (int i=0; i<N; i++) {
-        enums.push_back(file_descriptor.enum_type(i));
-    }
-
-    vector<DescriptorProto> messages;
-    N = file_descriptor.message_type_size();
-    for (int i=0; i<N; i++) {
-        messages.push_back(file_descriptor.message_type(i));
-    }
-
-    write_file(response, file_descriptor,
-            make_file_name(file_descriptor, make_outer_class_name(file_descriptor)),
-            true, enums, messages);
-}
-
-/**
  * Main.
  */
 int
@@ -273,24 +26,11 @@
 
     GOOGLE_PROTOBUF_VERIFY_VERSION;
 
-    CodeGeneratorRequest request;
-    CodeGeneratorResponse response;
-
     // Read the request
+    CodeGeneratorRequest request;
     request.ParseFromIstream(&cin);
 
-    // Build the files we need.
-    const int N = request.proto_file_size();
-    for (int i=0; i<N; i++) {
-        const FileDescriptorProto& file_descriptor = request.proto_file(i);
-        if (should_generate_for_file(request, file_descriptor.name())) {
-            if (file_descriptor.options().java_multiple_files()) {
-                write_multiple_files(&response, file_descriptor);
-            } else {
-                write_single_file(&response, file_descriptor);
-            }
-        }
-    }
+    CodeGeneratorResponse response = generate_java_protostream_code(request);
 
     // If we had errors, don't write the response. Print the errors and exit.
     if (ERRORS.HasErrors()) {
diff --git a/tools/streaming_proto/test/imported.proto b/tools/streaming_proto/test/integration/imported.proto
similarity index 100%
rename from tools/streaming_proto/test/imported.proto
rename to tools/streaming_proto/test/integration/imported.proto
diff --git a/tools/streaming_proto/test/integration/src/com/android/streaming_proto_test/Main.java b/tools/streaming_proto/test/integration/src/com/android/streaming_proto_test/Main.java
new file mode 100644
index 0000000..2a7001b
--- /dev/null
+++ b/tools/streaming_proto/test/integration/src/com/android/streaming_proto_test/Main.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 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.streaming_proto_test;
+
+public class Main {
+    public void main(String[] argv) {
+        System.out.println("hello world");
+    }
+}
diff --git a/tools/streaming_proto/test/test.proto b/tools/streaming_proto/test/integration/test.proto
similarity index 97%
rename from tools/streaming_proto/test/test.proto
rename to tools/streaming_proto/test/integration/test.proto
index de80ed6..3cf81b4 100644
--- a/tools/streaming_proto/test/test.proto
+++ b/tools/streaming_proto/test/integration/test.proto
@@ -16,7 +16,7 @@
 
 syntax = "proto2";
 
-import "frameworks/base/tools/streaming_proto/test/imported.proto";
+import "frameworks/base/tools/streaming_proto/test/integration/imported.proto";
 
 package com.android.streaming_proto_test;
 
diff --git a/tools/streaming_proto/test/src/com/android/streaming_proto_test/Main.java b/tools/streaming_proto/test/src/com/android/streaming_proto_test/Main.java
deleted file mode 100644
index 1246f53..0000000
--- a/tools/streaming_proto/test/src/com/android/streaming_proto_test/Main.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.android.streaming_proto_test;
-
-public class Main {
-    public void main(String[] argv) {
-        System.out.println("hello world");
-    }
-}
diff --git a/tools/streaming_proto/test/unit/streaming_proto_java.cpp b/tools/streaming_proto/test/unit/streaming_proto_java.cpp
new file mode 100644
index 0000000..8df9716
--- /dev/null
+++ b/tools/streaming_proto/test/unit/streaming_proto_java.cpp
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "java/java_proto_stream_code_generator.h"
+
+using ::testing::HasSubstr;
+using ::testing::Not;
+
+static void add_my_test_proto_file(CodeGeneratorRequest* request) {
+    request->add_file_to_generate("MyTestProtoFile");
+
+    FileDescriptorProto* file_desc = request->add_proto_file();
+    file_desc->set_name("MyTestProtoFile");
+    file_desc->set_package("test.package");
+
+    auto* file_options = file_desc->mutable_options();
+    file_options->set_java_multiple_files(false);
+
+    auto* message = file_desc->add_message_type();
+    message->set_name("MyTestMessage");
+
+    auto* field = message->add_field();
+    field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+    field->set_name("my_test_field");
+
+    field = message->add_field();
+    field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+    field->set_name("my_other_test_field");
+
+    field = message->add_field();
+    field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+    field->set_name("my_other_test_message");
+}
+
+static void add_my_other_test_proto_file(CodeGeneratorRequest* request) {
+    request->add_file_to_generate("MyOtherTestProtoFile");
+
+    FileDescriptorProto* file_desc = request->add_proto_file();
+    file_desc->set_name("MyOtherTestProtoFile");
+    file_desc->set_package("test.package");
+
+    auto* file_options = file_desc->mutable_options();
+    file_options->set_java_multiple_files(false);
+
+    auto* message = file_desc->add_message_type();
+    message->set_name("MyOtherTestMessage");
+
+    auto* field = message->add_field();
+    field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+    field->set_name("a_test_field");
+
+    field = message->add_field();
+    field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+    field->set_name("another_test_field");
+}
+
+static CodeGeneratorRequest create_simple_two_file_request() {
+    CodeGeneratorRequest request;
+
+    add_my_test_proto_file(&request);
+    add_my_other_test_proto_file(&request);
+
+    return request;
+}
+
+static CodeGeneratorRequest create_simple_multi_file_request() {
+    CodeGeneratorRequest request;
+
+    request.add_file_to_generate("MyMultiMessageTestProtoFile");
+
+    FileDescriptorProto* file_desc = request.add_proto_file();
+    file_desc->set_name("MyMultiMessageTestProtoFile");
+    file_desc->set_package("test.package");
+
+    auto* file_options = file_desc->mutable_options();
+    file_options->set_java_multiple_files(true);
+
+    auto* message = file_desc->add_message_type();
+    message->set_name("MyTestMessage");
+
+    auto* field = message->add_field();
+    field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+    field->set_name("my_test_field");
+
+    field = message->add_field();
+    field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+    field->set_name("my_other_test_field");
+
+    field = message->add_field();
+    field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+    field->set_name("my_other_test_message");
+
+    message = file_desc->add_message_type();
+    message->set_name("MyOtherTestMessage");
+
+    field = message->add_field();
+    field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+    field->set_name("a_test_field");
+
+    field = message->add_field();
+    field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+    field->set_name("another_test_field");
+
+    return request;
+}
+
+TEST(StreamingProtoJavaTest, NoFilter) {
+    CodeGeneratorRequest request = create_simple_two_file_request();
+    CodeGeneratorResponse response = generate_java_protostream_code(request);
+
+    auto generated_file_count = response.file_size();
+    EXPECT_EQ(generated_file_count, 2);
+
+    EXPECT_EQ(response.file(0).name(), "test/package/MyTestProtoFile.java");
+    EXPECT_THAT(response.file(0).content(), HasSubstr("class MyTestProtoFile"));
+    EXPECT_THAT(response.file(0).content(), HasSubstr("class MyTestMessage"));
+    EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_TEST_FIELD"));
+    EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_OTHER_TEST_FIELD"));
+
+    EXPECT_EQ(response.file(1).name(), "test/package/MyOtherTestProtoFile.java");
+    EXPECT_THAT(response.file(1).content(), HasSubstr("class MyOtherTestProtoFile"));
+    EXPECT_THAT(response.file(1).content(), HasSubstr("class MyOtherTestMessage"));
+    EXPECT_THAT(response.file(1).content(), HasSubstr("long A_TEST_FIELD"));
+    EXPECT_THAT(response.file(1).content(), HasSubstr("long ANOTHER_TEST_FIELD"));
+}
+
+TEST(StreamingProtoJavaTest, WithFilter) {
+    CodeGeneratorRequest request = create_simple_two_file_request();
+    request.set_parameter("include_filter:test.package.MyTestMessage");
+    CodeGeneratorResponse response = generate_java_protostream_code(request);
+
+    auto generated_file_count = response.file_size();
+    EXPECT_EQ(generated_file_count, 1);
+
+    EXPECT_EQ(response.file(0).name(), "test/package/MyTestProtoFile.java");
+    EXPECT_THAT(response.file(0).content(), HasSubstr("class MyTestProtoFile"));
+    EXPECT_THAT(response.file(0).content(), HasSubstr("class MyTestMessage"));
+    EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_TEST_FIELD"));
+    EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_OTHER_TEST_FIELD"));
+}
+
+TEST(StreamingProtoJavaTest, WithoutFilter_MultipleJavaFiles) {
+    CodeGeneratorRequest request = create_simple_multi_file_request();
+    CodeGeneratorResponse response = generate_java_protostream_code(request);
+
+    auto generated_file_count = response.file_size();
+    EXPECT_EQ(generated_file_count, 2);
+
+    EXPECT_EQ(response.file(0).name(), "test/package/MyTestMessage.java");
+    EXPECT_THAT(response.file(0).content(), Not(HasSubstr("class MyTestProtoFile")));
+    EXPECT_THAT(response.file(0).content(), HasSubstr("class MyTestMessage"));
+    EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_TEST_FIELD"));
+    EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_OTHER_TEST_FIELD"));
+
+    EXPECT_EQ(response.file(1).name(), "test/package/MyOtherTestMessage.java");
+    EXPECT_THAT(response.file(1).content(), Not(HasSubstr("class MyOtherTestProtoFile")));
+    EXPECT_THAT(response.file(1).content(), HasSubstr("class MyOtherTestMessage"));
+    EXPECT_THAT(response.file(1).content(), HasSubstr("long A_TEST_FIELD"));
+    EXPECT_THAT(response.file(1).content(), HasSubstr("long ANOTHER_TEST_FIELD"));
+}
+
+TEST(StreamingProtoJavaTest, WithFilter_MultipleJavaFiles) {
+    CodeGeneratorRequest request = create_simple_multi_file_request();
+    request.set_parameter("include_filter:test.package.MyTestMessage");
+    CodeGeneratorResponse response = generate_java_protostream_code(request);
+
+    auto generated_file_count = response.file_size();
+    EXPECT_EQ(generated_file_count, 1);
+
+    EXPECT_EQ(response.file(0).name(), "test/package/MyTestMessage.java");
+    EXPECT_THAT(response.file(0).content(), Not(HasSubstr("class MyTestProtoFile")));
+    EXPECT_THAT(response.file(0).content(), HasSubstr("class MyTestMessage"));
+    EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_TEST_FIELD"));
+    EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_OTHER_TEST_FIELD"));
+}