Merge "Changed output of get-main-user." into udc-dev
diff --git a/cmds/gpu_counter_producer/Android.bp b/cmds/gpu_counter_producer/Android.bp
new file mode 100644
index 0000000..5b118ce
--- /dev/null
+++ b/cmds/gpu_counter_producer/Android.bp
@@ -0,0 +1,26 @@
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+cc_binary {
+    name: "gpu_counter_producer",
+
+    srcs: ["main.cpp"],
+
+    shared_libs: [
+        "libdl",
+        "liblog",
+    ],
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wunused",
+        "-Wunreachable-code",
+        "-fPIE",
+        "-pie",
+    ],
+
+    soc_specific: true,
+}
diff --git a/cmds/gpu_counter_producer/OWNERS b/cmds/gpu_counter_producer/OWNERS
new file mode 100644
index 0000000..892c2fb
--- /dev/null
+++ b/cmds/gpu_counter_producer/OWNERS
@@ -0,0 +1 @@
+pmuetschard@google.com
diff --git a/cmds/gpu_counter_producer/main.cpp b/cmds/gpu_counter_producer/main.cpp
new file mode 100644
index 0000000..1054cba
--- /dev/null
+++ b/cmds/gpu_counter_producer/main.cpp
@@ -0,0 +1,160 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "gpu_counters"
+
+#include <dlfcn.h>
+#include <fcntl.h>
+#include <log/log.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define _LOG(level, msg, ...)                                 \
+    do {                                                      \
+        fprintf(stderr, #level ": " msg "\n", ##__VA_ARGS__); \
+        ALOG##level(msg, ##__VA_ARGS__);                      \
+    } while (false)
+
+#define LOG_ERR(msg, ...) _LOG(E, msg, ##__VA_ARGS__)
+#define LOG_WARN(msg, ...) _LOG(W, msg, ##__VA_ARGS__)
+#define LOG_INFO(msg, ...) _LOG(I, msg, ##__VA_ARGS__)
+
+#define NELEM(x) (sizeof(x) / sizeof(x[0]))
+
+typedef void (*FN_PTR)(void);
+
+const char* kProducerPaths[] = {
+        "libgpudataproducer.so",
+};
+const char* kPidFileName = "/data/local/tmp/gpu_counter_producer.pid";
+
+static FN_PTR loadLibrary(const char* lib) {
+    char* error;
+
+    LOG_INFO("Trying %s", lib);
+    void* handle = dlopen(lib, RTLD_GLOBAL);
+    if ((error = dlerror()) != nullptr || handle == nullptr) {
+        LOG_WARN("Error loading lib: %s", error);
+        return nullptr;
+    }
+
+    FN_PTR startFunc = (FN_PTR)dlsym(handle, "start");
+    if ((error = dlerror()) != nullptr) {
+        LOG_ERR("Error looking for start symbol: %s", error);
+        dlclose(handle);
+        return nullptr;
+    }
+    return startFunc;
+}
+
+static void killExistingProcess() {
+    int fd = open(kPidFileName, O_RDONLY);
+    if (fd == -1) {
+        return;
+    }
+    char pidString[10];
+    if (read(fd, pidString, 10) > 0) {
+        int pid = -1;
+        sscanf(pidString, "%d", &pid);
+        if (pid > 0) {
+            kill(pid, SIGINT);
+        }
+    }
+    close(fd);
+}
+
+static bool writeToPidFile() {
+    killExistingProcess();
+    int fd = open(kPidFileName, O_CREAT | O_WRONLY | O_TRUNC,
+                  S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
+    if (fd == -1) {
+        return false;
+    }
+    pid_t pid = getpid();
+    char pidString[10];
+    sprintf(pidString, "%d", pid);
+    write(fd, pidString, strlen(pidString));
+    close(fd);
+    return true;
+}
+
+static void clearPidFile() {
+    unlink(kPidFileName);
+}
+
+static void usage(const char* pname) {
+    fprintf(stderr,
+            "Starts the GPU hardware counter profiling Perfetto data producer.\n\n"
+            "usage: %s [-hf]\n"
+            "   -f: run in the foreground.\n"
+            "   -h: this message.\n",
+            pname);
+}
+
+// Program to load the GPU Perfetto producer .so and call start().
+int main(int argc, char** argv) {
+    const char* pname = argv[0];
+    bool foreground = false;
+    int c;
+    while ((c = getopt(argc, argv, "fh")) != -1) {
+        switch (c) {
+            case 'f':
+                foreground = true;
+                break;
+            case '?':
+            case ':':
+            case 'h':
+                usage(pname);
+                return 1;
+        }
+    }
+
+    if (optind < argc) {
+        usage(pname);
+        return 1;
+    }
+
+    if (!foreground) {
+        daemon(0, 0);
+    }
+
+    if (!writeToPidFile()) {
+        LOG_ERR("Could not open %s", kPidFileName);
+        return 1;
+    }
+
+    dlerror(); // Clear any possibly ignored previous error.
+    FN_PTR startFunc = nullptr;
+    for (int i = 0; startFunc == nullptr && i < NELEM(kProducerPaths); i++) {
+        startFunc = loadLibrary(kProducerPaths[i]);
+    }
+
+    if (startFunc == nullptr) {
+        LOG_ERR("Did not find the producer library");
+        LOG_ERR("LD_LIBRARY_PATH=%s", getenv("LD_LIBRARY_PATH"));
+        clearPidFile();
+        return 1;
+    }
+
+    LOG_INFO("Calling start at %p", startFunc);
+    (*startFunc)();
+    LOG_WARN("Producer has exited.");
+
+    clearPidFile();
+    return 0;
+}
diff --git a/core/api/current.txt b/core/api/current.txt
index 4332d21..b92295a 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -4507,7 +4507,6 @@
     method @NonNull public final <T extends android.view.View> T requireViewById(@IdRes int);
     method public final void runOnUiThread(Runnable);
     method public void setActionBar(@Nullable android.widget.Toolbar);
-    method public void setAllowCrossUidActivitySwitchFromBelow(boolean);
     method public void setContentTransitionManager(android.transition.TransitionManager);
     method public void setContentView(@LayoutRes int);
     method public void setContentView(android.view.View);
@@ -13645,10 +13644,10 @@
   }
 
   public final class CredentialDescription implements android.os.Parcelable {
-    ctor public CredentialDescription(@NonNull String, @NonNull String, @NonNull java.util.List<android.service.credentials.CredentialEntry>);
+    ctor public CredentialDescription(@NonNull String, @NonNull java.util.Set<java.lang.String>, @NonNull java.util.List<android.service.credentials.CredentialEntry>);
     method public int describeContents();
     method @NonNull public java.util.List<android.service.credentials.CredentialEntry> getCredentialEntries();
-    method @NonNull public String getFlattenedRequestString();
+    method @NonNull public java.util.Set<java.lang.String> getSupportedElementKeys();
     method @NonNull public String getType();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.credentials.CredentialDescription> CREATOR;
@@ -13675,7 +13674,7 @@
     method public boolean isSystemProviderRequired();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.credentials.CredentialOption> CREATOR;
-    field public static final String FLATTENED_REQUEST = "android.credentials.GetCredentialOption.FLATTENED_REQUEST_STRING";
+    field public static final String SUPPORTED_ELEMENT_KEYS = "android.credentials.GetCredentialOption.SUPPORTED_ELEMENT_KEYS";
   }
 
   public static final class CredentialOption.Builder {
@@ -19894,8 +19893,8 @@
     method public int bulkTransfer(android.hardware.usb.UsbEndpoint, byte[], int, int, int);
     method public boolean claimInterface(android.hardware.usb.UsbInterface, boolean);
     method public void close();
-    method public int controlTransfer(int, int, int, int, byte[], int, int);
-    method public int controlTransfer(int, int, int, int, byte[], int, int, int);
+    method public int controlTransfer(int, int, int, int, @Nullable byte[], int, int);
+    method public int controlTransfer(int, int, int, int, @Nullable byte[], int, int, int);
     method public int getFileDescriptor();
     method public byte[] getRawDescriptors();
     method public String getSerial();
@@ -40720,7 +40719,6 @@
     method public abstract void onBeginGetCredential(@NonNull android.service.credentials.BeginGetCredentialRequest, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.service.credentials.BeginGetCredentialResponse,android.credentials.GetCredentialException>);
     method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);
     method public abstract void onClearCredentialState(@NonNull android.service.credentials.ClearCredentialStateRequest, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.credentials.ClearCredentialStateException>);
-    field @Deprecated public static final String CAPABILITY_META_DATA_KEY = "android.credentials.capabilities";
     field public static final String EXTRA_BEGIN_GET_CREDENTIAL_REQUEST = "android.service.credentials.extra.BEGIN_GET_CREDENTIAL_REQUEST";
     field public static final String EXTRA_BEGIN_GET_CREDENTIAL_RESPONSE = "android.service.credentials.extra.BEGIN_GET_CREDENTIAL_RESPONSE";
     field public static final String EXTRA_CREATE_CREDENTIAL_EXCEPTION = "android.service.credentials.extra.CREATE_CREDENTIAL_EXCEPTION";
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 7107bf7..e93467b 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -415,6 +415,7 @@
     method public void clickNotification(@Nullable String, int, int, boolean);
     method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void collapsePanels();
     method public void expandNotificationsPanel();
+    method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public int getLastSystemKey();
     method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void handleSystemKey(int);
     method public void sendNotificationFeedback(@Nullable String, @Nullable android.os.Bundle);
     method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setExpansionDisabledForSimNetworkLock(boolean);
@@ -2628,6 +2629,7 @@
     field public static final String SELECTED_SPELL_CHECKER_SUBTYPE = "selected_spell_checker_subtype";
     field public static final String SHOW_FIRST_CRASH_DIALOG_DEV_OPTION = "show_first_crash_dialog_dev_option";
     field public static final String SHOW_IME_WITH_HARD_KEYBOARD = "show_ime_with_hard_keyboard";
+    field public static final String STYLUS_BUTTONS_ENABLED = "stylus_buttons_enabled";
     field @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static final String SYNC_PARENT_SOUNDS = "sync_parent_sounds";
     field public static final String VOICE_INTERACTION_SERVICE = "voice_interaction_service";
   }
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 125e727..8021ce0 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -9209,6 +9209,7 @@
      *
      * @param allowed {@code true} to disable the UID restrictions; {@code false} to revert back to
      *                            the default behaviour
+     * @hide
      */
     public void setAllowCrossUidActivitySwitchFromBelow(boolean allowed) {
         ActivityClient.getInstance().setAllowCrossUidActivitySwitchFromBelow(mToken, allowed);
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 682fec8..5bedc9d 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -240,6 +240,7 @@
 import java.lang.ref.WeakReference;
 import java.lang.reflect.Method;
 import java.net.InetAddress;
+import java.nio.file.DirectoryStream;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.StandardCopyOption;
@@ -4352,18 +4353,20 @@
 
     static void handleAttachStartupAgents(String dataDir) {
         try {
-            Path code_cache = ContextImpl.getCodeCacheDirBeforeBind(new File(dataDir)).toPath();
-            if (!Files.exists(code_cache)) {
+            Path codeCache = ContextImpl.getCodeCacheDirBeforeBind(new File(dataDir)).toPath();
+            if (!Files.exists(codeCache)) {
                 return;
             }
-            Path startup_path = code_cache.resolve("startup_agents");
-            if (Files.exists(startup_path)) {
-                for (Path p : Files.newDirectoryStream(startup_path)) {
-                    handleAttachAgent(
-                            p.toAbsolutePath().toString()
-                            + "="
-                            + dataDir,
-                            null);
+            Path startupPath = codeCache.resolve("startup_agents");
+            if (Files.exists(startupPath)) {
+                try (DirectoryStream<Path> startupFiles = Files.newDirectoryStream(startupPath)) {
+                    for (Path p : startupFiles) {
+                        handleAttachAgent(
+                                p.toAbsolutePath().toString()
+                                        + "="
+                                        + dataDir,
+                                null);
+                    }
                 }
             }
         } catch (Exception e) {
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index f74be22..29f774c 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -752,6 +752,29 @@
     }
 
     /**
+     * Gets the last handled system key. A system key is a KeyEvent that the
+     * {@link com.android.server.policy.PhoneWindowManager} sends directly to the
+     * status bar, rather than forwarding to apps. If a key has never been sent to the
+     * status bar, will return -1.
+     *
+     * @return the keycode of the last KeyEvent that has been sent to the system.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.STATUS_BAR)
+    @TestApi
+    public int getLastSystemKey() {
+        try {
+            final IStatusBarService svc = getService();
+            if (svc != null) {
+                return svc.getLastSystemKey();
+            }
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+        return -1;
+    }
+
+    /**
      * Expand the settings panel.
      *
      * @hide
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 540342b..4d55fee 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -858,6 +858,8 @@
      *     (some versions of T may throw a {@code SecurityException}).</li>
      *     <li>From version U, this method should not be used
      *     and will always throw a @code SecurityException}.</li>
+     *     <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE}
+     *     can still access the real wallpaper on all versions. </li>
      * </ul>
      * <br>
      *
@@ -893,6 +895,8 @@
      *     (some versions of T may throw a {@code SecurityException}).</li>
      *     <li>From version U, this method should not be used
      *     and will always throw a @code SecurityException}.</li>
+     *     <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE}
+     *     can still access the real wallpaper on all versions. </li>
      * </ul>
      * <br>
      *
@@ -1147,6 +1151,8 @@
      *     (some versions of T may throw a {@code SecurityException}).</li>
      *     <li>From version U, this method should not be used
      *     and will always throw a @code SecurityException}.</li>
+     *     <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE}
+     *     can still access the real wallpaper on all versions. </li>
      * </ul>
      * <br>
      *
@@ -1176,6 +1182,8 @@
      *     (some versions of T may throw a {@code SecurityException}).</li>
      *     <li>From version U, this method should not be used
      *     and will always throw a @code SecurityException}.</li>
+     *     <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE}
+     *     can still access the real wallpaper on all versions. </li>
      * </ul>
      * <br>
      *
@@ -1214,6 +1222,8 @@
      *     (some versions of T may throw a {@code SecurityException}).</li>
      *     <li>From version U, this method should not be used
      *     and will always throw a @code SecurityException}.</li>
+     *     <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE}
+     *     can still access the real wallpaper on all versions. </li>
      * </ul>
      * <br>
      *
@@ -1247,6 +1257,8 @@
      *     (some versions of T may throw a {@code SecurityException}).</li>
      *     <li>From version U, this method should not be used
      *     and will always throw a @code SecurityException}.</li>
+     *     <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE}
+     *     can still access the real wallpaper on all versions. </li>
      * </ul>
      * <br>
      *
@@ -1287,6 +1299,8 @@
      *     (some versions of T may throw a {@code SecurityException}).</li>
      *     <li>From version U, this method should not be used
      *     and will always throw a @code SecurityException}.</li>
+     *     <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE}
+     *     can still access the real wallpaper on all versions. </li>
      * </ul>
      * <br>
      *
@@ -1314,6 +1328,8 @@
      *     (some versions of T may throw a {@code SecurityException}).</li>
      *     <li>From version U, this method should not be used
      *     and will always throw a @code SecurityException}.</li>
+     *     <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE}
+     *     can still access the real wallpaper on all versions. </li>
      * </ul>
      * <br>
      *
@@ -1458,6 +1474,8 @@
      *     (some versions of T may throw a {@code SecurityException}).</li>
      *     <li>From version U, this method should not be used
      *     and will always throw a @code SecurityException}.</li>
+     *     <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE}
+     *     can still access the real wallpaper on all versions. </li>
      * </ul>
      * <br>
      *
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index a7aba21..b967ca9 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -32,15 +32,12 @@
 import android.companion.AssociationInfo;
 import android.companion.virtual.audio.VirtualAudioDevice;
 import android.companion.virtual.audio.VirtualAudioDevice.AudioConfigurationChangeCallback;
-import android.companion.virtual.camera.VirtualCameraDevice;
-import android.companion.virtual.camera.VirtualCameraInput;
 import android.companion.virtual.sensor.VirtualSensor;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.graphics.Point;
-import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManager.VirtualDisplayFlag;
 import android.hardware.display.DisplayManagerGlobal;
@@ -329,7 +326,7 @@
     }
 
     /**
-     * A virtual device has its own virtual display, audio output, microphone, and camera etc. The
+     * A virtual device has its own virtual display, audio output, microphone, sensors, etc. The
      * creator of a virtual device can take the output from the virtual display and stream it over
      * to another device, and inject input events that are received from the remote device.
      *
@@ -408,8 +405,6 @@
                     }
                 };
         @Nullable
-        private VirtualCameraDevice mVirtualCameraDevice;
-        @Nullable
         private VirtualAudioDevice mVirtualAudioDevice;
 
         @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@@ -603,10 +598,6 @@
                 mVirtualAudioDevice.close();
                 mVirtualAudioDevice = null;
             }
-            if (mVirtualCameraDevice != null) {
-                mVirtualCameraDevice.close();
-                mVirtualCameraDevice = null;
-            }
         }
 
         /**
@@ -819,34 +810,6 @@
         }
 
         /**
-         * Creates a new virtual camera. If a virtual camera was already created, it will be closed.
-         *
-         * @param cameraName name of the virtual camera.
-         * @param characteristics camera characteristics.
-         * @param virtualCameraInput callback that provides input to camera.
-         * @param executor Executor on which camera input will be sent into system. Don't
-         *         use the Main Thread for this executor.
-         * @return newly created camera;
-         *
-         * @hide
-         */
-        @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
-        @NonNull
-        public VirtualCameraDevice createVirtualCameraDevice(
-                @NonNull String cameraName,
-                @NonNull CameraCharacteristics characteristics,
-                @NonNull VirtualCameraInput virtualCameraInput,
-                @NonNull Executor executor) {
-            if (mVirtualCameraDevice != null) {
-                mVirtualCameraDevice.close();
-            }
-            int deviceId = getDeviceId();
-            mVirtualCameraDevice = new VirtualCameraDevice(
-                    deviceId, cameraName, characteristics, virtualCameraInput, executor);
-            return mVirtualCameraDevice;
-        }
-
-        /**
          * Sets the visibility of the pointer icon for this VirtualDevice's associated displays.
          *
          * @param showPointerIcon True if the pointer should be shown; false otherwise. The default
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraDevice.java b/core/java/android/companion/virtual/camera/VirtualCameraDevice.java
deleted file mode 100644
index a7eba87..0000000
--- a/core/java/android/companion/virtual/camera/VirtualCameraDevice.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.companion.virtual.camera;
-
-import android.hardware.camera2.CameraCharacteristics;
-
-import androidx.annotation.NonNull;
-
-import java.util.Locale;
-import java.util.Objects;
-import java.util.concurrent.Executor;
-
-/**
- * Virtual camera that is used to send image data into system.
- *
- * @hide
- */
-public final class VirtualCameraDevice implements AutoCloseable {
-
-    @NonNull
-    private final String mCameraDeviceName;
-    @NonNull
-    private final CameraCharacteristics mCameraCharacteristics;
-    @NonNull
-    private final VirtualCameraOutput mCameraOutput;
-    private boolean mCameraRegistered = false;
-
-    /**
-     * VirtualCamera device constructor.
-     *
-     * @param virtualDeviceId ID of virtual device to which camera will be added.
-     * @param cameraName must be unique for each camera per virtual device.
-     * @param characteristics of camera that will be passed into system in order to describe
-     *         camera.
-     * @param virtualCameraInput component that provides image data.
-     * @param executor on which to collect image data and pass it into system.
-     */
-    public VirtualCameraDevice(int virtualDeviceId, @NonNull String cameraName,
-            @NonNull CameraCharacteristics characteristics,
-            @NonNull VirtualCameraInput virtualCameraInput, @NonNull Executor executor) {
-        Objects.requireNonNull(cameraName);
-        mCameraCharacteristics = Objects.requireNonNull(characteristics);
-        mCameraDeviceName = generateCameraDeviceName(virtualDeviceId, cameraName);
-        mCameraOutput = new VirtualCameraOutput(virtualCameraInput, executor);
-        registerCamera();
-    }
-
-    private static String generateCameraDeviceName(int deviceId, @NonNull String cameraName) {
-        return String.format(Locale.ENGLISH, "%d_%s", deviceId, Objects.requireNonNull(cameraName));
-    }
-
-    @Override
-    public void close() {
-        if (!mCameraRegistered) {
-            return;
-        }
-
-        mCameraOutput.closeStream();
-    }
-
-    private void registerCamera() {
-        if (mCameraRegistered) {
-            return;
-        }
-
-        mCameraRegistered = true;
-    }
-}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraInput.java b/core/java/android/companion/virtual/camera/VirtualCameraInput.java
deleted file mode 100644
index 690a64b..0000000
--- a/core/java/android/companion/virtual/camera/VirtualCameraInput.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.companion.virtual.camera;
-
-import android.annotation.NonNull;
-import android.hardware.camera2.params.InputConfiguration;
-
-import java.io.InputStream;
-
-/***
- *  Used for sending image data into virtual camera.
- *  <p>
- *  The system will call {@link  #openStream(InputConfiguration)} to signal when you
- *  should start sending Camera image data.
- *  When Camera is no longer needed, or there is change in configuration
- *  {@link #closeStream()} will be called. At that time finish sending current
- *  image data and then close the stream.
- *  <p>
- *  If Camera image data is needed again, {@link #openStream(InputConfiguration)} will be
- *  called by the system.
- *
- * @hide
- */
-public interface VirtualCameraInput {
-
-    /**
-     * Opens a new image stream for the provided {@link InputConfiguration}.
-     *
-     * @param inputConfiguration image data configuration.
-     * @return image data stream.
-     */
-    @NonNull
-    InputStream openStream(@NonNull InputConfiguration inputConfiguration);
-
-    /**
-     * Stop sending image data and close {@link InputStream} provided in {@link
-     * #openStream(InputConfiguration)}. Do nothing if there is currently no active stream.
-     */
-    void closeStream();
-}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraOutput.java b/core/java/android/companion/virtual/camera/VirtualCameraOutput.java
deleted file mode 100644
index fa1c3ad..0000000
--- a/core/java/android/companion/virtual/camera/VirtualCameraOutput.java
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.companion.virtual.camera;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.hardware.camera2.params.InputConfiguration;
-import android.os.ParcelFileDescriptor;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.io.FileDescriptor;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Objects;
-import java.util.concurrent.Executor;
-
-/**
- * Component for providing Camera data to the system.
- * <p>
- * {@link #getStreamDescriptor(InputConfiguration)} will be called by the system when Camera should
- * start sending image data. Camera data will continue to be sent into {@link ParcelFileDescriptor}
- * until {@link #closeStream()} is called by the system, at which point {@link ParcelFileDescriptor}
- * will be closed.
- *
- * @hide
- */
-@VisibleForTesting
-public class VirtualCameraOutput {
-
-    private static final String TAG = "VirtualCameraDeviceImpl";
-
-    @NonNull
-    private final VirtualCameraInput mVirtualCameraInput;
-    @NonNull
-    private final Executor mExecutor;
-    @Nullable
-    private VirtualCameraStream mCameraStream;
-
-    @VisibleForTesting
-    public VirtualCameraOutput(@NonNull VirtualCameraInput cameraInput,
-            @NonNull Executor executor) {
-        mVirtualCameraInput = Objects.requireNonNull(cameraInput);
-        mExecutor = Objects.requireNonNull(executor);
-    }
-
-    /**
-     * Get a read Descriptor on which Camera HAL will receive data. At any point in time there can
-     * exist a maximum of one active {@link ParcelFileDescriptor}.
-     * Calling this method with a different {@link InputConfiguration} is going to close the
-     * previously created file descriptor.
-     *
-     * @param imageConfiguration for which to create the {@link ParcelFileDescriptor}.
-     * @return Newly created ParcelFileDescriptor if stream param is different from previous or if
-     *         this is first time call. Will return null if there was an error during Descriptor
-     *         creation process.
-     */
-    @Nullable
-    @VisibleForTesting
-    public ParcelFileDescriptor getStreamDescriptor(
-            @NonNull InputConfiguration imageConfiguration) {
-        Objects.requireNonNull(imageConfiguration);
-
-        // Reuse same descriptor if stream is the same, otherwise create a new one.
-        try {
-            if (mCameraStream == null) {
-                mCameraStream = new VirtualCameraStream(imageConfiguration, mExecutor);
-            } else if (!mCameraStream.isSameConfiguration(imageConfiguration)) {
-                mCameraStream.close();
-                mCameraStream = new VirtualCameraStream(imageConfiguration, mExecutor);
-            }
-        } catch (IOException exception) {
-            Log.e(TAG, "Unable to open file descriptor.", exception);
-            return null;
-        }
-
-        InputStream imageStream = mVirtualCameraInput.openStream(imageConfiguration);
-        mCameraStream.startSending(imageStream);
-        return mCameraStream.getDescriptor();
-    }
-
-    /**
-     * Closes currently opened stream. If there is no stream, do nothing.
-     */
-    @VisibleForTesting
-    public void closeStream() {
-        mVirtualCameraInput.closeStream();
-        if (mCameraStream != null) {
-            mCameraStream.close();
-            mCameraStream = null;
-        }
-
-        try {
-            mVirtualCameraInput.closeStream();
-        } catch (Exception e) {
-            Log.e(TAG, "Error during closing stream.", e);
-        }
-    }
-
-    private static class VirtualCameraStream implements AutoCloseable {
-
-        private static final String TAG = "VirtualCameraStream";
-        private static final int BUFFER_SIZE = 1024;
-
-        private static final int SENDING_STATE_INITIAL = 0;
-        private static final int SENDING_STATE_IN_PROGRESS = 1;
-        private static final int SENDING_STATE_CLOSED = 2;
-
-        @NonNull
-        private final InputConfiguration mImageConfiguration;
-        @NonNull
-        private final Executor mExecutor;
-        @Nullable
-        private final ParcelFileDescriptor mReadDescriptor;
-        @Nullable
-        private final ParcelFileDescriptor mWriteDescriptor;
-        private int mSendingState;
-
-        VirtualCameraStream(@NonNull InputConfiguration imageConfiguration,
-                @NonNull Executor executor) throws IOException {
-            mSendingState = SENDING_STATE_INITIAL;
-            mImageConfiguration = Objects.requireNonNull(imageConfiguration);
-            mExecutor = Objects.requireNonNull(executor);
-            ParcelFileDescriptor[] parcels = ParcelFileDescriptor.createPipe();
-            mReadDescriptor = parcels[0];
-            mWriteDescriptor = parcels[1];
-        }
-
-        boolean isSameConfiguration(@NonNull InputConfiguration imageConfiguration) {
-            return mImageConfiguration == Objects.requireNonNull(imageConfiguration);
-        }
-
-        @Nullable
-        ParcelFileDescriptor getDescriptor() {
-            return mReadDescriptor;
-        }
-
-        public void startSending(@NonNull InputStream inputStream) {
-            Objects.requireNonNull(inputStream);
-
-            if (mSendingState != SENDING_STATE_INITIAL) {
-                return;
-            }
-
-            mSendingState = SENDING_STATE_IN_PROGRESS;
-            mExecutor.execute(() -> sendData(inputStream));
-        }
-
-        @Override
-        public void close() {
-            mSendingState = SENDING_STATE_CLOSED;
-            try {
-                mReadDescriptor.close();
-            } catch (IOException e) {
-                Log.e(TAG, "Unable to close read descriptor.", e);
-            }
-            try {
-                mWriteDescriptor.close();
-            } catch (IOException e) {
-                Log.e(TAG, "Unable to close write descriptor.", e);
-            }
-        }
-
-        private void sendData(@NonNull InputStream inputStream) {
-            Objects.requireNonNull(inputStream);
-
-            byte[] buffer = new byte[BUFFER_SIZE];
-            FileDescriptor fd = mWriteDescriptor.getFileDescriptor();
-            try (FileOutputStream outputStream = new FileOutputStream(fd)) {
-                while (mSendingState == SENDING_STATE_IN_PROGRESS) {
-                    int bytesRead = inputStream.read(buffer, 0, BUFFER_SIZE);
-                    if (bytesRead < 1) continue;
-
-                    outputStream.write(buffer, 0, bytesRead);
-                }
-            } catch (IOException e) {
-                Log.e(TAG, "Error while sending camera data.", e);
-            }
-        }
-    }
-}
diff --git a/core/java/android/credentials/CredentialDescription.java b/core/java/android/credentials/CredentialDescription.java
index a23d7e4..db71624 100644
--- a/core/java/android/credentials/CredentialDescription.java
+++ b/core/java/android/credentials/CredentialDescription.java
@@ -25,8 +25,10 @@
 import com.android.internal.util.Preconditions;
 
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 
 /**
  * Represents the type and contained data fields of a {@link Credential}.
@@ -42,10 +44,10 @@
     private final String mType;
 
     /**
-     * Flattened semicolon separated keys of JSON values to match with requests.
+     * Keys of elements to match with Credential requests.
      */
     @NonNull
-    private final String mFlattenedRequestString;
+    private final Set<String> mSupportedElementKeys;
 
     /**
      * The credential entries to be used in the UI.
@@ -57,8 +59,7 @@
      * Constructs a {@link CredentialDescription}.
      *
      * @param type the type of the credential returned.
-     * @param flattenedRequestString flattened semicolon separated keys of JSON values
-     *                              to match with requests.
+     * @param supportedElementKeys Keys of elements to match with Credential requests.
      * @param credentialEntries a list of {@link CredentialEntry}s that are to be shown on the
      *                          account selector if a credential matches with this description.
      *                          Each entry contains information to be displayed within an
@@ -68,10 +69,10 @@
      * @throws IllegalArgumentException If type is empty.
      */
     public CredentialDescription(@NonNull String type,
-            @NonNull String flattenedRequestString,
+            @NonNull Set<String> supportedElementKeys,
             @NonNull List<CredentialEntry> credentialEntries) {
         mType = Preconditions.checkStringNotEmpty(type, "type must not be empty");
-        mFlattenedRequestString = Preconditions.checkStringNotEmpty(flattenedRequestString);
+        mSupportedElementKeys = Objects.requireNonNull(supportedElementKeys);
         mCredentialEntries = Objects.requireNonNull(credentialEntries);
         Preconditions.checkArgument(credentialEntries.size()
                         <= MAX_ALLOWED_ENTRIES_PER_DESCRIPTION,
@@ -82,15 +83,15 @@
 
     private CredentialDescription(@NonNull Parcel in) {
         String type = in.readString8();
-        String flattenedRequestString = in.readString();
+        List<String> descriptions = in.createStringArrayList();
         List<CredentialEntry> entries = new ArrayList<>();
         in.readTypedList(entries, CredentialEntry.CREATOR);
 
         mType = type;
         AnnotationValidations.validate(android.annotation.NonNull.class, null, mType);
-        mFlattenedRequestString = flattenedRequestString;
+        mSupportedElementKeys = new HashSet<>(descriptions);
         AnnotationValidations.validate(android.annotation.NonNull.class, null,
-                mFlattenedRequestString);
+                mSupportedElementKeys);
         mCredentialEntries = entries;
         AnnotationValidations.validate(android.annotation.NonNull.class, null,
                 mCredentialEntries);
@@ -125,7 +126,7 @@
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeString8(mType);
-        dest.writeString(mFlattenedRequestString);
+        dest.writeStringList(mSupportedElementKeys.stream().toList());
         dest.writeTypedList(mCredentialEntries, flags);
     }
 
@@ -141,8 +142,8 @@
      * Returns the flattened JSON string that will be matched with requests.
      */
     @NonNull
-    public String getFlattenedRequestString() {
-        return mFlattenedRequestString;
+    public Set<String> getSupportedElementKeys() {
+        return new HashSet<>(mSupportedElementKeys);
     }
 
     /**
@@ -155,18 +156,18 @@
 
     /**
      * {@link CredentialDescription#mType} and
-     * {@link CredentialDescription#mFlattenedRequestString} are enough for hashing. Constructor
+     * {@link CredentialDescription#mSupportedElementKeys} are enough for hashing. Constructor
      * enforces {@link CredentialEntry} to have the same type and
      * {@link android.app.slice.Slice} contained by the entry can not be hashed.
      */
     @Override
     public int hashCode() {
-        return Objects.hash(mType, mFlattenedRequestString);
+        return Objects.hash(mType, mSupportedElementKeys);
     }
 
     /**
      * {@link CredentialDescription#mType} and
-     * {@link CredentialDescription#mFlattenedRequestString} are enough for equality check.
+     * {@link CredentialDescription#mSupportedElementKeys} are enough for equality check.
      */
     @Override
     public boolean equals(Object obj) {
@@ -175,6 +176,6 @@
         }
         CredentialDescription other = (CredentialDescription) obj;
         return mType.equals(other.mType)
-                && mFlattenedRequestString.equals(other.mFlattenedRequestString);
+                && mSupportedElementKeys.equals(other.mSupportedElementKeys);
     }
 }
diff --git a/core/java/android/credentials/CredentialOption.java b/core/java/android/credentials/CredentialOption.java
index da6656a..e933123 100644
--- a/core/java/android/credentials/CredentialOption.java
+++ b/core/java/android/credentials/CredentialOption.java
@@ -43,12 +43,12 @@
 public final class CredentialOption implements Parcelable {
 
     /**
-     * Bundle key to the flattened version of the JSON request string. Framework will use this key
+     * Bundle key to the list of elements keys supported/requested. Framework will use this key
      * to determine which types of Credentials will utilize Credential Registry when filtering
      * Credential Providers to ping.
      */
-    public static final String FLATTENED_REQUEST = "android.credentials"
-            + ".GetCredentialOption.FLATTENED_REQUEST_STRING";
+    public static final String SUPPORTED_ELEMENT_KEYS = "android.credentials"
+            + ".GetCredentialOption.SUPPORTED_ELEMENT_KEYS";
 
     /**
      * The requested credential type.
diff --git a/core/java/android/hardware/DataSpace.java b/core/java/android/hardware/DataSpace.java
index b8b1eaa..312bfdf 100644
--- a/core/java/android/hardware/DataSpace.java
+++ b/core/java/android/hardware/DataSpace.java
@@ -16,6 +16,7 @@
 package android.hardware;
 
 import android.annotation.IntDef;
+import android.view.SurfaceControl;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -376,12 +377,19 @@
      */
     public static final int RANGE_LIMITED = 2 << 27;
     /**
-     * Extended range is used for scRGB only.
+     * Extended range can be used in combination with FP16 to communicate scRGB or with
+     * {@link android.view.SurfaceControl.Transaction#setExtendedRangeBrightness(SurfaceControl, float, float)}
+     * to indicate an HDR range.
      *
-     * <p>Intended for use with floating point pixel formats. [0.0 - 1.0] is the standard
-     * sRGB space. Values outside the range [0.0 - 1.0] can encode
-     * color outside the sRGB gamut. [-0.5, 7.5] is the scRGB range.
+     * <p>When used with floating point pixel formats and #STANDARD_BT709 then [0.0 - 1.0] is the
+     * standard sRGB space and values outside the range [0.0 - 1.0] can encode
+     * color outside the sRGB gamut. [-0.5, 7.5] is the standard scRGB range.
      * Used to blend/merge multiple dataspaces on a single display.</p>
+     *
+     * <p>As of {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} this may be combined with
+     * {@link android.view.SurfaceControl.Transaction#setExtendedRangeBrightness(SurfaceControl, float, float)}
+     * and other formats such as {@link HardwareBuffer#RGBA_8888} or
+     * {@link HardwareBuffer#RGBA_1010102} to communicate a variable HDR brightness range</p>
      */
     public static final int RANGE_EXTENDED = 3 << 27;
 
diff --git a/core/java/android/hardware/input/InputDeviceLightsManager.java b/core/java/android/hardware/input/InputDeviceLightsManager.java
index f4ee9a2..e2568e3 100644
--- a/core/java/android/hardware/input/InputDeviceLightsManager.java
+++ b/core/java/android/hardware/input/InputDeviceLightsManager.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.app.ActivityThread;
-import android.content.Context;
 import android.hardware.lights.Light;
 import android.hardware.lights.LightState;
 import android.hardware.lights.LightsManager;
@@ -44,8 +43,7 @@
     // Package name
     private final String mPackageName;
 
-    InputDeviceLightsManager(Context context, int deviceId) {
-        super(context);
+    InputDeviceLightsManager(int deviceId) {
         mGlobal = InputManagerGlobal.getInstance();
         mDeviceId = deviceId;
         mPackageName = ActivityThread.currentPackageName();
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 8a4a0e4..e7385b6 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -32,8 +32,6 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.hardware.BatteryState;
-import android.hardware.SensorManager;
-import android.hardware.lights.LightsManager;
 import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
@@ -41,7 +39,6 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.Vibrator;
-import android.os.VibratorManager;
 import android.util.Log;
 import android.view.Display;
 import android.view.InputDevice;
@@ -1311,47 +1308,6 @@
     }
 
     /**
-     * Gets a vibrator manager service associated with an input device, always creates a new
-     * instance.
-     * @return The vibrator manager, never null.
-     * @hide
-     */
-    @NonNull
-    public VibratorManager getInputDeviceVibratorManager(int deviceId) {
-        return new InputDeviceVibratorManager(deviceId);
-    }
-
-    /**
-     * Gets a sensor manager service associated with an input device, always creates a new instance.
-     * @return The sensor manager, never null.
-     * @hide
-     */
-    @NonNull
-    public SensorManager getInputDeviceSensorManager(int deviceId) {
-        return mGlobal.getInputDeviceSensorManager(deviceId);
-    }
-
-    /**
-     * Gets a battery state object associated with an input device, assuming it has one.
-     * @return The battery, never null.
-     * @hide
-     */
-    @NonNull
-    public BatteryState getInputDeviceBatteryState(int deviceId, boolean hasBattery) {
-        return mGlobal.getInputDeviceBatteryState(deviceId, hasBattery);
-    }
-
-    /**
-     * Gets a lights manager associated with an input device, always creates a new instance.
-     * @return The lights manager, never null.
-     * @hide
-     */
-    @NonNull
-    public LightsManager getInputDeviceLightsManager(int deviceId) {
-        return new InputDeviceLightsManager(getContext(), deviceId);
-    }
-
-    /**
      * Cancel all ongoing pointer gestures on all displays.
      * @hide
      */
diff --git a/core/java/android/hardware/input/InputManagerGlobal.java b/core/java/android/hardware/input/InputManagerGlobal.java
index 31ce7d9..701980d 100644
--- a/core/java/android/hardware/input/InputManagerGlobal.java
+++ b/core/java/android/hardware/input/InputManagerGlobal.java
@@ -29,6 +29,7 @@
 import android.hardware.input.InputManager.OnTabletModeChangedListener;
 import android.hardware.lights.Light;
 import android.hardware.lights.LightState;
+import android.hardware.lights.LightsManager;
 import android.hardware.lights.LightsRequest;
 import android.os.Binder;
 import android.os.CombinedVibration;
@@ -876,7 +877,7 @@
     }
 
     /**
-     * @see InputManager#getInputDeviceSensorManager(int)
+     * @see InputDevice#getSensorManager()
      */
     @NonNull
     public SensorManager getInputDeviceSensorManager(int deviceId) {
@@ -955,6 +956,14 @@
     }
 
     /**
+     * @see InputDevice#getLightsManager()
+     */
+    @NonNull
+    public LightsManager getInputDeviceLightsManager(int deviceId) {
+        return new InputDeviceLightsManager(deviceId);
+    }
+
+    /**
      * Gets a list of light objects associated with an input device.
      * @return The list of lights, never null.
      */
@@ -1032,7 +1041,7 @@
     }
 
     /**
-     * @see InputManager#getInputDeviceVibratorManager(int)
+     * @see InputDevice#getVibratorManager()
      */
     @NonNull
     public VibratorManager getInputDeviceVibratorManager(int deviceId) {
@@ -1138,7 +1147,7 @@
     }
 
     /**
-     * @see InputManager#getKeyCodeforKeyLocation(int, int)
+     * @see InputManager#getKeyCodeForKeyLocation(int, int)
      */
     public int getKeyCodeForKeyLocation(int deviceId, int locationKeyCode) {
         try {
diff --git a/core/java/android/hardware/lights/LightsManager.java b/core/java/android/hardware/lights/LightsManager.java
index 2d9bc0e..b71b7e0 100644
--- a/core/java/android/hardware/lights/LightsManager.java
+++ b/core/java/android/hardware/lights/LightsManager.java
@@ -25,8 +25,6 @@
 import android.os.Binder;
 import android.os.IBinder;
 
-import com.android.internal.util.Preconditions;
-
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.List;
@@ -39,7 +37,6 @@
 public abstract class LightsManager {
     private static final String TAG = "LightsManager";
 
-    @NonNull private final Context mContext;
     // These enum values copy the values from {@link com.android.server.lights.LightsManager}
     // and the light HAL. Since 0-7 are lights reserved for system use, only the microphone light
     // and following types are available through this API.
@@ -62,9 +59,7 @@
     /**
      * @hide to prevent subclassing from outside of the framework
      */
-    public LightsManager(Context context) {
-        mContext = Preconditions.checkNotNull(context);
-    }
+    public LightsManager() {}
 
     /**
      * Returns the lights available on the device.
diff --git a/core/java/android/hardware/lights/SystemLightsManager.java b/core/java/android/hardware/lights/SystemLightsManager.java
index 055a7f4..3beb4ba 100644
--- a/core/java/android/hardware/lights/SystemLightsManager.java
+++ b/core/java/android/hardware/lights/SystemLightsManager.java
@@ -59,7 +59,6 @@
      */
     @VisibleForTesting
     public SystemLightsManager(@NonNull Context context, @NonNull ILightsManager service) {
-        super(context);
         mService = Preconditions.checkNotNull(service);
     }
 
diff --git a/core/java/android/hardware/usb/UsbDeviceConnection.java b/core/java/android/hardware/usb/UsbDeviceConnection.java
index 7c2e518..44144d9 100644
--- a/core/java/android/hardware/usb/UsbDeviceConnection.java
+++ b/core/java/android/hardware/usb/UsbDeviceConnection.java
@@ -238,7 +238,7 @@
      * or negative value for failure
      */
     public int controlTransfer(int requestType, int request, int value,
-            int index, byte[] buffer, int length, int timeout) {
+            int index, @Nullable byte[] buffer, int length, int timeout) {
         return controlTransfer(requestType, request, value, index, buffer, 0, length, timeout);
     }
 
@@ -263,7 +263,7 @@
      * or negative value for failure
      */
     public int controlTransfer(int requestType, int request, int value, int index,
-            byte[] buffer, int offset, int length, int timeout) {
+            @Nullable byte[] buffer, int offset, int length, int timeout) {
         checkBounds(buffer, offset, length);
         return native_control_request(requestType, request, value, index,
                 buffer, offset, length, timeout);
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 5137bc1..7ae280f 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7414,6 +7414,8 @@
          *
          * @hide
          */
+        @TestApi
+        @Readable
         @SuppressLint("NoSettingsProvider")
         public static final String STYLUS_BUTTONS_ENABLED = "stylus_buttons_enabled";
 
@@ -11728,13 +11730,14 @@
         public static final String THEATER_MODE_ON = "theater_mode_on";
 
         /**
-         * Constant for use in AIRPLANE_MODE_RADIOS to specify Bluetooth radio.
+         * Constant for use in AIRPLANE_MODE_RADIOS or SATELLITE_MODE_RADIOS to specify Bluetooth
+         * radio.
          */
         @Readable
         public static final String RADIO_BLUETOOTH = "bluetooth";
 
         /**
-         * Constant for use in AIRPLANE_MODE_RADIOS to specify Wi-Fi radio.
+         * Constant for use in AIRPLANE_MODE_RADIOS or SATELLITE_MODE_RADIOS to specify Wi-Fi radio.
          */
         @Readable
         public static final String RADIO_WIFI = "wifi";
@@ -11751,12 +11754,40 @@
         public static final String RADIO_CELL = "cell";
 
         /**
-         * Constant for use in AIRPLANE_MODE_RADIOS to specify NFC radio.
+         * Constant for use in AIRPLANE_MODE_RADIOS or SATELLITE_MODE_RADIOS to specify NFC radio.
          */
         @Readable
         public static final String RADIO_NFC = "nfc";
 
         /**
+         * Constant for use in SATELLITE_MODE_RADIOS to specify UWB radio.
+         *
+         * {@hide}
+         */
+        public static final String RADIO_UWB = "uwb";
+
+
+        /**
+         * A comma separated list of radios that need to be disabled when satellite mode is on.
+         *
+         * {@hide}
+         */
+        public static final String SATELLITE_MODE_RADIOS = "satellite_mode_radios";
+
+        /**
+         * The satellite mode is enabled for the user. When the satellite mode is enabled, the
+         * satellite radio will be turned on and all other radios will be turned off. When the
+         * satellite mode is disabled, the satellite radio will be turned off and the states of
+         * other radios will be restored.
+         * <p>
+         * When this setting is set to 0, it means the satellite mode is disabled. When this
+         * setting is set to 1, it means the satellite mode is enabled.
+         *
+         * {@hide}
+         */
+        public static final String SATELLITE_MODE_ENABLED = "satellite_mode_enabled";
+
+        /**
          * A comma separated list of radios that need to be disabled when airplane mode
          * is on. This overrides WIFI_ON and BLUETOOTH_ON, if Wi-Fi and bluetooth are
          * included in the comma separated list.
diff --git a/core/java/android/service/credentials/CredentialProviderInfoFactory.java b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
index 47b75d1..9120b8a 100644
--- a/core/java/android/service/credentials/CredentialProviderInfoFactory.java
+++ b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
@@ -224,14 +224,6 @@
             Log.e(TAG, "Failed to get XML metadata", e);
         }
 
-        // 5. Extract the legacy metadata.
-        try {
-            builder.addCapabilities(
-                    populateLegacyProviderCapabilities(resources, metadata, serviceInfo));
-        } catch (Exception e) {
-            Log.e(TAG, "Failed to get legacy metadata ", e);
-        }
-
         return builder;
     }
 
@@ -325,38 +317,6 @@
         return capabilities;
     }
 
-    private static Set<String> populateLegacyProviderCapabilities(
-            Resources resources, Bundle metadata, ServiceInfo serviceInfo) {
-        Set<String> output = new HashSet<>();
-        Set<String> capabilities = new HashSet<>();
-
-        try {
-            String[] discovered =
-                    resources.getStringArray(
-                            metadata.getInt(CredentialProviderService.CAPABILITY_META_DATA_KEY));
-            if (discovered != null) {
-                capabilities.addAll(Arrays.asList(discovered));
-            }
-        } catch (Resources.NotFoundException | NullPointerException e) {
-            Log.e(TAG, "Failed to get capabilities: ", e);
-        }
-
-        if (capabilities.size() == 0) {
-            Log.e(TAG, "No capabilities found for provider:" + serviceInfo);
-            return output;
-        }
-
-        for (String capability : capabilities) {
-            if (capability == null || capability.isEmpty()) {
-                Log.w(TAG, "Skipping empty/null capability");
-                continue;
-            }
-            Log.i(TAG, "Capabilities found for provider: " + capability);
-            output.add(capability);
-        }
-        return output;
-    }
-
     private static ServiceInfo getServiceInfoOrThrow(
             @NonNull ComponentName serviceComponent, int userId)
             throws PackageManager.NameNotFoundException {
diff --git a/core/java/android/service/credentials/CredentialProviderService.java b/core/java/android/service/credentials/CredentialProviderService.java
index 6824159..b977606 100644
--- a/core/java/android/service/credentials/CredentialProviderService.java
+++ b/core/java/android/service/credentials/CredentialProviderService.java
@@ -156,14 +156,6 @@
 
     private static final String TAG = "CredProviderService";
 
-    /**
-     * The list of capabilities exposed by a credential provider.
-     *
-     * @deprecated Replaced with {@link android.service.credentials#SERVICE_META_DATA}
-     */
-    @Deprecated
-    public static final String CAPABILITY_META_DATA_KEY = "android.credentials.capabilities";
-
      /**
       * Name under which a Credential Provider service component publishes information
       * about itself.  This meta-data must reference an XML resource containing
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index e81aecb..48fb719 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -28,7 +28,6 @@
 import android.hardware.SensorManager;
 import android.hardware.input.HostUsiVersion;
 import android.hardware.input.InputDeviceIdentifier;
-import android.hardware.input.InputManager;
 import android.hardware.input.InputManagerGlobal;
 import android.hardware.lights.LightsManager;
 import android.icu.util.ULocale;
@@ -1191,7 +1190,8 @@
     public LightsManager getLightsManager() {
         synchronized (mMotionRanges) {
             if (mLightsManager == null) {
-                mLightsManager = InputManager.getInstance().getInputDeviceLightsManager(mId);
+                mLightsManager = InputManagerGlobal.getInstance()
+                        .getInputDeviceLightsManager(mId);
             }
         }
         return mLightsManager;
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index 16bc155..bd249c4 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -63,37 +63,6 @@
  */
 public class InsetsState implements Parcelable {
 
-    /**
-     * Internal representation of inset source types. This is different from the public API in
-     * {@link WindowInsets.Type} as one type from the public API might indicate multiple windows
-     * at the same time.
-     */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(prefix = "ITYPE", value = {
-            ITYPE_CAPTION_BAR,
-            ITYPE_LEFT_TAPPABLE_ELEMENT,
-            ITYPE_TOP_TAPPABLE_ELEMENT,
-            ITYPE_RIGHT_TAPPABLE_ELEMENT,
-            ITYPE_BOTTOM_TAPPABLE_ELEMENT,
-            ITYPE_LEFT_GENERIC_OVERLAY,
-            ITYPE_TOP_GENERIC_OVERLAY,
-            ITYPE_RIGHT_GENERIC_OVERLAY,
-            ITYPE_BOTTOM_GENERIC_OVERLAY
-    })
-    public @interface InternalInsetsType {}
-
-    public static final int ITYPE_CAPTION_BAR = 0;
-
-    public static final int ITYPE_LEFT_TAPPABLE_ELEMENT = 1;
-    public static final int ITYPE_TOP_TAPPABLE_ELEMENT = 2;
-    public static final int ITYPE_RIGHT_TAPPABLE_ELEMENT = 3;
-    public static final int ITYPE_BOTTOM_TAPPABLE_ELEMENT = 4;
-
-    public static final int ITYPE_LEFT_GENERIC_OVERLAY = 5;
-    public static final int ITYPE_TOP_GENERIC_OVERLAY = 6;
-    public static final int ITYPE_RIGHT_GENERIC_OVERLAY = 7;
-    public static final int ITYPE_BOTTOM_GENERIC_OVERLAY = 8;
-
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = "ISIDE", value = {
             ISIDE_LEFT,
@@ -677,30 +646,6 @@
                 && !WindowConfiguration.inMultiWindowMode(windowingMode);
     }
 
-    /**
-     * Converting a internal type to the public type.
-     * @param type internal insets type, {@code InternalInsetsType}.
-     * @return public insets type, {@code Type.InsetsType}.
-     */
-    public static @Type.InsetsType int toPublicType(@InternalInsetsType int type) {
-        switch (type) {
-            case ITYPE_LEFT_GENERIC_OVERLAY:
-            case ITYPE_TOP_GENERIC_OVERLAY:
-            case ITYPE_RIGHT_GENERIC_OVERLAY:
-            case ITYPE_BOTTOM_GENERIC_OVERLAY:
-                return Type.SYSTEM_OVERLAYS;
-            case ITYPE_CAPTION_BAR:
-                return Type.CAPTION_BAR;
-            case ITYPE_LEFT_TAPPABLE_ELEMENT:
-            case ITYPE_TOP_TAPPABLE_ELEMENT:
-            case ITYPE_RIGHT_TAPPABLE_ELEMENT:
-            case ITYPE_BOTTOM_TAPPABLE_ELEMENT:
-                return Type.TAPPABLE_ELEMENT;
-            default:
-                throw new IllegalArgumentException("Unknown type: " + type);
-        }
-    }
-
     public void dump(String prefix, PrintWriter pw) {
         final String newPrefix = prefix + "  ";
         pw.println(prefix + "InsetsState");
diff --git a/core/java/android/view/WindowMetrics.java b/core/java/android/view/WindowMetrics.java
index b74b80e..7ad43c7 100644
--- a/core/java/android/view/WindowMetrics.java
+++ b/core/java/android/view/WindowMetrics.java
@@ -162,7 +162,7 @@
         return WindowMetrics.class.getSimpleName() + ":{"
                 + "bounds=" + mBounds
                 + ", windowInsets=" + mWindowInsets
-                + ", density" + mDensity
+                + ", density=" + mDensity
                 + "}";
     }
 }
diff --git a/core/java/android/view/autofill/AutofillFeatureFlags.java b/core/java/android/view/autofill/AutofillFeatureFlags.java
index e267a7f..e51eff4 100644
--- a/core/java/android/view/autofill/AutofillFeatureFlags.java
+++ b/core/java/android/view/autofill/AutofillFeatureFlags.java
@@ -150,6 +150,15 @@
             "package_deny_list_for_unimportant_view";
 
     /**
+     * Sets the list of activities and packages allowed for autofill. The format is same with
+     * {@link #DEVICE_CONFIG_PACKAGE_DENYLIST_FOR_UNIMPORTANT_VIEW}
+     *
+     * @hide
+     */
+    public static final String DEVICE_CONFIG_PACKAGE_AND_ACTIVITY_ALLOWLIST_FOR_TRIGGERING_FILL_REQUEST =
+            "package_and_activity_allowlist_for_triggering_fill_request";
+
+    /**
      * Whether the heuristics check for view is enabled
      */
     public static final String DEVICE_CONFIG_TRIGGER_FILL_REQUEST_ON_UNIMPORTANT_VIEW =
@@ -183,6 +192,7 @@
      */
     public static final String DEVICE_CONFIG_SHOULD_ENABLE_AUTOFILL_ON_ALL_VIEW_TYPES =
             "should_enable_autofill_on_all_view_types";
+
     // END AUTOFILL FOR ALL APPS FLAGS //
 
 
@@ -378,6 +388,16 @@
             DEVICE_CONFIG_PACKAGE_DENYLIST_FOR_UNIMPORTANT_VIEW, "");
     }
 
+    /**
+     * Get autofill allowlist from flag
+     *
+     * @hide
+     */
+    public static String getAllowlistStringFromFlag() {
+        return DeviceConfig.getString(
+            DeviceConfig.NAMESPACE_AUTOFILL,
+            DEVICE_CONFIG_PACKAGE_AND_ACTIVITY_ALLOWLIST_FOR_TRIGGERING_FILL_REQUEST, "");
+    }
 
     // START AUTOFILL PCC CLASSIFICATION FUNCTIONS
 
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index c5befb6..cc8ab10 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -694,7 +694,18 @@
     private boolean mIsPackagePartiallyDeniedForAutofill = false;
 
     // A deny set read from device config
-    private Set<String> mDeniedActivitiySet = new ArraySet<>();
+    private Set<String> mDeniedActivitySet = new ArraySet<>();
+
+    // If a package is fully allowed, all views in package will skip the heuristic check
+    private boolean mIsPackageFullyAllowedForAutofill = false;
+
+    // If a package is partially denied, autofill manager will check whether
+    // current activity is in allowed activity set. If it's allowed activity, then autofill manager
+    // will skip the heuristic check
+    private boolean mIsPackagePartiallyAllowedForAutofill = false;
+
+    // An allowed activity set read from device config
+    private Set<String> mAllowedActivitySet = new ArraySet<>();
 
     // Indicates whether called the showAutofillDialog() method.
     private boolean mShowAutofillDialogCalled = false;
@@ -873,19 +884,34 @@
             AutofillFeatureFlags.getNonAutofillableImeActionIdSetFromFlag();
 
         final String denyListString = AutofillFeatureFlags.getDenylistStringFromFlag();
+        final String allowlistString = AutofillFeatureFlags.getAllowlistStringFromFlag();
 
         final String packageName = mContext.getPackageName();
 
         mIsPackageFullyDeniedForAutofill =
-            isPackageFullyDeniedForAutofill(denyListString, packageName);
+            isPackageFullyAllowedOrDeniedForAutofill(denyListString, packageName);
+
+        mIsPackageFullyAllowedForAutofill =
+            isPackageFullyAllowedOrDeniedForAutofill(allowlistString, packageName);
 
         if (!mIsPackageFullyDeniedForAutofill) {
             mIsPackagePartiallyDeniedForAutofill =
-                isPackagePartiallyDeniedForAutofill(denyListString, packageName);
+                isPackagePartiallyDeniedOrAllowedForAutofill(denyListString, packageName);
+        }
+
+        if (!mIsPackageFullyAllowedForAutofill) {
+            mIsPackagePartiallyAllowedForAutofill =
+                isPackagePartiallyDeniedOrAllowedForAutofill(allowlistString, packageName);
         }
 
         if (mIsPackagePartiallyDeniedForAutofill) {
-            setDeniedActivitySetWithDenyList(denyListString, packageName);
+            mDeniedActivitySet = getDeniedOrAllowedActivitySetFromString(
+                    denyListString, packageName);
+        }
+
+        if (mIsPackagePartiallyAllowedForAutofill) {
+            mAllowedActivitySet = getDeniedOrAllowedActivitySetFromString(
+                    allowlistString, packageName);
         }
     }
 
@@ -921,59 +947,59 @@
         return true;
     }
 
-    private boolean isPackageFullyDeniedForAutofill(
-            @NonNull String denyListString, @NonNull String packageName) {
-        // If "PackageName:;" is in the string, then it means the package name is in denylist
-        // and there are no activities specified under it. That means the package is fully
-        // denied for autofill
-        return denyListString.indexOf(packageName + ":;") != -1;
+    private boolean isPackageFullyAllowedOrDeniedForAutofill(
+            @NonNull String listString, @NonNull String packageName) {
+        // If "PackageName:;" is in the string, then it the package is fully denied or allowed for
+        // autofill, depending on which string is passed to this function
+        return listString.indexOf(packageName + ":;") != -1;
     }
 
-    private boolean isPackagePartiallyDeniedForAutofill(
-            @NonNull String denyListString, @NonNull String packageName) {
-        // This check happens after checking package is not fully denied. If "PackageName:" instead
-        // is in denylist, then it means there are specific activities to be denied. So the package
-        // is partially denied for autofill
-        return denyListString.indexOf(packageName + ":") != -1;
+    private boolean isPackagePartiallyDeniedOrAllowedForAutofill(
+            @NonNull String listString, @NonNull String packageName) {
+        // If "PackageName:" is in string when "PackageName:;" is not, then it means there are
+        // specific activities to be allowed or denied. So the package is partially allowed or
+        // denied for autofill.
+        return listString.indexOf(packageName + ":") != -1;
     }
 
     /**
-     * Get the denied activitiy names under specified package from denylist and set it in field
-     * mDeniedActivitiySet
+     * Get the denied or allowed activitiy names under specified package from the list string and
+     * set it in fields accordingly
      *
-     * If using parameter as the example below, the denied activity set would be set to
-     * Set{Activity1,Activity2}.
+     * For example, if the package name is Package1, and the string is
+     * "Package1:Activity1,Activity2;", then the extracted activity set would be
+     * {Activity1, Activity2}
      *
-     * @param denyListString Denylist that is got from device config. For example,
+     * @param listString Denylist that is got from device config. For example,
      *        "Package1:Activity1,Activity2;Package2:;"
-     * @param packageName Specify to extract activities under which package.For example,
-     *        "Package1:;"
+     * @param packageName Specify which package to extract.For example, "Package1"
+     *
+     * @return the extracted activity set, For example, {Activity1, Activity2}
      */
-    private void setDeniedActivitySetWithDenyList(
-            @NonNull String denyListString, @NonNull String packageName) {
+    private Set<String> getDeniedOrAllowedActivitySetFromString(
+            @NonNull String listString, @NonNull String packageName) {
         // 1. Get the index of where the Package name starts
-        final int packageInStringIndex = denyListString.indexOf(packageName + ":");
+        final int packageInStringIndex = listString.indexOf(packageName + ":");
 
         // 2. Get the ";" index after this index of package
-        final int firstNextSemicolonIndex = denyListString.indexOf(";", packageInStringIndex);
+        final int firstNextSemicolonIndex = listString.indexOf(";", packageInStringIndex);
 
         // 3. Get the activity names substring between the indexes
         final int activityStringStartIndex = packageInStringIndex + packageName.length() + 1;
+
         if (activityStringStartIndex >= firstNextSemicolonIndex) {
-            Log.e(TAG, "Failed to get denied activity names from denylist because it's wrongly "
+            Log.e(TAG, "Failed to get denied activity names from list because it's wrongly "
                     + "formatted");
-            return;
+            return new ArraySet<>();
         }
         final String activitySubstring =
-                denyListString.substring(activityStringStartIndex, firstNextSemicolonIndex);
+                listString.substring(activityStringStartIndex, firstNextSemicolonIndex);
 
         // 4. Split the activity name substring
         final String[] activityStringArray = activitySubstring.split(",");
 
-        // 5. Set the denied activity set
-        mDeniedActivitiySet = new ArraySet<>(Arrays.asList(activityStringArray));
-
-        return;
+        // 5. return the extracted activities in a set
+        return new ArraySet<>(Arrays.asList(activityStringArray));
     }
 
     /**
@@ -992,7 +1018,32 @@
                 return false;
             }
             final ComponentName clientActivity = client.autofillClientGetComponentName();
-            if (mDeniedActivitiySet.contains(clientActivity.flattenToShortString())) {
+            if (mDeniedActivitySet.contains(clientActivity.flattenToShortString())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Check whether current activity is allowlisted for autofill.
+     *
+     * If it is, the view in current activity will bypass heuristic check when checking whether it's
+     * autofillable
+     *
+     * @hide
+     */
+    public boolean isActivityAllowedForAutofill() {
+        if (mIsPackageFullyAllowedForAutofill) {
+            return true;
+        }
+        if (mIsPackagePartiallyAllowedForAutofill) {
+            final AutofillClient client = getClient();
+            if (client == null) {
+                return false;
+            }
+            final ComponentName clientActivity = client.autofillClientGetComponentName();
+            if (mAllowedActivitySet.contains(clientActivity.flattenToShortString())) {
                 return true;
             }
         }
@@ -1009,17 +1060,22 @@
      * @hide
      */
     public boolean isAutofillable(View view) {
-        if (isActivityDeniedForAutofill()) {
-            Log.d(TAG, "view is not autofillable - activity denied for autofill");
-            return false;
-        }
-
         // Duplicate the autofill type check here because ViewGroup will call this function to
         // decide whether to include view in assist structure.
         // Also keep the autofill type check inside View#IsAutofillable() to serve as an early out
         // or if other functions need to call it.
         if (view.getAutofillType() == View.AUTOFILL_TYPE_NONE) return false;
 
+        if (isActivityDeniedForAutofill()) {
+            Log.d(TAG, "view is not autofillable - activity denied for autofill");
+            return false;
+        }
+
+        if (isActivityAllowedForAutofill()) {
+            Log.d(TAG, "view is autofillable - activity allowed for autofill");
+            return true;
+        }
+
         if (view instanceof EditText) {
             return isPassingImeActionCheck((EditText) view);
         }
@@ -1037,7 +1093,7 @@
             || view instanceof RadioGroup) {
             return true;
         }
-
+        Log.d(TAG, "view is not autofillable - not important and filtered by view type check");
         return false;
     }
 
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 0bbaac0f..6523fff 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -2000,8 +2000,19 @@
      * in order to facilitate debugging of web layouts and JavaScript
      * code running inside WebViews. Please refer to WebView documentation
      * for the debugging guide.
-     *
-     * The default is {@code false}.
+     * <p>
+     * In WebView 113.0.5656.0 and later, this is enabled automatically if the
+     * app is declared as
+     * <a href="https://developer.android.com/guide/topics/manifest/application-element#debug">
+     * {@code android:debuggable="true"}</a> in its manifest; otherwise, the
+     * default is {@code false}.
+     * <p>
+     * Enabling web contents debugging allows the state of any WebView in the
+     * app to be inspected and modified by the user via adb. This is a security
+     * liability and should not be enabled in production builds of apps unless
+     * this is an explicitly intended use of the app. More info on
+     * <a href="https://developer.android.com/topic/security/risks/android-debuggable">
+     * secure debug settings</a>.
      *
      * @param enabled whether to enable web contents debugging
      */
diff --git a/core/java/android/window/BackNavigationInfo.java b/core/java/android/window/BackNavigationInfo.java
index 5140594..e0ee683 100644
--- a/core/java/android/window/BackNavigationInfo.java
+++ b/core/java/android/window/BackNavigationInfo.java
@@ -16,6 +16,8 @@
 
 package android.window;
 
+import android.annotation.AnimRes;
+import android.annotation.ColorInt;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -245,6 +247,9 @@
     public static final class CustomAnimationInfo implements Parcelable {
         private final String mPackageName;
         private int mWindowAnimations;
+        @AnimRes private int mCustomExitAnim;
+        @AnimRes private int mCustomEnterAnim;
+        @ColorInt private int mCustomBackground;
 
         /**
          * The package name of the windowAnimations.
@@ -261,6 +266,27 @@
             return mWindowAnimations;
         }
 
+        /**
+         * The exit animation resource Id of customize activity transition.
+         */
+        public int getCustomExitAnim() {
+            return mCustomExitAnim;
+        }
+
+        /**
+         * The entering animation resource Id of customize activity transition.
+         */
+        public int getCustomEnterAnim() {
+            return mCustomEnterAnim;
+        }
+
+        /**
+         * The background color of customize activity transition.
+         */
+        public int getCustomBackground() {
+            return mCustomBackground;
+        }
+
         public CustomAnimationInfo(@NonNull String packageName) {
             this.mPackageName = packageName;
         }
@@ -274,11 +300,17 @@
         public void writeToParcel(@NonNull Parcel dest, int flags) {
             dest.writeString8(mPackageName);
             dest.writeInt(mWindowAnimations);
+            dest.writeInt(mCustomEnterAnim);
+            dest.writeInt(mCustomExitAnim);
+            dest.writeInt(mCustomBackground);
         }
 
         private CustomAnimationInfo(@NonNull Parcel in) {
             mPackageName = in.readString8();
             mWindowAnimations = in.readInt();
+            mCustomEnterAnim = in.readInt();
+            mCustomExitAnim = in.readInt();
+            mCustomBackground = in.readInt();
         }
 
         @Override
@@ -349,10 +381,25 @@
          * Set windowAnimations for customize animation.
          */
         public Builder setWindowAnimations(String packageName, int windowAnimations) {
-            mCustomAnimationInfo = new CustomAnimationInfo(packageName);
+            if (mCustomAnimationInfo == null) {
+                mCustomAnimationInfo = new CustomAnimationInfo(packageName);
+            }
             mCustomAnimationInfo.mWindowAnimations = windowAnimations;
             return this;
         }
+        /**
+         * Set resources ids for customize activity animation.
+         */
+        public Builder setCustomAnimation(String packageName, @AnimRes int enterResId,
+                @AnimRes int exitResId, @ColorInt int backgroundColor) {
+            if (mCustomAnimationInfo == null) {
+                mCustomAnimationInfo = new CustomAnimationInfo(packageName);
+            }
+            mCustomAnimationInfo.mCustomExitAnim = exitResId;
+            mCustomAnimationInfo.mCustomEnterAnim = enterResId;
+            mCustomAnimationInfo.mCustomBackground = backgroundColor;
+            return this;
+        }
 
         /**
          * Builds and returns an instance of {@link BackNavigationInfo}
diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
index b83d1d8..f19f6c7 100644
--- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
+++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
@@ -87,6 +87,8 @@
             new ComponentName("com.android.server.accessibility", "OneHandedMode");
     public static final ComponentName REDUCE_BRIGHT_COLORS_COMPONENT_NAME =
             new ComponentName("com.android.server.accessibility", "ReduceBrightColors");
+    public static final ComponentName FONT_SIZE_COMPONENT_NAME =
+            new ComponentName("com.android.server.accessibility", "FontSize");
 
     // The component name for the sub setting of Accessibility button in Accessibility settings
     public static final ComponentName ACCESSIBILITY_BUTTON_COMPONENT_NAME =
diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java
index 3226669..1c0da18 100644
--- a/core/java/com/android/internal/jank/FrameTracker.java
+++ b/core/java/com/android/internal/jank/FrameTracker.java
@@ -328,6 +328,7 @@
         mTracingStarted = true;
         markEvent("FT#begin");
         Trace.beginAsyncSection(mSession.getName(), (int) mBeginVsyncId);
+        markEvent("FT#layerId#" + mSurfaceControl.getLayerId());
         mSurfaceControlWrapper.addJankStatsListener(this, mSurfaceControl);
         if (!mSurfaceOnly) {
             mRendererWrapper.addObserver(mObserver);
@@ -437,8 +438,10 @@
                     "The length of the trace event description <%s> exceeds %d",
                     desc, MAX_LENGTH_EVENT_DESC));
         }
-        Trace.beginSection(TextUtils.formatSimple("%s#%s", mSession.getName(), desc));
-        Trace.endSection();
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_APP)) {
+            Trace.instant(Trace.TRACE_TAG_APP,
+                    TextUtils.formatSimple("%s#%s", mSession.getName(), desc));
+        }
     }
 
     private void notifyCujEvent(String action) {
diff --git a/core/java/com/android/internal/os/anr/AnrLatencyTracker.java b/core/java/com/android/internal/os/anr/AnrLatencyTracker.java
index 6a61656..096d1cd 100644
--- a/core/java/com/android/internal/os/anr/AnrLatencyTracker.java
+++ b/core/java/com/android/internal/os/anr/AnrLatencyTracker.java
@@ -137,14 +137,14 @@
         close();
     }
 
-    /** Records the start of ActivityManagerService#dumpStackTraces. */
+    /** Records the start of StackTracesDumpHelper#dumpStackTraces. */
     public void dumpStackTracesStarted() {
         mDumpStackTracesStartUptime = getUptimeMillis();
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER,
                 "dumpStackTraces()");
     }
 
-    /** Records the end of ActivityManagerService#dumpStackTraces. */
+    /** Records the end of StackTracesDumpHelper#dumpStackTraces. */
     public void dumpStackTracesEnded() {
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
     }
@@ -328,7 +328,7 @@
         anrSkipped("appNotResponding");
     }
 
-    /** Records a skipped ANR in ActivityManagerService#dumpStackTraces. */
+    /** Records a skipped ANR in StackTracesDumpHelper#dumpStackTraces. */
     public void anrSkippedDumpStackTraces() {
         anrSkipped("dumpStackTraces");
     }
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 8f04cda..c1dbc87 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -111,6 +111,7 @@
     void clickTile(in ComponentName tile);
     @UnsupportedAppUsage
     void handleSystemKey(in int key);
+    int getLastSystemKey();
 
     /**
      * Methods to show toast messages for screen pinning
diff --git a/core/java/com/android/internal/view/menu/CascadingMenuPopup.java b/core/java/com/android/internal/view/menu/CascadingMenuPopup.java
index a0f7905..f08e860 100644
--- a/core/java/com/android/internal/view/menu/CascadingMenuPopup.java
+++ b/core/java/com/android/internal/view/menu/CascadingMenuPopup.java
@@ -5,12 +5,14 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.StyleRes;
+import android.app.AppGlobals;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Rect;
 import android.os.Handler;
 import android.os.Parcelable;
 import android.os.SystemClock;
+import android.text.TextFlags;
 import android.view.Gravity;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
@@ -46,8 +48,6 @@
  */
 final class CascadingMenuPopup extends MenuPopup implements MenuPresenter, OnKeyListener,
         PopupWindow.OnDismissListener {
-    private static final int ITEM_LAYOUT = com.android.internal.R.layout.cascading_menu_item_layout;
-
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({HORIZ_POSITION_LEFT, HORIZ_POSITION_RIGHT})
     public @interface HorizPosition {}
@@ -190,6 +190,7 @@
     private Callback mPresenterCallback;
     private ViewTreeObserver mTreeObserver;
     private PopupWindow.OnDismissListener mOnDismissListener;
+    private final int mItemLayout;
 
     /** Whether popup menus should disable exit animations when closing. */
     private boolean mShouldCloseImmediately;
@@ -215,6 +216,12 @@
                 res.getDimensionPixelSize(com.android.internal.R.dimen.config_prefDialogWidth));
 
         mSubMenuHoverHandler = new Handler();
+
+        mItemLayout = AppGlobals.getIntCoreSetting(
+                TextFlags.KEY_ENABLE_NEW_CONTEXT_MENU,
+                TextFlags.ENABLE_NEW_CONTEXT_MENU_DEFAULT ? 1 : 0) != 0
+                ? com.android.internal.R.layout.cascading_menu_item_layout_material
+                : com.android.internal.R.layout.cascading_menu_item_layout;
     }
 
     @Override
@@ -348,7 +355,7 @@
      */
     private void showMenu(@NonNull MenuBuilder menu) {
         final LayoutInflater inflater = LayoutInflater.from(mContext);
-        final MenuAdapter adapter = new MenuAdapter(menu, inflater, mOverflowOnly, ITEM_LAYOUT);
+        final MenuAdapter adapter = new MenuAdapter(menu, inflater, mOverflowOnly, mItemLayout);
 
         // Apply "force show icon" setting. There are 3 cases:
         // (1) This is the top level menu and icon spacing is forced. Add spacing.
diff --git a/core/java/com/android/internal/view/menu/ListMenuItemView.java b/core/java/com/android/internal/view/menu/ListMenuItemView.java
index 0d2b29b..cb1abf1 100644
--- a/core/java/com/android/internal/view/menu/ListMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ListMenuItemView.java
@@ -16,12 +16,12 @@
 
 package com.android.internal.view.menu;
 
-import com.android.internal.R;
-
+import android.app.AppGlobals;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.text.TextFlags;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -61,6 +61,8 @@
 
     private int mMenuType;
 
+    private boolean mUseNewContextMenu;
+
     private LayoutInflater mInflater;
 
     private boolean mForceShowIcon;
@@ -87,6 +89,10 @@
 
         a.recycle();
         b.recycle();
+
+        mUseNewContextMenu = AppGlobals.getIntCoreSetting(
+                TextFlags.KEY_ENABLE_NEW_CONTEXT_MENU,
+                TextFlags.ENABLE_NEW_CONTEXT_MENU_DEFAULT ? 1 : 0) != 0;
     }
 
     public ListMenuItemView(Context context, AttributeSet attrs, int defStyleAttr) {
@@ -283,7 +289,9 @@
 
     private void insertIconView() {
         LayoutInflater inflater = getInflater();
-        mIconView = (ImageView) inflater.inflate(com.android.internal.R.layout.list_menu_item_icon,
+        mIconView = (ImageView) inflater.inflate(
+                mUseNewContextMenu ? com.android.internal.R.layout.list_menu_item_fixed_size_icon :
+                        com.android.internal.R.layout.list_menu_item_icon,
                 this, false);
         addContentView(mIconView, 0);
     }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 12106c7..2f0ef14 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -7401,6 +7401,8 @@
          <p>Protection level: normal
     -->
     <permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION"
+                android:label="@string/permlab_updatePackagesWithoutUserAction"
+                android:description="@string/permdesc_updatePackagesWithoutUserAction"
                 android:protectionLevel="normal" />
     <uses-permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION"/>
 
diff --git a/core/res/res/layout/cascading_menu_item_layout_material.xml b/core/res/res/layout/cascading_menu_item_layout_material.xml
new file mode 100644
index 0000000..168ed78
--- /dev/null
+++ b/core/res/res/layout/cascading_menu_item_layout_material.xml
@@ -0,0 +1,84 @@
+<?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.
+-->
+
+<!-- Keep in sync with popup_menu_item_layout.xml (which only differs in the title and shortcut
+    position). -->
+<com.android.internal.view.menu.ListMenuItemView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minWidth="112dip"
+    android:maxWidth="280dip"
+    android:orientation="vertical" >
+
+    <ImageView
+        android:id="@+id/group_divider"
+        android:layout_width="match_parent"
+        android:layout_height="1dip"
+        android:layout_marginTop="8dip"
+        android:layout_marginBottom="8dip"
+        android:background="@drawable/list_divider_material" />
+
+    <LinearLayout
+        android:id="@+id/content"
+        android:layout_width="match_parent"
+        android:layout_height="48dip"
+        android:layout_marginStart="12dip"
+        android:layout_marginEnd="12dip"
+        android:duplicateParentState="true" >
+
+        <!-- Icon will be inserted here. -->
+
+        <TextView
+            android:id="@+id/title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:layout_marginStart="6dip"
+            android:textAppearance="?attr/textAppearanceLargePopupMenu"
+            android:singleLine="true"
+            android:duplicateParentState="true"
+            android:textAlignment="viewStart" />
+
+        <Space
+            android:layout_width="0dip"
+            android:layout_height="1dip"
+            android:layout_weight="1"/>
+
+        <TextView
+            android:id="@+id/shortcut"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:layout_marginStart="16dip"
+            android:textAppearance="?attr/textAppearanceSmallPopupMenu"
+            android:singleLine="true"
+            android:duplicateParentState="true"
+            android:textAlignment="viewEnd" />
+
+        <ImageView
+            android:id="@+id/submenuarrow"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:layout_marginStart="8dp"
+            android:scaleType="center"
+            android:visibility="gone" />
+
+        <!-- Checkbox, and/or radio button will be inserted here. -->
+
+    </LinearLayout>
+
+</com.android.internal.view.menu.ListMenuItemView>
diff --git a/core/res/res/layout/list_menu_item_fixed_size_icon.xml b/core/res/res/layout/list_menu_item_fixed_size_icon.xml
new file mode 100644
index 0000000..7797682
--- /dev/null
+++ b/core/res/res/layout/list_menu_item_fixed_size_icon.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+
+<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/icon"
+    android:layout_width="24dp"
+    android:layout_height="24dp"
+    android:layout_gravity="center_vertical"
+    android:layout_marginStart="0dip"
+    android:layout_marginEnd="6dip"
+    android:layout_marginTop="0dip"
+    android:layout_marginBottom="0dip"
+    android:scaleType="centerInside"
+    android:duplicateParentState="true" />
+
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 6afdae5..3ee8af2 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2191,6 +2191,11 @@
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this.[CHAR_LIMIT=NONE] -->
     <string name="permdesc_highSamplingRateSensors">Allows the app to sample sensor data at a rate greater than 200 Hz</string>
 
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=NONE] -->
+    <string name="permlab_updatePackagesWithoutUserAction">update app without user action</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this.[CHAR_LIMIT=NONE] -->
+    <string name="permdesc_updatePackagesWithoutUserAction">Allows the holder to update the app it previously installed without user action</string>
+
     <!-- Policy administration -->
 
     <!-- Title of policy access to limiting the user's password choices -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 8fb7c9d..5c734df 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1476,6 +1476,7 @@
   <java-symbol type="layout" name="action_mode_close_item" />
   <java-symbol type="layout" name="alert_dialog" />
   <java-symbol type="layout" name="cascading_menu_item_layout" />
+  <java-symbol type="layout" name="cascading_menu_item_layout_material" />
   <java-symbol type="layout" name="choose_account" />
   <java-symbol type="layout" name="choose_account_row" />
   <java-symbol type="layout" name="choose_account_type" />
@@ -1525,6 +1526,7 @@
   <java-symbol type="layout" name="list_content_simple" />
   <java-symbol type="layout" name="list_menu_item_checkbox" />
   <java-symbol type="layout" name="list_menu_item_icon" />
+  <java-symbol type="layout" name="list_menu_item_fixed_size_icon" />
   <java-symbol type="layout" name="list_menu_item_layout" />
   <java-symbol type="layout" name="list_menu_item_radio" />
   <java-symbol type="layout" name="locale_picker_item" />
diff --git a/core/tests/coretests/src/android/companion/virtual/camera/VirtualCameraOutputTest.java b/core/tests/coretests/src/android/companion/virtual/camera/VirtualCameraOutputTest.java
deleted file mode 100644
index f96d138..0000000
--- a/core/tests/coretests/src/android/companion/virtual/camera/VirtualCameraOutputTest.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.companion.virtual.camera;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.fail;
-
-import android.graphics.PixelFormat;
-import android.hardware.camera2.params.InputConfiguration;
-import android.os.ParcelFileDescriptor;
-import android.platform.test.annotations.Presubmit;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.ByteArrayInputStream;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-@Presubmit
-@RunWith(AndroidJUnit4.class)
-public class VirtualCameraOutputTest {
-
-    private static final String TAG = "VirtualCameraOutputTest";
-
-    private ExecutorService mExecutor;
-
-    private InputConfiguration mConfiguration;
-
-    @Before
-    public void setUp() {
-        mExecutor = Executors.newSingleThreadExecutor();
-        mConfiguration = new InputConfiguration(64, 64, PixelFormat.RGB_888);
-    }
-
-    @After
-    public void cleanUp() {
-        mExecutor.shutdownNow();
-    }
-
-    @Test
-    public void createStreamDescriptor_successfulDataStream() {
-        byte[] cameraData = new byte[]{1, 2, 3, 4, 5};
-        VirtualCameraInput input = createCameraInput(cameraData);
-        VirtualCameraOutput output = new VirtualCameraOutput(input, mExecutor);
-        ParcelFileDescriptor descriptor = output.getStreamDescriptor(mConfiguration);
-
-        try (FileInputStream fis = new FileInputStream(descriptor.getFileDescriptor())) {
-            byte[] receivedData = fis.readNBytes(cameraData.length);
-
-            output.closeStream();
-            assertThat(receivedData).isEqualTo(cameraData);
-        } catch (IOException exception) {
-            fail("Unable to read bytes from FileInputStream. Message: " + exception.getMessage());
-        }
-    }
-
-    @Test
-    public void createStreamDescriptor_multipleCallsSameStream() {
-        VirtualCameraInput input = createCameraInput(new byte[]{0});
-        VirtualCameraOutput output = new VirtualCameraOutput(input, mExecutor);
-
-        ParcelFileDescriptor firstDescriptor = output.getStreamDescriptor(mConfiguration);
-        ParcelFileDescriptor secondDescriptor = output.getStreamDescriptor(mConfiguration);
-
-        assertThat(firstDescriptor).isSameInstanceAs(secondDescriptor);
-    }
-
-    @Test
-    public void createStreamDescriptor_differentStreams() {
-        VirtualCameraInput input = createCameraInput(new byte[]{0});
-        VirtualCameraOutput callback = new VirtualCameraOutput(input, mExecutor);
-
-        InputConfiguration differentConfig = new InputConfiguration(mConfiguration.getWidth() + 1,
-                mConfiguration.getHeight() + 1, mConfiguration.getFormat());
-
-        ParcelFileDescriptor firstDescriptor = callback.getStreamDescriptor(mConfiguration);
-        ParcelFileDescriptor secondDescriptor = callback.getStreamDescriptor(differentConfig);
-
-        assertThat(firstDescriptor).isNotSameInstanceAs(secondDescriptor);
-    }
-
-    private VirtualCameraInput createCameraInput(byte[] data) {
-        return new VirtualCameraInput() {
-            private ByteArrayInputStream mInputStream = null;
-
-            @Override
-            @NonNull
-            public InputStream openStream(@NonNull InputConfiguration inputConfiguration) {
-                closeStream();
-                mInputStream = new ByteArrayInputStream(data);
-                return mInputStream;
-            }
-
-            @Override
-            public void closeStream() {
-                if (mInputStream == null) {
-                    return;
-                }
-                try {
-                    mInputStream.close();
-                } catch (IOException e) {
-                    Log.e(TAG, "Unable to close image stream.", e);
-                }
-                mInputStream = null;
-            }
-        };
-    }
-}
diff --git a/core/tests/coretests/src/android/credentials/CredentialManagerTest.java b/core/tests/coretests/src/android/credentials/CredentialManagerTest.java
index 6f0c3d3..c7e0261 100644
--- a/core/tests/coretests/src/android/credentials/CredentialManagerTest.java
+++ b/core/tests/coretests/src/android/credentials/CredentialManagerTest.java
@@ -49,6 +49,7 @@
 import org.mockito.junit.MockitoJUnitRunner;
 
 import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.Executor;
@@ -126,11 +127,11 @@
                 null, List.of(Slice.HINT_TITLE)).build();
         mRegisterRequest = new RegisterCredentialDescriptionRequest(
                 new CredentialDescription(Credential.TYPE_PASSWORD_CREDENTIAL,
-                        "{ \"foo\": \"bar\" }",
+                        new HashSet<>(List.of("{ \"foo\": \"bar\" }")),
                         List.of(new CredentialEntry(Credential.TYPE_PASSWORD_CREDENTIAL, slice))));
         mUnregisterRequest = new UnregisterCredentialDescriptionRequest(
                 new CredentialDescription(Credential.TYPE_PASSWORD_CREDENTIAL,
-                        "{ \"foo\": \"bar\" }",
+                        new HashSet<>(List.of("{ \"foo\": \"bar\" }")),
                         List.of(new CredentialEntry(Credential.TYPE_PASSWORD_CREDENTIAL, slice))));
 
         final Context context = InstrumentationRegistry.getInstrumentation().getContext();
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
index 60898ef..51f99ec 100644
--- a/graphics/java/android/graphics/ImageDecoder.java
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -914,7 +914,6 @@
             case "image/gif":
             case "image/heif":
             case "image/heic":
-            case "image/avif":
             case "image/bmp":
             case "image/x-ico":
             case "image/vnd.wap.wbmp":
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 404429a..89f4890 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -40,7 +40,9 @@
 import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenAdjacent;
 import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenStacked;
 import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPAND_FAILED_NO_TF_INFO;
+import static androidx.window.extensions.embedding.SplitPresenter.getActivitiesMinDimensionsPair;
 import static androidx.window.extensions.embedding.SplitPresenter.getActivityIntentMinDimensionsPair;
+import static androidx.window.extensions.embedding.SplitPresenter.getTaskWindowMetrics;
 import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSplit;
 
 import android.app.Activity;
@@ -1037,9 +1039,15 @@
         final TaskFragmentContainer primaryContainer = getContainerWithActivity(
                 primaryActivity);
         final SplitContainer splitContainer = getActiveSplitForContainer(primaryContainer);
-        final WindowMetrics taskWindowMetrics = mPresenter.getTaskWindowMetrics(primaryActivity);
+        final TaskContainer.TaskProperties taskProperties = mPresenter
+                .getTaskProperties(primaryActivity);
+        final SplitAttributes calculatedSplitAttributes = mPresenter.computeSplitAttributes(
+                taskProperties, splitRule, splitRule.getDefaultSplitAttributes(),
+                getActivitiesMinDimensionsPair(primaryActivity, secondaryActivity));
         if (splitContainer != null && primaryContainer == splitContainer.getPrimaryContainer()
-                && canReuseContainer(splitRule, splitContainer.getSplitRule(), taskWindowMetrics)) {
+                && canReuseContainer(splitRule, splitContainer.getSplitRule(),
+                        getTaskWindowMetrics(taskProperties.getConfiguration()),
+                        calculatedSplitAttributes, splitContainer.getCurrentSplitAttributes())) {
             // Can launch in the existing secondary container if the rules share the same
             // presentation.
             final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer();
@@ -1058,7 +1066,8 @@
             }
         }
         // Create new split pair.
-        mPresenter.createNewSplitContainer(wct, primaryActivity, secondaryActivity, splitRule);
+        mPresenter.createNewSplitContainer(wct, primaryActivity, secondaryActivity, splitRule,
+                calculatedSplitAttributes);
         return true;
     }
 
@@ -1283,9 +1292,16 @@
         }
         final TaskFragmentContainer existingContainer = getContainerWithActivity(primaryActivity);
         final SplitContainer splitContainer = getActiveSplitForContainer(existingContainer);
-        final WindowMetrics taskWindowMetrics = mPresenter.getTaskWindowMetrics(primaryActivity);
+        final TaskContainer.TaskProperties taskProperties = mPresenter
+                .getTaskProperties(primaryActivity);
+        final WindowMetrics taskWindowMetrics = getTaskWindowMetrics(
+                taskProperties.getConfiguration());
+        final SplitAttributes calculatedSplitAttributes = mPresenter.computeSplitAttributes(
+                taskProperties, splitRule, splitRule.getDefaultSplitAttributes(),
+                getActivityIntentMinDimensionsPair(primaryActivity, intent));
         if (splitContainer != null && existingContainer == splitContainer.getPrimaryContainer()
-                && (canReuseContainer(splitRule, splitContainer.getSplitRule(), taskWindowMetrics)
+                && (canReuseContainer(splitRule, splitContainer.getSplitRule(), taskWindowMetrics,
+                        calculatedSplitAttributes, splitContainer.getCurrentSplitAttributes())
                 // TODO(b/231845476) we should always respect clearTop.
                 || !respectClearTop)
                 && mPresenter.expandSplitContainerIfNeeded(wct, splitContainer, primaryActivity,
@@ -1296,7 +1312,7 @@
         }
         // Create a new TaskFragment to split with the primary activity for the new activity.
         return mPresenter.createNewSplitWithEmptySideContainer(wct, primaryActivity, intent,
-                splitRule);
+                splitRule, calculatedSplitAttributes);
     }
 
     /**
@@ -2273,21 +2289,29 @@
     }
 
     /**
-     * If the two rules have the same presentation, we can reuse the same {@link SplitContainer} if
-     * there is any.
+     * If the two rules have the same presentation, and the calculated {@link SplitAttributes}
+     * matches the {@link SplitAttributes} of {@link SplitContainer}, we can reuse the same
+     * {@link SplitContainer} if there is any.
      */
     private static boolean canReuseContainer(@NonNull SplitRule rule1, @NonNull SplitRule rule2,
-            @NonNull WindowMetrics parentWindowMetrics) {
+            @NonNull WindowMetrics parentWindowMetrics,
+            @NonNull SplitAttributes calculatedSplitAttributes,
+            @NonNull SplitAttributes containerSplitAttributes) {
         if (!isContainerReusableRule(rule1) || !isContainerReusableRule(rule2)) {
             return false;
         }
-        return haveSamePresentation((SplitPairRule) rule1, (SplitPairRule) rule2,
-                parentWindowMetrics);
+        return areRulesSamePresentation((SplitPairRule) rule1, (SplitPairRule) rule2,
+                parentWindowMetrics)
+                // Besides rules, we should also check whether the SplitContainer's splitAttributes
+                // matches the current splitAttributes or not. The splitAttributes may change
+                // if the app chooses different SplitAttributes calculator function before a new
+                // activity is started even they match the same splitRule.
+                && calculatedSplitAttributes.equals(containerSplitAttributes);
     }
 
     /** Whether the two rules have the same presentation. */
     @VisibleForTesting
-    static boolean haveSamePresentation(@NonNull SplitPairRule rule1,
+    static boolean areRulesSamePresentation(@NonNull SplitPairRule rule1,
             @NonNull SplitPairRule rule2, @NonNull WindowMetrics parentWindowMetrics) {
         if (rule1.getTag() != null || rule2.getTag() != null) {
             // Tag must be unique if it is set. We don't want to reuse the container if the rules
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 0408511..20b6ae2 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -174,12 +174,9 @@
     @NonNull
     TaskFragmentContainer createNewSplitWithEmptySideContainer(
             @NonNull WindowContainerTransaction wct, @NonNull Activity primaryActivity,
-            @NonNull Intent secondaryIntent, @NonNull SplitPairRule rule) {
+            @NonNull Intent secondaryIntent, @NonNull SplitPairRule rule,
+            @NonNull SplitAttributes splitAttributes) {
         final TaskProperties taskProperties = getTaskProperties(primaryActivity);
-        final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair(
-                primaryActivity, secondaryIntent);
-        final SplitAttributes splitAttributes = computeSplitAttributes(taskProperties, rule,
-                rule.getDefaultSplitAttributes(), minDimensionsPair);
         final Rect primaryRelBounds = getRelBoundsForPosition(POSITION_START, taskProperties,
                 splitAttributes);
         final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct,
@@ -217,15 +214,12 @@
      *                          same container as the primary activity, a new container will be
      *                          created and the activity will be re-parented to it.
      * @param rule The split rule to be applied to the container.
+     * @param splitAttributes The {@link SplitAttributes} to apply
      */
     void createNewSplitContainer(@NonNull WindowContainerTransaction wct,
             @NonNull Activity primaryActivity, @NonNull Activity secondaryActivity,
-            @NonNull SplitPairRule rule) {
+            @NonNull SplitPairRule rule, @NonNull SplitAttributes splitAttributes) {
         final TaskProperties taskProperties = getTaskProperties(primaryActivity);
-        final Pair<Size, Size> minDimensionsPair = getActivitiesMinDimensionsPair(primaryActivity,
-                secondaryActivity);
-        final SplitAttributes splitAttributes = computeSplitAttributes(taskProperties, rule,
-                rule.getDefaultSplitAttributes(), minDimensionsPair);
         final Rect primaryRelBounds = getRelBoundsForPosition(POSITION_START, taskProperties,
                 splitAttributes);
         final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct,
@@ -654,7 +648,7 @@
     }
 
     @NonNull
-    private static Pair<Size, Size> getActivitiesMinDimensionsPair(
+    static Pair<Size, Size> getActivitiesMinDimensionsPair(
             @NonNull Activity primaryActivity, @NonNull Activity secondaryActivity) {
         return new Pair<>(getMinDimensions(primaryActivity), getMinDimensions(secondaryActivity));
     }
@@ -1027,7 +1021,7 @@
     }
 
     @NonNull
-    private static WindowMetrics getTaskWindowMetrics(@NonNull Configuration taskConfiguration) {
+    static WindowMetrics getTaskWindowMetrics(@NonNull Configuration taskConfiguration) {
         final Rect taskBounds = taskConfiguration.windowConfiguration.getBounds();
         // TODO(b/190433398): Supply correct insets.
         final float density = taskConfiguration.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index cbdc262..ff08782 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -565,7 +565,6 @@
         assertNotNull(mSplitController.getActiveSplitForContainers(primaryContainer, container));
         assertTrue(primaryContainer.areLastRequestedBoundsEqual(null));
         assertTrue(container.areLastRequestedBoundsEqual(null));
-        assertEquals(container, mSplitController.getContainerWithActivity(secondaryActivity));
     }
 
     @Test
@@ -1008,9 +1007,8 @@
 
         assertTrue(result);
         assertSplitPair(primaryActivity, mActivity, true /* matchParentBounds */);
-        assertEquals(mSplitController.getContainerWithActivity(secondaryActivity),
-                mSplitController.getContainerWithActivity(mActivity));
-        verify(mSplitPresenter, never()).createNewSplitContainer(any(), any(), any(), any());
+        assertTrue(mSplitController.getContainerWithActivity(mActivity)
+                .areLastRequestedBoundsEqual(new Rect()));
     }
 
     @Test
@@ -1215,7 +1213,7 @@
                 .build();
 
         assertTrue("Rules must have same presentation if tags are null and has same properties.",
-                SplitController.haveSamePresentation(splitRule1, splitRule2,
+                SplitController.areRulesSamePresentation(splitRule1, splitRule2,
                         new WindowMetrics(TASK_BOUNDS, WindowInsets.CONSUMED)));
 
         splitRule2 = createSplitPairRuleBuilder(
@@ -1230,7 +1228,7 @@
 
         assertFalse("Rules must have different presentations if tags are not equal regardless"
                         + "of other properties",
-                SplitController.haveSamePresentation(splitRule1, splitRule2,
+                SplitController.areRulesSamePresentation(splitRule1, splitRule2,
                         new WindowMetrics(TASK_BOUNDS, WindowInsets.CONSUMED)));
     }
 
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
index 8330156..8dd1384 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
@@ -678,7 +678,8 @@
                 .setShouldClearTop(false)
                 .build();
 
-        mPresenter.createNewSplitContainer(mTransaction, mActivity, secondaryActivity, rule);
+        mPresenter.createNewSplitContainer(mTransaction, mActivity, secondaryActivity, rule,
+                SPLIT_ATTRIBUTES);
 
         assertEquals(primaryTf, mController.getContainerWithActivity(mActivity));
         final TaskFragmentContainer secondaryTf = mController.getContainerWithActivity(
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index c7c9424..54978bd 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -46,6 +46,8 @@
         "src/com/android/wm/shell/common/split/SplitScreenConstants.java",
         "src/com/android/wm/shell/sysui/ShellSharedConstants.java",
         "src/com/android/wm/shell/common/TransactionPool.java",
+        "src/com/android/wm/shell/common/bubbles/*.java",
+        "src/com/android/wm/shell/common/TriangleShape.java",
         "src/com/android/wm/shell/animation/Interpolators.java",
         "src/com/android/wm/shell/pip/PipContentOverlay.java",
         "src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
index ae33b94..4eaedd3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
@@ -25,7 +25,10 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Activity;
 import android.content.Context;
+import android.graphics.Color;
 import android.graphics.Rect;
 import android.os.RemoteException;
 import android.util.FloatProperty;
@@ -34,6 +37,7 @@
 import android.view.IRemoteAnimationRunner;
 import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
+import android.view.WindowManager.LayoutParams;
 import android.view.animation.Animation;
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Transformation;
@@ -43,6 +47,7 @@
 import android.window.BackProgressAnimator;
 import android.window.IOnBackInvokedCallback;
 
+import com.android.internal.R;
 import com.android.internal.dynamicanimation.animation.SpringAnimation;
 import com.android.internal.dynamicanimation.animation.SpringForce;
 import com.android.internal.policy.ScreenDecorationsUtils;
@@ -78,6 +83,7 @@
     final CustomAnimationLoader mCustomAnimationLoader;
     private Animation mEnterAnimation;
     private Animation mCloseAnimation;
+    private int mNextBackgroundColor;
     final Transformation mTransformation = new Transformation();
 
     private final Choreographer mChoreographer;
@@ -144,8 +150,9 @@
 
         // Draw background with task background color.
         if (mEnteringTarget.taskInfo != null && mEnteringTarget.taskInfo.taskDescription != null) {
-            mBackground.ensureBackground(
-                    mEnteringTarget.taskInfo.taskDescription.getBackgroundColor(), mTransaction);
+            mBackground.ensureBackground(mNextBackgroundColor == Color.TRANSPARENT
+                    ? mEnteringTarget.taskInfo.taskDescription.getBackgroundColor()
+                    : mNextBackgroundColor, mTransaction);
         }
     }
 
@@ -191,6 +198,7 @@
         mTransaction.apply();
         mTransformation.clear();
         mLatestProgress = 0;
+        mNextBackgroundColor = Color.TRANSPARENT;
         if (mFinishCallback != null) {
             try {
                 mFinishCallback.onAnimationFinished();
@@ -252,11 +260,11 @@
      * Load customize animation before animation start.
      */
     boolean prepareNextAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo) {
-        mCloseAnimation = mCustomAnimationLoader.load(
-                animationInfo, false /* enterAnimation */);
-        if (mCloseAnimation != null) {
-            mEnterAnimation = mCustomAnimationLoader.load(
-                    animationInfo, true /* enterAnimation */);
+        final AnimationLoadResult result = mCustomAnimationLoader.loadAll(animationInfo);
+        if (result != null) {
+            mCloseAnimation = result.mCloseAnimation;
+            mEnterAnimation = result.mEnterAnimation;
+            mNextBackgroundColor = result.mBackgroundColor;
             return true;
         }
         return false;
@@ -318,35 +326,79 @@
         }
     }
 
+
+    static final class AnimationLoadResult {
+        Animation mCloseAnimation;
+        Animation mEnterAnimation;
+        int mBackgroundColor;
+    }
+
     /**
      * Helper class to load custom animation.
      */
     static class CustomAnimationLoader {
-        private final TransitionAnimation mTransitionAnimation;
+        final TransitionAnimation mTransitionAnimation;
 
         CustomAnimationLoader(Context context) {
             mTransitionAnimation = new TransitionAnimation(
                     context, false /* debug */, "CustomizeBackAnimation");
         }
 
-        Animation load(BackNavigationInfo.CustomAnimationInfo animationInfo,
+        /**
+         * Load both enter and exit animation for the close activity transition.
+         * Note that the result is only valid if the exit animation has set and loaded success.
+         * If the entering animation has not set(i.e. 0), here will load the default entering
+         * animation for it.
+         *
+         * @param animationInfo The information of customize animation, which can be set from
+         * {@link Activity#overrideActivityTransition} and/or
+         * {@link LayoutParams#windowAnimations}
+         */
+        AnimationLoadResult loadAll(BackNavigationInfo.CustomAnimationInfo animationInfo) {
+            if (animationInfo.getPackageName().isEmpty()) {
+                return null;
+            }
+            final Animation close = loadAnimation(animationInfo, false);
+            if (close == null) {
+                return null;
+            }
+            final Animation open = loadAnimation(animationInfo, true);
+            AnimationLoadResult result = new AnimationLoadResult();
+            result.mCloseAnimation = close;
+            result.mEnterAnimation = open;
+            result.mBackgroundColor = animationInfo.getCustomBackground();
+            return result;
+        }
+
+        /**
+         * Load enter or exit animation from CustomAnimationInfo
+         * @param animationInfo The information for customize animation.
+         * @param enterAnimation true when load for enter animation, false for exit animation.
+         * @return Loaded animation.
+         */
+        @Nullable
+        Animation loadAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo,
                 boolean enterAnimation) {
-            final String packageName = animationInfo.getPackageName();
-            if (packageName.isEmpty()) {
-                return null;
+            Animation a = null;
+            // Activity#overrideActivityTransition has higher priority than windowAnimations
+            // Try to get animation from Activity#overrideActivityTransition
+            if ((enterAnimation && animationInfo.getCustomEnterAnim() != 0)
+                    || (!enterAnimation && animationInfo.getCustomExitAnim() != 0)) {
+                a = mTransitionAnimation.loadAppTransitionAnimation(
+                        animationInfo.getPackageName(),
+                        enterAnimation ? animationInfo.getCustomEnterAnim()
+                                : animationInfo.getCustomExitAnim());
+            } else if (animationInfo.getWindowAnimations() != 0) {
+                // try to get animation from LayoutParams#windowAnimations
+                a = mTransitionAnimation.loadAnimationAttr(animationInfo.getPackageName(),
+                        animationInfo.getWindowAnimations(), enterAnimation
+                                ? R.styleable.WindowAnimation_activityCloseEnterAnimation
+                                : R.styleable.WindowAnimation_activityCloseExitAnimation,
+                        false /* translucent */);
             }
-            final int windowAnimations = animationInfo.getWindowAnimations();
-            if (windowAnimations == 0) {
-                return null;
-            }
-            final int attrs = enterAnimation
-                    ? com.android.internal.R.styleable.WindowAnimation_activityCloseEnterAnimation
-                    : com.android.internal.R.styleable.WindowAnimation_activityCloseExitAnimation;
-            Animation a = mTransitionAnimation.loadAnimationAttr(packageName, windowAnimations,
-                    attrs, false /* translucent */);
             // Only allow to load default animation for opening target.
             if (a == null && enterAnimation) {
-                a = mTransitionAnimation.loadDefaultAnimationAttr(attrs, false /* translucent */);
+                a = loadDefaultOpenAnimation();
             }
             if (a != null) {
                 ProtoLog.d(WM_SHELL_BACK_PREVIEW, "custom animation loaded %s", a);
@@ -355,5 +407,11 @@
             }
             return a;
         }
+
+        private Animation loadDefaultOpenAnimation() {
+            return mTransitionAnimation.loadDefaultAnimationAttr(
+                    R.styleable.WindowAnimation_activityCloseEnterAnimation,
+                    false /* translucent */);
+        }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 85a353f..4805ed3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -47,6 +47,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.InstanceId;
+import com.android.wm.shell.common.bubbles.BubbleInfo;
 
 import java.io.PrintWriter;
 import java.util.List;
@@ -244,6 +245,16 @@
         setEntry(entry);
     }
 
+    /** Converts this bubble into a {@link BubbleInfo} object to be shared with external callers. */
+    public BubbleInfo asBubbleBarBubble() {
+        return new BubbleInfo(getKey(),
+                getFlags(),
+                getShortcutInfo().getId(),
+                getIcon(),
+                getUser().getIdentifier(),
+                getPackageName());
+    }
+
     @Override
     public String getKey() {
         return mKey;
@@ -545,8 +556,13 @@
         }
     }
 
+    /**
+     * @return the icon set on BubbleMetadata, if it exists. This is only non-null for bubbles
+     * created via a PendingIntent. This is null for bubbles created by a shortcut, as we use the
+     * icon from the shortcut.
+     */
     @Nullable
-    Icon getIcon() {
+    public Icon getIcon() {
         return mIcon;
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index d2889e7..4b4b1af 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -38,7 +38,9 @@
 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_PACKAGE_REMOVED;
 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_SHORTCUT_REMOVED;
 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_USER_CHANGED;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BUBBLES;
 
+import android.annotation.BinderThread;
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
@@ -59,6 +61,7 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Icon;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -88,13 +91,17 @@
 import com.android.wm.shell.TaskViewTransitions;
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
 import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.RemoteCallable;
 import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SingleInstanceRemoteListener;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TaskStackListenerCallback;
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.common.annotations.ShellBackgroundThread;
 import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
 import com.android.wm.shell.draganddrop.DragAndDropController;
 import com.android.wm.shell.onehanded.OneHandedController;
 import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
@@ -123,7 +130,8 @@
  *
  * The controller manages addition, removal, and visible state of bubbles on screen.
  */
-public class BubbleController implements ConfigurationChangeListener {
+public class BubbleController implements ConfigurationChangeListener,
+        RemoteCallable<BubbleController> {
 
     private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES;
 
@@ -248,6 +256,8 @@
     private Optional<OneHandedController> mOneHandedOptional;
     /** Drag and drop controller to register listener for onDragStarted. */
     private DragAndDropController mDragAndDropController;
+    /** Used to send bubble events to launcher. */
+    private Bubbles.BubbleStateListener mBubbleStateListener;
 
     public BubbleController(Context context,
             ShellInit shellInit,
@@ -458,9 +468,15 @@
         mCurrentProfiles = userProfiles;
 
         mShellController.addConfigurationChangeListener(this);
+        mShellController.addExternalInterface(KEY_EXTRA_SHELL_BUBBLES,
+                this::createExternalInterface, this);
         mShellCommandHandler.addDumpCallback(this::dump, this);
     }
 
+    private ExternalInterfaceBinder createExternalInterface() {
+        return new BubbleController.IBubblesImpl(this);
+    }
+
     @VisibleForTesting
     public Bubbles asBubbles() {
         return mImpl;
@@ -475,6 +491,48 @@
         return mMainExecutor;
     }
 
+    @Override
+    public Context getContext() {
+        return mContext;
+    }
+
+    @Override
+    public ShellExecutor getRemoteCallExecutor() {
+        return mMainExecutor;
+    }
+
+    /**
+     * Sets a listener to be notified of bubble updates. This is used by launcher so that
+     * it may render bubbles in itself. Only one listener is supported.
+     */
+    public void registerBubbleStateListener(Bubbles.BubbleStateListener listener) {
+        if (isShowingAsBubbleBar()) {
+            // Only set the listener if bubble bar is showing.
+            mBubbleStateListener = listener;
+            sendInitialListenerUpdate();
+        } else {
+            mBubbleStateListener = null;
+        }
+    }
+
+    /**
+     * Unregisters the {@link Bubbles.BubbleStateListener}.
+     */
+    public void unregisterBubbleStateListener() {
+        mBubbleStateListener = null;
+    }
+
+    /**
+     * If a {@link Bubbles.BubbleStateListener} is present, this will send the current bubble
+     * state to it.
+     */
+    private void sendInitialListenerUpdate() {
+        if (mBubbleStateListener != null) {
+            BubbleBarUpdate update = mBubbleData.getInitialStateForBubbleBar();
+            mBubbleStateListener.onBubbleStateChange(update);
+        }
+    }
+
     /**
      * Hides the current input method, wherever it may be focused, via InputMethodManagerInternal.
      */
@@ -1722,6 +1780,73 @@
         }
     }
 
+    /**
+     * The interface for calls from outside the host process.
+     */
+    @BinderThread
+    private class IBubblesImpl extends IBubbles.Stub implements ExternalInterfaceBinder {
+        private BubbleController mController;
+        private final SingleInstanceRemoteListener<BubbleController, IBubblesListener> mListener;
+        private final Bubbles.BubbleStateListener mBubbleListener =
+                new Bubbles.BubbleStateListener() {
+
+            @Override
+            public void onBubbleStateChange(BubbleBarUpdate update) {
+                Bundle b = new Bundle();
+                b.setClassLoader(BubbleBarUpdate.class.getClassLoader());
+                b.putParcelable(BubbleBarUpdate.BUNDLE_KEY, update);
+                mListener.call(l -> l.onBubbleStateChange(b));
+            }
+        };
+
+        IBubblesImpl(BubbleController controller) {
+            mController = controller;
+            mListener = new SingleInstanceRemoteListener<>(mController,
+                    c -> c.registerBubbleStateListener(mBubbleListener),
+                    c -> c.unregisterBubbleStateListener());
+        }
+
+        /**
+         * Invalidates this instance, preventing future calls from updating the controller.
+         */
+        @Override
+        public void invalidate() {
+            mController = null;
+        }
+
+        @Override
+        public void registerBubbleListener(IBubblesListener listener) {
+            mMainExecutor.execute(() -> {
+                mListener.register(listener);
+            });
+        }
+
+        @Override
+        public void unregisterBubbleListener(IBubblesListener listener) {
+            mMainExecutor.execute(() -> mListener.unregister());
+        }
+
+        @Override
+        public void showBubble(String key, boolean onLauncherHome) {
+            // TODO
+        }
+
+        @Override
+        public void removeBubble(String key, int reason) {
+            // TODO
+        }
+
+        @Override
+        public void collapseBubbles() {
+            // TODO
+        }
+
+        @Override
+        public void onTaskbarStateChanged(int newState) {
+            // TODO (b/269670598)
+        }
+    }
+
     private class BubblesImpl implements Bubbles {
         // Up-to-date cached state of bubbles data for SysUI to query from the calling thread
         @VisibleForTesting
@@ -1835,6 +1960,17 @@
 
         private CachedState mCachedState = new CachedState();
 
+        private IBubblesImpl mIBubbles;
+
+        @Override
+        public IBubbles createExternalInterface() {
+            if (mIBubbles != null) {
+                mIBubbles.invalidate();
+            }
+            mIBubbles = new IBubblesImpl(BubbleController.this);
+            return mIBubbles;
+        }
+
         @Override
         public boolean isBubbleNotificationSuppressedFromShade(String key, String groupKey) {
             return mCachedState.isBubbleNotificationSuppressedFromShade(key, groupKey);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index 3fd0967..a26c0c4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -40,6 +40,8 @@
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.wm.shell.R;
 import com.android.wm.shell.bubbles.Bubbles.DismissReason;
+import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
+import com.android.wm.shell.common.bubbles.RemovedBubble;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -113,6 +115,61 @@
         void bubbleRemoved(Bubble bubbleToRemove, @DismissReason int reason) {
             removedBubbles.add(new Pair<>(bubbleToRemove, reason));
         }
+
+        /**
+         * Converts the update to a {@link BubbleBarUpdate} which contains updates relevant
+         * to the bubble bar. Only used when {@link BubbleController#isShowingAsBubbleBar()} is
+         * true.
+         */
+        BubbleBarUpdate toBubbleBarUpdate() {
+            BubbleBarUpdate bubbleBarUpdate = new BubbleBarUpdate();
+
+            bubbleBarUpdate.expandedChanged = expandedChanged;
+            bubbleBarUpdate.expanded = expanded;
+            if (selectionChanged) {
+                bubbleBarUpdate.selectedBubbleKey = selectedBubble != null
+                        ? selectedBubble.getKey()
+                        : null;
+            }
+            bubbleBarUpdate.addedBubble = addedBubble != null
+                    ? addedBubble.asBubbleBarBubble()
+                    : null;
+            // TODO(b/269670235): We need to handle updates better, I think for the bubble bar only
+            //  certain updates need to be sent instead of any updatedBubble.
+            bubbleBarUpdate.updatedBubble = updatedBubble != null
+                    ? updatedBubble.asBubbleBarBubble()
+                    : null;
+            bubbleBarUpdate.suppressedBubbleKey = suppressedBubble != null
+                    ? suppressedBubble.getKey()
+                    : null;
+            bubbleBarUpdate.unsupressedBubbleKey = unsuppressedBubble != null
+                    ? unsuppressedBubble.getKey()
+                    : null;
+            for (int i = 0; i < removedBubbles.size(); i++) {
+                Pair<Bubble, Integer> pair = removedBubbles.get(i);
+                bubbleBarUpdate.removedBubbles.add(
+                        new RemovedBubble(pair.first.getKey(), pair.second));
+            }
+            if (orderChanged) {
+                // Include the new order
+                for (int i = 0; i < bubbles.size(); i++) {
+                    bubbleBarUpdate.bubbleKeysInOrder.add(bubbles.get(i).getKey());
+                }
+            }
+            return bubbleBarUpdate;
+        }
+
+        /**
+         * Gets the current state of active bubbles and populates the update with that.  Only
+         * used when {@link BubbleController#isShowingAsBubbleBar()} is true.
+         */
+        BubbleBarUpdate getInitialState() {
+            BubbleBarUpdate bubbleBarUpdate = new BubbleBarUpdate();
+            for (int i = 0; i < bubbles.size(); i++) {
+                bubbleBarUpdate.currentBubbleList.add(bubbles.get(i).asBubbleBarBubble());
+            }
+            return bubbleBarUpdate;
+        }
     }
 
     /**
@@ -190,6 +247,13 @@
         mMaxOverflowBubbles = mContext.getResources().getInteger(R.integer.bubbles_max_overflow);
     }
 
+    /**
+     * Returns a bubble bar update populated with the current list of active bubbles.
+     */
+    public BubbleBarUpdate getInitialStateForBubbleBar() {
+        return mStateChange.getInitialState();
+    }
+
     public void setSuppressionChangedListener(Bubbles.BubbleMetadataFlagListener listener) {
         mBubbleMetadataFlagListener = listener;
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
new file mode 100644
index 0000000..2a31629
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
@@ -0,0 +1,282 @@
+/*
+ * 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.
+ */
+package com.android.wm.shell.bubbles;
+
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
+
+import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_EXPANDED_VIEW;
+
+import android.app.ActivityOptions;
+import android.app.ActivityTaskManager;
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+
+import com.android.wm.shell.TaskView;
+import com.android.wm.shell.TaskViewTaskController;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+
+/**
+ * Handles creating and updating the {@link TaskView} associated with a {@link Bubble}.
+ */
+public class BubbleTaskViewHelper {
+
+    private static final String TAG = BubbleTaskViewHelper.class.getSimpleName();
+
+    /**
+     * Listener for users of {@link BubbleTaskViewHelper} to use to be notified of events
+     * on the task.
+     */
+    public interface Listener {
+
+        /** Called when the task is first created. */
+        void onTaskCreated();
+
+        /** Called when the visibility of the task changes. */
+        void onContentVisibilityChanged(boolean visible);
+
+        /** Called when back is pressed on the task root. */
+        void onBackPressed();
+    }
+
+    private final Context mContext;
+    private final BubbleController mController;
+    private final @ShellMainThread ShellExecutor mMainExecutor;
+    private final BubbleTaskViewHelper.Listener mListener;
+    private final View mParentView;
+
+    @Nullable
+    private Bubble mBubble;
+    @Nullable
+    private PendingIntent mPendingIntent;
+    private TaskViewTaskController mTaskViewTaskController;
+    @Nullable
+    private TaskView mTaskView;
+    private int mTaskId = INVALID_TASK_ID;
+
+    private final TaskView.Listener mTaskViewListener = new TaskView.Listener() {
+        private boolean mInitialized = false;
+        private boolean mDestroyed = false;
+
+        @Override
+        public void onInitialized() {
+            if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+                Log.d(TAG, "onInitialized: destroyed=" + mDestroyed
+                        + " initialized=" + mInitialized
+                        + " bubble=" + getBubbleKey());
+            }
+
+            if (mDestroyed || mInitialized) {
+                return;
+            }
+
+            // Custom options so there is no activity transition animation
+            ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext,
+                    0 /* enterResId */, 0 /* exitResId */);
+
+            Rect launchBounds = new Rect();
+            mTaskView.getBoundsOnScreen(launchBounds);
+
+            // TODO: I notice inconsistencies in lifecycle
+            // Post to keep the lifecycle normal
+            mParentView.post(() -> {
+                if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+                    Log.d(TAG, "onInitialized: calling startActivity, bubble="
+                            + getBubbleKey());
+                }
+                try {
+                    options.setTaskAlwaysOnTop(true);
+                    options.setLaunchedFromBubble(true);
+
+                    Intent fillInIntent = new Intent();
+                    // Apply flags to make behaviour match documentLaunchMode=always.
+                    fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);
+                    fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+
+                    if (mBubble.isAppBubble()) {
+                        PendingIntent pi = PendingIntent.getActivity(mContext, 0,
+                                mBubble.getAppBubbleIntent(),
+                                PendingIntent.FLAG_MUTABLE,
+                                null);
+                        mTaskView.startActivity(pi, fillInIntent, options, launchBounds);
+                    } else if (mBubble.hasMetadataShortcutId()) {
+                        options.setApplyActivityFlagsForBubbles(true);
+                        mTaskView.startShortcutActivity(mBubble.getShortcutInfo(),
+                                options, launchBounds);
+                    } else {
+                        if (mBubble != null) {
+                            mBubble.setIntentActive();
+                        }
+                        mTaskView.startActivity(mPendingIntent, fillInIntent, options,
+                                launchBounds);
+                    }
+                } catch (RuntimeException e) {
+                    // If there's a runtime exception here then there's something
+                    // wrong with the intent, we can't really recover / try to populate
+                    // the bubble again so we'll just remove it.
+                    Log.w(TAG, "Exception while displaying bubble: " + getBubbleKey()
+                            + ", " + e.getMessage() + "; removing bubble");
+                    mController.removeBubble(getBubbleKey(), Bubbles.DISMISS_INVALID_INTENT);
+                }
+                mInitialized = true;
+            });
+        }
+
+        @Override
+        public void onReleased() {
+            mDestroyed = true;
+        }
+
+        @Override
+        public void onTaskCreated(int taskId, ComponentName name) {
+            if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+                Log.d(TAG, "onTaskCreated: taskId=" + taskId
+                        + " bubble=" + getBubbleKey());
+            }
+            // The taskId is saved to use for removeTask, preventing appearance in recent tasks.
+            mTaskId = taskId;
+
+            // With the task org, the taskAppeared callback will only happen once the task has
+            // already drawn
+            mListener.onTaskCreated();
+        }
+
+        @Override
+        public void onTaskVisibilityChanged(int taskId, boolean visible) {
+            mListener.onContentVisibilityChanged(visible);
+        }
+
+        @Override
+        public void onTaskRemovalStarted(int taskId) {
+            if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+                Log.d(TAG, "onTaskRemovalStarted: taskId=" + taskId
+                        + " bubble=" + getBubbleKey());
+            }
+            if (mBubble != null) {
+                mController.removeBubble(mBubble.getKey(), Bubbles.DISMISS_TASK_FINISHED);
+            }
+        }
+
+        @Override
+        public void onBackPressedOnTaskRoot(int taskId) {
+            if (mTaskId == taskId && mController.isStackExpanded()) {
+                mListener.onBackPressed();
+            }
+        }
+    };
+
+    public BubbleTaskViewHelper(Context context,
+            BubbleController controller,
+            BubbleTaskViewHelper.Listener listener,
+            View parent) {
+        mContext = context;
+        mController = controller;
+        mMainExecutor = mController.getMainExecutor();
+        mListener = listener;
+        mParentView = parent;
+        mTaskViewTaskController = new TaskViewTaskController(mContext,
+                mController.getTaskOrganizer(),
+                mController.getTaskViewTransitions(), mController.getSyncTransactionQueue());
+        mTaskView = new TaskView(mContext, mTaskViewTaskController);
+        mTaskView.setListener(mMainExecutor, mTaskViewListener);
+    }
+
+    /**
+     * Sets the bubble or updates the bubble used to populate the view.
+     *
+     * @return true if the bubble is new, false if it was an update to the same bubble.
+     */
+    public boolean update(Bubble bubble) {
+        boolean isNew = mBubble == null || didBackingContentChange(bubble);
+        mBubble = bubble;
+        if (isNew) {
+            mPendingIntent = mBubble.getBubbleIntent();
+            return true;
+        }
+        return false;
+    }
+
+    /** Cleans up anything related to the task and {@code TaskView}. */
+    public void cleanUpTaskView() {
+        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+            Log.d(TAG, "cleanUpExpandedState: bubble=" + getBubbleKey() + " task=" + mTaskId);
+        }
+        if (mTaskId != INVALID_TASK_ID) {
+            try {
+                ActivityTaskManager.getService().removeTask(mTaskId);
+            } catch (RemoteException e) {
+                Log.w(TAG, e.getMessage());
+            }
+        }
+        if (mTaskView != null) {
+            mTaskView.release();
+            mTaskView = null;
+        }
+    }
+
+    /** Returns the bubble key associated with this view. */
+    @Nullable
+    public String getBubbleKey() {
+        return mBubble != null ? mBubble.getKey() : null;
+    }
+
+    /** Returns the TaskView associated with this view. */
+    @Nullable
+    public TaskView getTaskView() {
+        return mTaskView;
+    }
+
+    /**
+     * Returns the task id associated with the task in this view. If the task doesn't exist then
+     * {@link ActivityTaskManager#INVALID_TASK_ID}.
+     */
+    public int getTaskId() {
+        return mTaskId;
+    }
+
+    /** Returns whether the bubble set on the helper is valid to populate the task view. */
+    public boolean isValidBubble() {
+        return mBubble != null && (mPendingIntent != null || mBubble.hasMetadataShortcutId());
+    }
+
+    // TODO (b/274980695): Is this still relevant?
+    /**
+     * Bubbles are backed by a pending intent or a shortcut, once the activity is
+     * started we never change it / restart it on notification updates -- unless the bubble's
+     * backing data switches.
+     *
+     * This indicates if the new bubble is backed by a different data source than what was
+     * previously shown here (e.g. previously a pending intent & now a shortcut).
+     *
+     * @param newBubble the bubble this view is being updated with.
+     * @return true if the backing content has changed.
+     */
+    private boolean didBackingContentChange(Bubble newBubble) {
+        boolean prevWasIntentBased = mBubble != null && mPendingIntent != null;
+        boolean newIsIntentBased = newBubble.getBubbleIntent() != null;
+        return prevWasIntentBased != newIsIntentBased;
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 876a720..259f692 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -39,6 +39,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
@@ -81,6 +82,11 @@
     int DISMISS_RELOAD_FROM_DISK = 15;
     int DISMISS_USER_REMOVED = 16;
 
+    /** Returns a binder that can be passed to an external process to manipulate Bubbles. */
+    default IBubbles createExternalInterface() {
+        return null;
+    }
+
     /**
      * @return {@code true} if there is a bubble associated with the provided key and if its
      * notification is hidden from the shade or there is a group summary associated with the
@@ -277,6 +283,17 @@
      */
     void onUserRemoved(int removedUserId);
 
+    /**
+     * A listener to be notified of bubble state changes, used by launcher to render bubbles in
+     * its process.
+     */
+    interface BubbleStateListener {
+        /**
+         * Called when the bubbles state changes.
+         */
+        void onBubbleStateChange(BubbleBarUpdate update);
+    }
+
     /** Listener to find out about stack expansion / collapse events. */
     interface BubbleExpandListener {
         /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
new file mode 100644
index 0000000..862e818
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+package com.android.wm.shell.bubbles;
+
+import android.content.Intent;
+import com.android.wm.shell.bubbles.IBubblesListener;
+
+/**
+ * Interface that is exposed to remote callers (launcher) to manipulate the bubbles feature when
+ * showing in the bubble bar.
+ */
+interface IBubbles {
+
+    oneway void registerBubbleListener(in IBubblesListener listener) = 1;
+
+    oneway void unregisterBubbleListener(in IBubblesListener listener) = 2;
+
+    oneway void showBubble(in String key, in boolean onLauncherHome) = 3;
+
+    oneway void removeBubble(in String key, in int reason) = 4;
+
+    oneway void collapseBubbles() = 5;
+
+    oneway void onTaskbarStateChanged(in int newState) = 6;
+
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl
new file mode 100644
index 0000000..e48f8d5
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+package com.android.wm.shell.bubbles;
+import android.os.Bundle;
+
+/**
+ * Listener interface that Launcher attaches to SystemUI to get bubbles callbacks.
+ */
+oneway interface IBubblesListener {
+
+    /**
+     * Called when the bubbles state changes.
+     */
+    void onBubbleStateChange(in Bundle update);
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java
new file mode 100644
index 0000000..8142347
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java
@@ -0,0 +1,137 @@
+/*
+ * 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.
+ */
+
+package com.android.wm.shell.common.bubbles;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents an update to bubbles state. This is passed through
+ * {@link com.android.wm.shell.bubbles.IBubblesListener} to launcher so that taskbar may render
+ * bubbles. This should be kept this as minimal as possible in terms of data.
+ */
+public class BubbleBarUpdate implements Parcelable {
+
+    public static final String BUNDLE_KEY = "update";
+
+    public boolean expandedChanged;
+    public boolean expanded;
+    @Nullable
+    public String selectedBubbleKey;
+    @Nullable
+    public BubbleInfo addedBubble;
+    @Nullable
+    public BubbleInfo updatedBubble;
+    @Nullable
+    public String suppressedBubbleKey;
+    @Nullable
+    public String unsupressedBubbleKey;
+
+    // This is only populated if bubbles have been removed.
+    public List<RemovedBubble> removedBubbles = new ArrayList<>();
+
+    // This is only populated if the order of the bubbles has changed.
+    public List<String> bubbleKeysInOrder = new ArrayList<>();
+
+    // This is only populated the first time a listener is connected so it gets the current state.
+    public List<BubbleInfo> currentBubbleList = new ArrayList<>();
+
+    public BubbleBarUpdate() {
+    }
+
+    public BubbleBarUpdate(Parcel parcel) {
+        expandedChanged = parcel.readBoolean();
+        expanded = parcel.readBoolean();
+        selectedBubbleKey = parcel.readString();
+        addedBubble = parcel.readParcelable(BubbleInfo.class.getClassLoader(),
+                BubbleInfo.class);
+        updatedBubble = parcel.readParcelable(BubbleInfo.class.getClassLoader(),
+                BubbleInfo.class);
+        suppressedBubbleKey = parcel.readString();
+        unsupressedBubbleKey = parcel.readString();
+        removedBubbles = parcel.readParcelableList(new ArrayList<>(),
+                RemovedBubble.class.getClassLoader());
+        parcel.readStringList(bubbleKeysInOrder);
+        currentBubbleList = parcel.readParcelableList(new ArrayList<>(),
+                BubbleInfo.class.getClassLoader());
+    }
+
+    /**
+     * Returns whether anything has changed in this update.
+     */
+    public boolean anythingChanged() {
+        return expandedChanged
+                || selectedBubbleKey != null
+                || addedBubble != null
+                || updatedBubble != null
+                || !removedBubbles.isEmpty()
+                || !bubbleKeysInOrder.isEmpty()
+                || suppressedBubbleKey != null
+                || unsupressedBubbleKey != null
+                || !currentBubbleList.isEmpty();
+    }
+
+    @Override
+    public String toString() {
+        return "BubbleBarUpdate{ expandedChanged=" + expandedChanged
+                + " expanded=" + expanded
+                + " selectedBubbleKey=" + selectedBubbleKey
+                + " addedBubble=" + addedBubble
+                + " updatedBubble=" + updatedBubble
+                + " suppressedBubbleKey=" + suppressedBubbleKey
+                + " unsuppressedBubbleKey=" + unsupressedBubbleKey
+                + " removedBubbles=" + removedBubbles
+                + " bubbles=" + bubbleKeysInOrder
+                + " currentBubbleList=" + currentBubbleList
+                + " }";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeBoolean(expandedChanged);
+        parcel.writeBoolean(expanded);
+        parcel.writeString(selectedBubbleKey);
+        parcel.writeParcelable(addedBubble, flags);
+        parcel.writeParcelable(updatedBubble, flags);
+        parcel.writeString(suppressedBubbleKey);
+        parcel.writeString(unsupressedBubbleKey);
+        parcel.writeParcelableList(removedBubbles, flags);
+        parcel.writeStringList(bubbleKeysInOrder);
+        parcel.writeParcelableList(currentBubbleList, flags);
+    }
+
+    @NonNull
+    public static final Creator<BubbleBarUpdate> CREATOR =
+            new Creator<BubbleBarUpdate>() {
+                public BubbleBarUpdate createFromParcel(Parcel source) {
+                    return new BubbleBarUpdate(source);
+                }
+                public BubbleBarUpdate[] newArray(int size) {
+                    return new BubbleBarUpdate[size];
+                }
+            };
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java
new file mode 100644
index 0000000..b0dea72
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java
@@ -0,0 +1,154 @@
+/*
+ * 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.
+ */
+
+package com.android.wm.shell.common.bubbles;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Notification;
+import android.graphics.drawable.Icon;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Contains information necessary to present a bubble.
+ */
+public class BubbleInfo implements Parcelable {
+
+    // TODO(b/269672147): needs a title string for a11y & that comes from notification
+    // TODO(b/269671451): needs whether the bubble is an 'important person' or not
+
+    private String mKey; // Same key as the Notification
+    private int mFlags;  // Flags from BubbleMetadata
+    private String mShortcutId;
+    private int mUserId;
+    private String mPackageName;
+    /**
+     * All notification bubbles require a shortcut to be set on the notification, however, the
+     * app could still specify an Icon and PendingIntent to use for the bubble. In that case
+     * this icon will be populated. If the bubble is entirely shortcut based, this will be null.
+     */
+    @Nullable
+    private Icon mIcon;
+
+    public BubbleInfo(String key, int flags, String shortcutId, @Nullable Icon icon,
+            int userId, String packageName) {
+        mKey = key;
+        mFlags = flags;
+        mShortcutId = shortcutId;
+        mIcon = icon;
+        mUserId = userId;
+        mPackageName = packageName;
+    }
+
+    public BubbleInfo(Parcel source) {
+        mKey = source.readString();
+        mFlags = source.readInt();
+        mShortcutId = source.readString();
+        mIcon = source.readTypedObject(Icon.CREATOR);
+        mUserId = source.readInt();
+        mPackageName = source.readString();
+    }
+
+    public String getKey() {
+        return mKey;
+    }
+
+    public String getShortcutId() {
+        return mShortcutId;
+    }
+
+    public Icon getIcon() {
+        return mIcon;
+    }
+
+    public int getFlags() {
+        return mFlags;
+    }
+
+    public int getUserId() {
+        return mUserId;
+    }
+
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    /**
+     * Whether this bubble is currently being hidden from the stack.
+     */
+    public boolean isBubbleSuppressed() {
+        return (mFlags & Notification.BubbleMetadata.FLAG_SUPPRESS_BUBBLE) != 0;
+    }
+
+    /**
+     * Whether this bubble is able to be suppressed (i.e. has the developer opted into the API
+     * to
+     * hide the bubble when in the same content).
+     */
+    public boolean isBubbleSuppressable() {
+        return (mFlags & Notification.BubbleMetadata.FLAG_SUPPRESSABLE_BUBBLE) != 0;
+    }
+
+    /**
+     * Whether the notification for this bubble is hidden from the shade.
+     */
+    public boolean isNotificationSuppressed() {
+        return (mFlags & Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION) != 0;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof BubbleInfo)) return false;
+        BubbleInfo bubble = (BubbleInfo) o;
+        return Objects.equals(mKey, bubble.mKey);
+    }
+
+    @Override
+    public int hashCode() {
+        return mKey.hashCode();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeString(mKey);
+        parcel.writeInt(mFlags);
+        parcel.writeString(mShortcutId);
+        parcel.writeTypedObject(mIcon, flags);
+        parcel.writeInt(mUserId);
+        parcel.writeString(mPackageName);
+    }
+
+    @NonNull
+    public static final Creator<BubbleInfo> CREATOR =
+            new Creator<BubbleInfo>() {
+                public BubbleInfo createFromParcel(Parcel source) {
+                    return new BubbleInfo(source);
+                }
+
+                public BubbleInfo[] newArray(int size) {
+                    return new BubbleInfo[size];
+                }
+            };
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RemovedBubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RemovedBubble.java
new file mode 100644
index 0000000..f90591b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RemovedBubble.java
@@ -0,0 +1,70 @@
+/*
+ * 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.
+ */
+
+package com.android.wm.shell.common.bubbles;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Represents a removed bubble, defining the key and reason the bubble was removed.
+ */
+public class RemovedBubble implements Parcelable {
+
+    private final String mKey;
+    private final int mRemovalReason;
+
+    public RemovedBubble(String key, int removalReason) {
+        mKey = key;
+        mRemovalReason = removalReason;
+    }
+
+    public RemovedBubble(Parcel parcel) {
+        mKey = parcel.readString();
+        mRemovalReason = parcel.readInt();
+    }
+
+    public String getKey() {
+        return mKey;
+    }
+
+    public int getRemovalReason() {
+        return mRemovalReason;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mKey);
+        dest.writeInt(mRemovalReason);
+    }
+
+    @NonNull
+    public static final Creator<RemovedBubble> CREATOR =
+            new Creator<RemovedBubble>() {
+                public RemovedBubble createFromParcel(Parcel source) {
+                    return new RemovedBubble(source);
+                }
+                public RemovedBubble[] newArray(int size) {
+                    return new RemovedBubble[size];
+                }
+            };
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index c5fc879..f2f30ea 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -1176,20 +1176,7 @@
 
         final Rect newDestinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
         if (newDestinationBounds.equals(currentDestinationBounds)) return;
-        if (animator.getAnimationType() == ANIM_TYPE_BOUNDS) {
-            if (mWaitForFixedRotation) {
-                // The new destination bounds are in next rotation (DisplayLayout has been rotated
-                // in computeRotatedBounds). The animation runs in previous rotation so the end
-                // bounds need to be transformed.
-                final Rect displayBounds = mPipBoundsState.getDisplayBounds();
-                final Rect rotatedEndBounds = new Rect(newDestinationBounds);
-                rotateBounds(rotatedEndBounds, displayBounds, mNextRotation, mCurrentRotation);
-                animator.updateEndValue(rotatedEndBounds);
-            } else {
-                animator.updateEndValue(newDestinationBounds);
-            }
-        }
-        animator.setDestinationBounds(newDestinationBounds);
+        updateAnimatorBounds(newDestinationBounds);
         destinationBoundsOut.set(newDestinationBounds);
     }
 
@@ -1201,7 +1188,17 @@
                 mPipAnimationController.getCurrentAnimator();
         if (animator != null && animator.isRunning()) {
             if (animator.getAnimationType() == ANIM_TYPE_BOUNDS) {
-                animator.updateEndValue(bounds);
+                if (mWaitForFixedRotation) {
+                    // The new destination bounds are in next rotation (DisplayLayout has been
+                    // rotated in computeRotatedBounds). The animation runs in previous rotation so
+                    // the end bounds need to be transformed.
+                    final Rect displayBounds = mPipBoundsState.getDisplayBounds();
+                    final Rect rotatedEndBounds = new Rect(bounds);
+                    rotateBounds(rotatedEndBounds, displayBounds, mNextRotation, mCurrentRotation);
+                    animator.updateEndValue(rotatedEndBounds);
+                } else {
+                    animator.updateEndValue(bounds);
+                }
             }
             animator.setDestinationBounds(bounds);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
index 73123b1..2c6ca1af 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
@@ -18,7 +18,7 @@
 
 import static android.view.WindowManager.SHELL_ROOT_LAYER_PIP;
 
-import android.app.ActivityManager;
+import android.annotation.IntDef;
 import android.app.RemoteAction;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -36,6 +36,7 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.R;
 import com.android.wm.shell.common.SystemWindows;
@@ -61,13 +62,36 @@
     private SurfaceControl mLeash;
     private TvPipMenuView mPipMenuView;
     private View mPipBackgroundView;
+    private boolean mMenuIsFocused;
 
-    private boolean mMenuIsOpen;
-    // User can actively move the PiP via the DPAD.
-    private boolean mInMoveMode;
-    // Used when only showing the move menu since we want to close the menu completely when
-    // exiting the move menu instead of showing the regular button menu.
-    private boolean mCloseAfterExitMoveMenu;
+    @TvPipMenuMode
+    private int mCurrentMenuMode = MODE_NO_MENU;
+    @TvPipMenuMode
+    private int mPrevMenuMode = MODE_NO_MENU;
+
+    @IntDef(prefix = { "MODE_" }, value = {
+        MODE_NO_MENU,
+        MODE_MOVE_MENU,
+        MODE_ALL_ACTIONS_MENU,
+    })
+    public @interface TvPipMenuMode {}
+
+    /**
+     * In this mode the PiP menu is not focused and no user controls are displayed.
+     */
+    static final int MODE_NO_MENU = 0;
+
+    /**
+     * In this mode the PiP menu is focused and the user can use the DPAD controls to move the PiP
+     * to a different position on the screen. We draw arrows in all possible movement directions.
+     */
+    static final int MODE_MOVE_MENU = 1;
+
+    /**
+     * In this mode the PiP menu is focused and we display an array of actions that the user can
+     * select. See {@link TvPipActionsProvider} for the types of available actions.
+     */
+    static final int MODE_ALL_ACTIONS_MENU = 2;
 
     public TvPipMenuController(Context context, TvPipBoundsState tvPipBoundsState,
             SystemWindows systemWindows, Handler mainHandler) {
@@ -143,11 +167,16 @@
                     "%s: Actions provider is not set", TAG);
             return;
         }
-        mPipMenuView = new TvPipMenuView(mContext, mMainHandler, this, mTvPipActionsProvider);
+        mPipMenuView = createTvPipMenuView();
         setUpViewSurfaceZOrder(mPipMenuView, 1);
         addPipMenuViewToSystemWindows(mPipMenuView, MENU_WINDOW_TITLE);
     }
 
+    @VisibleForTesting
+    TvPipMenuView createTvPipMenuView() {
+        return new TvPipMenuView(mContext, mMainHandler, this, mTvPipActionsProvider);
+    }
+
     private void attachPipBackgroundView() {
         mPipBackgroundView = LayoutInflater.from(mContext)
                 .inflate(R.layout.tv_pip_menu_background, null);
@@ -188,37 +217,14 @@
 
     void showMovementMenu() {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                "%s: showMovementMenuOnly()", TAG);
-        setInMoveMode(true);
-        if (mMenuIsOpen) {
-            mPipMenuView.showMoveMenu(mTvPipBoundsState.getTvPipGravity());
-        } else {
-            mCloseAfterExitMoveMenu = true;
-            showMenuInternal();
-        }
+                "%s: showMovementMenu()", TAG);
+        switchToMenuMode(MODE_MOVE_MENU);
     }
 
     @Override
     public void showMenu() {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMenu()", TAG);
-        setInMoveMode(false);
-        mCloseAfterExitMoveMenu = false;
-        showMenuInternal();
-    }
-
-    private void showMenuInternal() {
-        if (mPipMenuView == null) {
-            return;
-        }
-
-        mMenuIsOpen = true;
-        grantPipMenuFocus(true);
-        if (mInMoveMode) {
-            mPipMenuView.showMoveMenu(mTvPipBoundsState.getTvPipGravity());
-        } else {
-            mPipMenuView.showButtonsMenu(/* exitingMoveMode= */ false);
-        }
-        mPipMenuView.updateBounds(mTvPipBoundsState.getBounds());
+        switchToMenuMode(MODE_ALL_ACTIONS_MENU, true);
     }
 
     void onPipTransitionToTargetBoundsStarted(Rect targetBounds) {
@@ -228,9 +234,7 @@
     }
 
     void updateGravity(int gravity) {
-        if (mInMoveMode) {
-            mPipMenuView.showMovementHints(gravity);
-        }
+        mPipMenuView.setPipGravity(gravity);
     }
 
     private Rect calculateMenuSurfaceBounds(Rect pipBounds) {
@@ -240,58 +244,7 @@
     void closeMenu() {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "%s: closeMenu()", TAG);
-
-        if (mPipMenuView == null) {
-            return;
-        }
-
-        mMenuIsOpen = false;
-        mPipMenuView.hideAllUserControls();
-        grantPipMenuFocus(false);
-        mDelegate.onMenuClosed();
-    }
-
-    boolean isInMoveMode() {
-        return mInMoveMode;
-    }
-
-    private void setInMoveMode(boolean moveMode) {
-        if (mInMoveMode == moveMode) {
-            return;
-        }
-        mInMoveMode = moveMode;
-        if (mDelegate != null) {
-            mDelegate.onInMoveModeChanged();
-        }
-    }
-
-    @Override
-    public boolean onExitMoveMode() {
-        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                "%s: onExitMoveMode - %b, close when exiting move menu: %b",
-                TAG, mInMoveMode, mCloseAfterExitMoveMenu);
-
-        if (mInMoveMode) {
-            setInMoveMode(false);
-            if (mCloseAfterExitMoveMenu) {
-                mCloseAfterExitMoveMenu = false;
-                closeMenu();
-            } else {
-                mPipMenuView.showButtonsMenu(/* exitingMoveMode= */ true);
-            }
-            return true;
-        }
-        return false;
-    }
-
-    @Override
-    public boolean onPipMovement(int keycode) {
-        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                "%s: onPipMovement - %b", TAG, mInMoveMode);
-        if (mInMoveMode) {
-            mDelegate.movePip(keycode);
-        }
-        return mInMoveMode;
+        switchToMenuMode(MODE_NO_MENU);
     }
 
     @Override
@@ -422,13 +375,90 @@
                 getPipMenuLayoutParams(mContext, MENU_WINDOW_TITLE, menuBounds.width(),
                         menuBounds.height()));
         if (mPipMenuView != null) {
-            mPipMenuView.updateBounds(pipBounds);
+            mPipMenuView.setPipBounds(pipBounds);
         }
     }
 
+    // Start methods handling {@link TvPipMenuMode}
+
+    @VisibleForTesting
+    boolean isMenuOpen() {
+        return mCurrentMenuMode != MODE_NO_MENU;
+    }
+
+    @VisibleForTesting
+    boolean isInMoveMode() {
+        return mCurrentMenuMode == MODE_MOVE_MENU;
+    }
+
+    @VisibleForTesting
+    boolean isInAllActionsMode() {
+        return mCurrentMenuMode == MODE_ALL_ACTIONS_MENU;
+    }
+
+    private void switchToMenuMode(@TvPipMenuMode int menuMode) {
+        switchToMenuMode(menuMode, false);
+    }
+
+    private void switchToMenuMode(@TvPipMenuMode int menuMode, boolean resetMenu) {
+        // Note: we intentionally don't return early here, because the TvPipMenuView needs to
+        // refresh the Ui even if there is no menu mode change.
+        mPrevMenuMode = mCurrentMenuMode;
+        mCurrentMenuMode = menuMode;
+
+        ProtoLog.i(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                "%s: switchToMenuMode: setting mCurrentMenuMode=%s, mPrevMenuMode=%s", TAG,
+                getMenuModeString(), getMenuModeString(mPrevMenuMode));
+
+        updateUiOnNewMenuModeRequest(resetMenu);
+        updateDelegateOnNewMenuModeRequest();
+    }
+
+    private void updateUiOnNewMenuModeRequest(boolean resetMenu) {
+        if (mPipMenuView == null) return;
+
+        mPipMenuView.setPipGravity(mTvPipBoundsState.getTvPipGravity());
+        mPipMenuView.transitionToMenuMode(mCurrentMenuMode, resetMenu);
+        grantPipMenuFocus(mCurrentMenuMode != MODE_NO_MENU);
+    }
+
+    private void updateDelegateOnNewMenuModeRequest() {
+        if (mPrevMenuMode == mCurrentMenuMode) return;
+        if (mDelegate == null) return;
+
+        if (mPrevMenuMode == MODE_MOVE_MENU || isInMoveMode()) {
+            mDelegate.onInMoveModeChanged();
+        }
+
+        if (mCurrentMenuMode == MODE_NO_MENU) {
+            mDelegate.onMenuClosed();
+        }
+    }
+
+    @VisibleForTesting
+    String getMenuModeString() {
+        return getMenuModeString(mCurrentMenuMode);
+    }
+
+    static String getMenuModeString(@TvPipMenuMode int menuMode) {
+        switch(menuMode) {
+            case MODE_NO_MENU:
+                return "MODE_NO_MENU";
+            case MODE_MOVE_MENU:
+                return "MODE_MOVE_MENU";
+            case MODE_ALL_ACTIONS_MENU:
+                return "MODE_ALL_ACTIONS_MENU";
+            default:
+                return "Unknown";
+        }
+    }
+
+    // Start {@link TvPipMenuView.Delegate} methods
+
     @Override
-    public void onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo) {
-        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: onFocusTaskChanged", TAG);
+    public void onCloseEduText() {
+        mTvPipBoundsState.setPipMenuTemporaryDecorInsets(Insets.NONE);
+        mDelegate.closeEduText();
     }
 
     @Override
@@ -439,9 +469,35 @@
     }
 
     @Override
-    public void onCloseEduText() {
-        mTvPipBoundsState.setPipMenuTemporaryDecorInsets(Insets.NONE);
-        mDelegate.closeEduText();
+    public boolean onExitMoveMode() {
+        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                "%s: onExitMoveMode - mCurrentMenuMode=%s", TAG, getMenuModeString());
+
+        final int saveMenuMode = mCurrentMenuMode;
+        if (isInMoveMode()) {
+            switchToMenuMode(mPrevMenuMode);
+        }
+        return saveMenuMode == MODE_MOVE_MENU;
+    }
+
+    @Override
+    public boolean onPipMovement(int keycode) {
+        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                "%s: onPipMovement - mCurrentMenuMode=%s", TAG, getMenuModeString());
+        if (isInMoveMode()) {
+            mDelegate.movePip(keycode);
+        }
+        return isInMoveMode();
+    }
+
+    @Override
+    public void onPipWindowFocusChanged(boolean focused) {
+        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                "%s: onPipWindowFocusChanged - focused=%b", TAG, focused);
+        mMenuIsFocused = focused;
+        if (!focused && isMenuOpen()) {
+            closeMenu();
+        }
     }
 
     interface Delegate {
@@ -455,6 +511,8 @@
     }
 
     private void grantPipMenuFocus(boolean grantFocus) {
+        if (mMenuIsFocused == grantFocus) return;
+
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "%s: grantWindowFocus(%b)", TAG, grantFocus);
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
index 56c602a..ccf65c2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
@@ -26,6 +26,9 @@
 import static android.view.KeyEvent.KEYCODE_ENTER;
 
 import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_MOVE;
+import static com.android.wm.shell.pip.tv.TvPipMenuController.MODE_ALL_ACTIONS_MENU;
+import static com.android.wm.shell.pip.tv.TvPipMenuController.MODE_MOVE_MENU;
+import static com.android.wm.shell.pip.tv.TvPipMenuController.MODE_NO_MENU;
 
 import android.content.Context;
 import android.graphics.Rect;
@@ -86,9 +89,9 @@
     private final ImageView mArrowLeft;
     private final TvWindowMenuActionButton mA11yDoneButton;
 
-    private Rect mCurrentPipBounds;
-    private boolean mMoveMenuIsVisible;
-    private boolean mButtonMenuIsVisible;
+    private @TvPipMenuController.TvPipMenuMode int mCurrentMenuMode = MODE_NO_MENU;
+    private final Rect mCurrentPipBounds = new Rect();
+    private int mCurrentPipGravity;
     private boolean mSwitchingOrientation;
 
     private final AccessibilityManager mA11yManager;
@@ -172,7 +175,7 @@
             return;
         }
 
-        if (mButtonMenuIsVisible) {
+        if (mCurrentMenuMode == MODE_ALL_ACTIONS_MENU) {
             mSwitchingOrientation = true;
             mActionButtonsRecyclerView.animate()
                     .alpha(0)
@@ -217,19 +220,14 @@
     /**
      * Also updates the button gravity.
      */
-    void updateBounds(Rect updatedBounds) {
+    void setPipBounds(Rect updatedPipBounds) {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                "%s: updateLayout, width: %s, height: %s", TAG, updatedBounds.width(),
-                updatedBounds.height());
-        mCurrentPipBounds = updatedBounds;
-        updatePipFrameBounds();
-    }
+                "%s: updateLayout, width: %s, height: %s", TAG, updatedPipBounds.width(),
+                updatedPipBounds.height());
+        if (updatedPipBounds.equals(mCurrentPipBounds)) return;
 
-    Rect getPipMenuContainerBounds(Rect pipBounds) {
-        final Rect menuUiBounds = new Rect(pipBounds);
-        menuUiBounds.inset(-mPipMenuOuterSpace, -mPipMenuOuterSpace);
-        menuUiBounds.bottom += mEduTextDrawer.getHeight();
-        return menuUiBounds;
+        mCurrentPipBounds.set(updatedPipBounds);
+        updatePipFrameBounds();
     }
 
     /**
@@ -264,12 +262,39 @@
         }
     }
 
-    /**
-     * @param gravity for the arrow hints
-     */
-    void showMoveMenu(int gravity) {
+    Rect getPipMenuContainerBounds(Rect pipBounds) {
+        final Rect menuUiBounds = new Rect(pipBounds);
+        menuUiBounds.inset(-mPipMenuOuterSpace, -mPipMenuOuterSpace);
+        menuUiBounds.bottom += mEduTextDrawer.getHeight();
+        return menuUiBounds;
+    }
+
+    void transitionToMenuMode(int menuMode, boolean resetMenu) {
+        switch (menuMode) {
+            case MODE_NO_MENU:
+                hideAllUserControls();
+                break;
+            case MODE_MOVE_MENU:
+                showMoveMenu();
+                break;
+            case MODE_ALL_ACTIONS_MENU:
+                showAllActionsMenu(resetMenu);
+                break;
+            default:
+                throw new IllegalArgumentException(
+                        "Unknown TV PiP menu mode: "
+                        + TvPipMenuController.getMenuModeString(mCurrentMenuMode));
+        }
+
+        mCurrentMenuMode = menuMode;
+    }
+
+    private void showMoveMenu() {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMoveMenu()", TAG);
-        showMovementHints(gravity);
+
+        if (mCurrentMenuMode == MODE_MOVE_MENU) return;
+
+        showMovementHints();
         setMenuButtonsVisible(false);
         setFrameHighlighted(true);
 
@@ -278,32 +303,38 @@
         mEduTextDrawer.closeIfNeeded();
     }
 
-
-    void showButtonsMenu(boolean exitingMoveMode) {
+    private void showAllActionsMenu(boolean resetMenu) {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                "%s: showButtonsMenu(), exitingMoveMode %b", TAG, exitingMoveMode);
+                "%s: showAllActionsMenu(), resetMenu %b", TAG, resetMenu);
+
+        if (resetMenu) {
+            scrollToFirstAction();
+        }
+
+        if (mCurrentMenuMode == MODE_ALL_ACTIONS_MENU) return;
+
         setMenuButtonsVisible(true);
         hideMovementHints();
         setFrameHighlighted(true);
         animateAlphaTo(1f, mDimLayer);
         mEduTextDrawer.closeIfNeeded();
 
-        if (exitingMoveMode) {
-            scrollAndRefocusButton(mTvPipActionsProvider.getFirstIndexOfAction(ACTION_MOVE),
-                    /* alwaysScroll= */ false);
-        } else {
-            scrollAndRefocusButton(0, /* alwaysScroll= */ true);
+        if (mCurrentMenuMode == MODE_MOVE_MENU) {
+            refocusButton(mTvPipActionsProvider.getFirstIndexOfAction(ACTION_MOVE));
         }
+
     }
 
-    private void scrollAndRefocusButton(int position, boolean alwaysScroll) {
-        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                "%s: scrollAndRefocusButton, target: %d", TAG, position);
-
-        if (alwaysScroll || !refocusButton(position)) {
-            mButtonLayoutManager.scrollToPositionWithOffset(position, 0);
-            mActionButtonsRecyclerView.post(() -> refocusButton(position));
+    private void scrollToFirstAction() {
+        // Clearing the focus here is necessary to allow a smooth scroll even if the first action
+        // is currently not visible.
+        final View focusedChild = mActionButtonsRecyclerView.getFocusedChild();
+        if (focusedChild != null) {
+            focusedChild.clearFocus();
         }
+
+        mButtonLayoutManager.scrollToPosition(0);
+        mActionButtonsRecyclerView.post(() -> refocusButton(0));
     }
 
     /**
@@ -311,6 +342,9 @@
      * the view for the position not being available (scrolling beforehand will be necessary).
      */
     private boolean refocusButton(int position) {
+        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                "%s: refocusButton, position: %d", TAG, position);
+
         View itemToFocus = mButtonLayoutManager.findViewByPosition(position);
         if (itemToFocus != null) {
             itemToFocus.requestFocus();
@@ -319,21 +353,29 @@
         return itemToFocus != null;
     }
 
-    void hideAllUserControls() {
+    private void hideAllUserControls() {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "%s: hideAllUserControls()", TAG);
+
+        if (mCurrentMenuMode == MODE_NO_MENU) return;
+
         setMenuButtonsVisible(false);
         hideMovementHints();
         setFrameHighlighted(false);
         animateAlphaTo(0f, mDimLayer);
     }
 
+    void setPipGravity(int gravity) {
+        mCurrentPipGravity = gravity;
+        if (mCurrentMenuMode == MODE_MOVE_MENU) {
+            showMovementHints();
+        }
+    }
+
     @Override
     public void onWindowFocusChanged(boolean hasWindowFocus) {
         super.onWindowFocusChanged(hasWindowFocus);
-        if (!hasWindowFocus) {
-            hideAllUserControls();
-        }
+        mListener.onPipWindowFocusChanged(hasWindowFocus);
     }
 
     private void animateAlphaTo(float alpha, View view) {
@@ -399,15 +441,13 @@
     /**
      * Shows user hints for moving the PiP, e.g. arrows.
      */
-    public void showMovementHints(int gravity) {
+    public void showMovementHints() {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                "%s: showMovementHints(), position: %s", TAG, Gravity.toString(gravity));
-        mMoveMenuIsVisible = true;
-
-        animateAlphaTo(checkGravity(gravity, Gravity.BOTTOM) ? 1f : 0f, mArrowUp);
-        animateAlphaTo(checkGravity(gravity, Gravity.TOP) ? 1f : 0f, mArrowDown);
-        animateAlphaTo(checkGravity(gravity, Gravity.RIGHT) ? 1f : 0f, mArrowLeft);
-        animateAlphaTo(checkGravity(gravity, Gravity.LEFT) ? 1f : 0f, mArrowRight);
+                "%s: showMovementHints(), position: %s", TAG, Gravity.toString(mCurrentPipGravity));
+        animateAlphaTo(checkGravity(mCurrentPipGravity, Gravity.BOTTOM) ? 1f : 0f, mArrowUp);
+        animateAlphaTo(checkGravity(mCurrentPipGravity, Gravity.TOP) ? 1f : 0f, mArrowDown);
+        animateAlphaTo(checkGravity(mCurrentPipGravity, Gravity.RIGHT) ? 1f : 0f, mArrowLeft);
+        animateAlphaTo(checkGravity(mCurrentPipGravity, Gravity.LEFT) ? 1f : 0f, mArrowRight);
 
         boolean a11yEnabled = mA11yManager.isEnabled();
         setArrowA11yEnabled(mArrowUp, a11yEnabled, KEYCODE_DPAD_UP);
@@ -446,10 +486,7 @@
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "%s: hideMovementHints()", TAG);
 
-        if (!mMoveMenuIsVisible) {
-            return;
-        }
-        mMoveMenuIsVisible = false;
+        if (mCurrentMenuMode != MODE_MOVE_MENU) return;
 
         animateAlphaTo(0, mArrowUp);
         animateAlphaTo(0, mArrowRight);
@@ -464,7 +501,6 @@
     private void setMenuButtonsVisible(boolean visible) {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "%s: showUserActions: %b", TAG, visible);
-        mButtonMenuIsVisible = visible;
         animateAlphaTo(visible ? 1 : 0, mActionButtonsRecyclerView);
     }
 
@@ -534,5 +570,11 @@
          * @return whether pip movement was handled.
          */
         boolean onPipMovement(int keycode);
+
+        /**
+         * Called when the TvPipMenuView loses focus. This also means that the TV PiP menu window
+         * has lost focus.
+         */
+        void onPipWindowFocusChanged(boolean focused);
     }
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index db75be7..5c64177 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -418,6 +418,13 @@
             for (int i = 0; i < info.getChanges().size(); ++i) {
                 final TransitionInfo.Change change = info.getChanges().get(i);
                 final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+                if (taskInfo != null
+                        && taskInfo.configuration.windowConfiguration.isAlwaysOnTop()) {
+                    // Tasks that are always on top (e.g. bubbles), will handle their own transition
+                    // as they are on top of everything else. So cancel the merge here.
+                    cancel();
+                    return;
+                }
                 hasTaskChange = hasTaskChange || taskInfo != null;
                 final boolean isLeafTask = leafTaskFilter.test(change);
                 if (TransitionUtil.isOpeningType(change.getMode())) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java
index bdda6a8..bfa6390 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java
@@ -22,6 +22,8 @@
 public class ShellSharedConstants {
     // See IPip.aidl
     public static final String KEY_EXTRA_SHELL_PIP = "extra_shell_pip";
+    // See IBubbles.aidl
+    public static final String KEY_EXTRA_SHELL_BUBBLES = "extra_shell_bubbles";
     // See ISplitScreen.aidl
     public static final String KEY_EXTRA_SHELL_SPLIT_SCREEN = "extra_shell_split_screen";
     // See IOneHanded.aidl
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
index e632b56..d25318d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
@@ -228,7 +228,7 @@
         } else if ((mEndWidth > mStartWidth) == (mEndHeight > mStartHeight)
                 && (mEndWidth != mStartWidth || mEndHeight != mStartHeight)) {
             // Display resizes without rotation change.
-            final float scale = Math.max((float) mEndWidth / mStartHeight,
+            final float scale = Math.max((float) mEndWidth / mStartWidth,
                     (float) mEndHeight / mStartHeight);
             matrix.setScale(scale, scale);
         }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java
index 2814ef9..e7d4598 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java
@@ -18,14 +18,20 @@
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
 import android.app.WindowConfiguration;
+import android.graphics.Color;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.RemoteException;
@@ -69,11 +75,7 @@
                 mBackAnimationBackground, mock(SurfaceControl.Transaction.class),
                 mock(Choreographer.class));
         spyOn(mCustomizeActivityAnimation);
-        spyOn(mCustomizeActivityAnimation.mCustomAnimationLoader);
-        doReturn(mMockCloseAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader)
-                .load(any(), eq(false));
-        doReturn(mMockOpenAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader)
-                .load(any(), eq(true));
+        spyOn(mCustomizeActivityAnimation.mCustomAnimationLoader.mTransitionAnimation);
     }
 
     RemoteAnimationTarget createAnimationTarget(boolean open) {
@@ -87,6 +89,12 @@
 
     @Test
     public void receiveFinishAfterInvoke() throws InterruptedException {
+        spyOn(mCustomizeActivityAnimation.mCustomAnimationLoader);
+        doReturn(mMockCloseAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader)
+                .loadAnimation(any(), eq(false));
+        doReturn(mMockOpenAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader)
+                .loadAnimation(any(), eq(true));
+
         mCustomizeActivityAnimation.prepareNextAnimation(
                 new BackNavigationInfo.CustomAnimationInfo("TestPackage"));
         final RemoteAnimationTarget close = createAnimationTarget(false);
@@ -112,6 +120,12 @@
 
     @Test
     public void receiveFinishAfterCancel() throws InterruptedException {
+        spyOn(mCustomizeActivityAnimation.mCustomAnimationLoader);
+        doReturn(mMockCloseAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader)
+                .loadAnimation(any(), eq(false));
+        doReturn(mMockOpenAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader)
+                .loadAnimation(any(), eq(true));
+
         mCustomizeActivityAnimation.prepareNextAnimation(
                 new BackNavigationInfo.CustomAnimationInfo("TestPackage"));
         final RemoteAnimationTarget close = createAnimationTarget(false);
@@ -152,4 +166,67 @@
         verify(mCustomizeActivityAnimation).onGestureCommitted();
         finishCalled.await(1, TimeUnit.SECONDS);
     }
+
+    @Test
+    public void testLoadCustomAnimation() {
+        testLoadCustomAnimation(10, 20, 0);
+    }
+
+    @Test
+    public void testLoadCustomAnimationNoEnter() {
+        testLoadCustomAnimation(0, 10, 0);
+    }
+
+    @Test
+    public void testLoadWindowAnimations() {
+        testLoadCustomAnimation(0, 0, 30);
+    }
+
+    @Test
+    public void testCustomAnimationHigherThanWindowAnimations() {
+        testLoadCustomAnimation(10, 20, 30);
+    }
+
+    private void testLoadCustomAnimation(int enterResId, int exitResId, int windowAnimations) {
+        final String testPackage = "TestPackage";
+        BackNavigationInfo.Builder builder = new BackNavigationInfo.Builder()
+                .setCustomAnimation(testPackage, enterResId, exitResId, Color.GREEN)
+                .setWindowAnimations(testPackage, windowAnimations);
+        final BackNavigationInfo.CustomAnimationInfo info = builder.build()
+                .getCustomAnimationInfo();
+
+        doReturn(mMockOpenAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader
+                        .mTransitionAnimation)
+                .loadAppTransitionAnimation(eq(testPackage), eq(enterResId));
+        doReturn(mMockCloseAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader
+                        .mTransitionAnimation)
+                .loadAppTransitionAnimation(eq(testPackage), eq(exitResId));
+        doReturn(mMockCloseAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader
+                        .mTransitionAnimation)
+                .loadAnimationAttr(eq(testPackage), eq(windowAnimations), anyInt(), anyBoolean());
+        doReturn(mMockOpenAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader
+                        .mTransitionAnimation).loadDefaultAnimationAttr(anyInt(), anyBoolean());
+
+        CustomizeActivityAnimation.AnimationLoadResult result =
+                mCustomizeActivityAnimation.mCustomAnimationLoader.loadAll(info);
+
+        if (exitResId != 0) {
+            if (enterResId == 0) {
+                verify(mCustomizeActivityAnimation.mCustomAnimationLoader.mTransitionAnimation,
+                        never()).loadAppTransitionAnimation(eq(testPackage), eq(enterResId));
+                verify(mCustomizeActivityAnimation.mCustomAnimationLoader.mTransitionAnimation)
+                        .loadDefaultAnimationAttr(anyInt(), anyBoolean());
+            } else {
+                assertEquals(result.mEnterAnimation, mMockOpenAnimation);
+            }
+            assertEquals(result.mBackgroundColor, Color.GREEN);
+            assertEquals(result.mCloseAnimation, mMockCloseAnimation);
+            verify(mCustomizeActivityAnimation.mCustomAnimationLoader.mTransitionAnimation, never())
+                    .loadAnimationAttr(eq(testPackage), anyInt(), anyInt(), anyBoolean());
+        } else if (windowAnimations != 0) {
+            verify(mCustomizeActivityAnimation.mCustomAnimationLoader.mTransitionAnimation,
+                    times(2)).loadAnimationAttr(eq(testPackage), anyInt(), anyInt(), anyBoolean());
+            assertEquals(result.mCloseAnimation, mMockCloseAnimation);
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipMenuControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipMenuControllerTest.java
new file mode 100644
index 0000000..7c6037c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipMenuControllerTest.java
@@ -0,0 +1,324 @@
+/*
+ * 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.
+ */
+
+package com.android.wm.shell.pip.tv;
+
+import static android.view.KeyEvent.KEYCODE_DPAD_UP;
+
+import static com.android.wm.shell.pip.tv.TvPipMenuController.MODE_ALL_ACTIONS_MENU;
+import static com.android.wm.shell.pip.tv.TvPipMenuController.MODE_MOVE_MENU;
+import static com.android.wm.shell.pip.tv.TvPipMenuController.MODE_NO_MENU;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.os.Handler;
+import android.view.SurfaceControl;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.SystemWindows;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class TvPipMenuControllerTest extends ShellTestCase {
+    private static final int TEST_MOVE_KEYCODE = KEYCODE_DPAD_UP;
+
+    @Mock
+    private TvPipMenuController.Delegate mMockDelegate;
+    @Mock
+    private TvPipBoundsState mMockTvPipBoundsState;
+    @Mock
+    private SystemWindows mMockSystemWindows;
+    @Mock
+    private SurfaceControl mMockPipLeash;
+    @Mock
+    private Handler mMockHandler;
+    @Mock
+    private TvPipActionsProvider mMockActionsProvider;
+    @Mock
+    private TvPipMenuView mMockTvPipMenuView;
+
+    private TvPipMenuController mTvPipMenuController;
+
+    @Before
+    public void setUp() {
+        assumeTrue(isTelevision());
+
+        MockitoAnnotations.initMocks(this);
+
+        mTvPipMenuController = new TestTvPipMenuController();
+        mTvPipMenuController.setDelegate(mMockDelegate);
+        mTvPipMenuController.setTvPipActionsProvider(mMockActionsProvider);
+        mTvPipMenuController.attach(mMockPipLeash);
+    }
+
+    @Test
+    public void testMenuNotOpenByDefault() {
+        assertMenuIsOpen(false);
+    }
+
+    @Test
+    public void testSwitch_FromNoMenuMode_ToMoveMode() {
+        showAndAssertMoveMenu();
+    }
+
+    @Test
+    public void testSwitch_FromNoMenuMode_ToAllActionsMode() {
+        showAndAssertAllActionsMenu();
+    }
+
+    @Test
+    public void testSwitch_FromMoveMode_ToAllActionsMode() {
+        showAndAssertMoveMenu();
+        showAndAssertAllActionsMenu();
+    }
+
+    @Test
+    public void testSwitch_FromAllActionsMode_ToMoveMode() {
+        showAndAssertAllActionsMenu();
+        showAndAssertMoveMenu();
+    }
+
+    @Test
+    public void testCloseMenu_NoMenuMode() {
+        mTvPipMenuController.closeMenu();
+        assertMenuIsOpen(false);
+        verify(mMockDelegate, never()).onMenuClosed();
+    }
+
+    @Test
+    public void testCloseMenu_MoveMode() {
+        showAndAssertMoveMenu();
+
+        closeMenuAndAssertMenuClosed();
+        verify(mMockDelegate, times(2)).onInMoveModeChanged();
+    }
+
+    @Test
+    public void testCloseMenu_AllActionsMode() {
+        showAndAssertAllActionsMenu();
+
+        closeMenuAndAssertMenuClosed();
+    }
+
+    @Test
+    public void testCloseMenu_MoveModeFollowedByAllActionsMode() {
+        showAndAssertMoveMenu();
+        showAndAssertAllActionsMenu();
+        verify(mMockDelegate, times(2)).onInMoveModeChanged();
+
+        closeMenuAndAssertMenuClosed();
+    }
+
+    @Test
+    public void testCloseMenu_AllActionsModeFollowedByMoveMode() {
+        showAndAssertAllActionsMenu();
+        showAndAssertMoveMenu();
+
+        closeMenuAndAssertMenuClosed();
+        verify(mMockDelegate, times(2)).onInMoveModeChanged();
+    }
+
+    @Test
+    public void testExitMoveMode_NoMenuMode() {
+        mTvPipMenuController.onExitMoveMode();
+        assertMenuIsOpen(false);
+        verify(mMockDelegate, never()).onMenuClosed();
+    }
+
+    @Test
+    public void testExitMoveMode_MoveMode() {
+        showAndAssertMoveMenu();
+
+        mTvPipMenuController.onExitMoveMode();
+        assertMenuClosed();
+        verify(mMockDelegate, times(2)).onInMoveModeChanged();
+    }
+
+    @Test
+    public void testExitMoveMode_AllActionsMode() {
+        showAndAssertAllActionsMenu();
+
+        mTvPipMenuController.onExitMoveMode();
+        assertMenuIsInAllActionsMode();
+
+    }
+
+    @Test
+    public void testExitMoveMode_AllActionsModeFollowedByMoveMode() {
+        showAndAssertAllActionsMenu();
+        showAndAssertMoveMenu();
+
+        mTvPipMenuController.onExitMoveMode();
+        assertMenuIsInAllActionsMode();
+        verify(mMockDelegate, times(2)).onInMoveModeChanged();
+        verify(mMockTvPipMenuView).transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU), eq(false));
+    }
+
+    @Test
+    public void testOnBackPress_NoMenuMode() {
+        mTvPipMenuController.onBackPress();
+        assertMenuIsOpen(false);
+        verify(mMockDelegate, never()).onMenuClosed();
+    }
+
+    @Test
+    public void testOnBackPress_MoveMode() {
+        showAndAssertMoveMenu();
+
+        pressBackAndAssertMenuClosed();
+        verify(mMockDelegate, times(2)).onInMoveModeChanged();
+    }
+
+    @Test
+    public void testOnBackPress_AllActionsMode() {
+        showAndAssertAllActionsMenu();
+
+        pressBackAndAssertMenuClosed();
+    }
+
+    @Test
+    public void testOnBackPress_MoveModeFollowedByAllActionsMode() {
+        showAndAssertMoveMenu();
+        showAndAssertAllActionsMenu();
+        verify(mMockDelegate, times(2)).onInMoveModeChanged();
+
+        pressBackAndAssertMenuClosed();
+    }
+
+    @Test
+    public void testOnBackPress_AllActionsModeFollowedByMoveMode() {
+        showAndAssertAllActionsMenu();
+        showAndAssertMoveMenu();
+
+        mTvPipMenuController.onBackPress();
+        assertMenuIsInAllActionsMode();
+        verify(mMockDelegate, times(2)).onInMoveModeChanged();
+        verify(mMockTvPipMenuView).transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU), eq(false));
+
+        pressBackAndAssertMenuClosed();
+    }
+
+    @Test
+    public void testOnPipMovement_NoMenuMode() {
+        assertPipMoveSuccessful(false, mTvPipMenuController.onPipMovement(TEST_MOVE_KEYCODE));
+    }
+
+    @Test
+    public void testOnPipMovement_MoveMode() {
+        showAndAssertMoveMenu();
+        assertPipMoveSuccessful(true, mTvPipMenuController.onPipMovement(TEST_MOVE_KEYCODE));
+        verify(mMockDelegate).movePip(eq(TEST_MOVE_KEYCODE));
+    }
+
+    @Test
+    public void testOnPipMovement_AllActionsMode() {
+        showAndAssertAllActionsMenu();
+        assertPipMoveSuccessful(false, mTvPipMenuController.onPipMovement(TEST_MOVE_KEYCODE));
+    }
+
+    @Test
+    public void testOnPipWindowFocusChanged_NoMenuMode() {
+        mTvPipMenuController.onPipWindowFocusChanged(false);
+        assertMenuIsOpen(false);
+    }
+
+    @Test
+    public void testOnPipWindowFocusChanged_MoveMode() {
+        showAndAssertMoveMenu();
+        mTvPipMenuController.onPipWindowFocusChanged(false);
+        assertMenuClosed();
+    }
+
+    @Test
+    public void testOnPipWindowFocusChanged_AllActionsMode() {
+        showAndAssertAllActionsMenu();
+        mTvPipMenuController.onPipWindowFocusChanged(false);
+        assertMenuClosed();
+    }
+
+    private void showAndAssertMoveMenu() {
+        mTvPipMenuController.showMovementMenu();
+        assertMenuIsInMoveMode();
+        verify(mMockDelegate).onInMoveModeChanged();
+        verify(mMockTvPipMenuView).transitionToMenuMode(eq(MODE_MOVE_MENU), eq(false));
+    }
+
+    private void showAndAssertAllActionsMenu() {
+        mTvPipMenuController.showMenu();
+        assertMenuIsInAllActionsMode();
+        verify(mMockTvPipMenuView).transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU), eq(true));
+    }
+
+    private void closeMenuAndAssertMenuClosed() {
+        mTvPipMenuController.closeMenu();
+        assertMenuClosed();
+    }
+
+    private void pressBackAndAssertMenuClosed() {
+        mTvPipMenuController.onBackPress();
+        assertMenuClosed();
+    }
+
+    private void assertMenuClosed() {
+        assertMenuIsOpen(false);
+        verify(mMockDelegate).onMenuClosed();
+        verify(mMockTvPipMenuView).transitionToMenuMode(eq(MODE_NO_MENU), eq(false));
+    }
+
+    private void assertMenuIsOpen(boolean open) {
+        assertTrue("The TV PiP menu should " + (open ? "" : "not ") + "be open, but it"
+                + " is in mode " + mTvPipMenuController.getMenuModeString(),
+                mTvPipMenuController.isMenuOpen() == open);
+    }
+
+    private void assertMenuIsInMoveMode() {
+        assertTrue("Expected MODE_MOVE_MENU, but got " + mTvPipMenuController.getMenuModeString(),
+                mTvPipMenuController.isInMoveMode());
+        assertMenuIsOpen(true);
+    }
+
+    private void assertMenuIsInAllActionsMode() {
+        assertTrue("Expected MODE_ALL_ACTIONS_MENU, but got "
+                + mTvPipMenuController.getMenuModeString(),
+                mTvPipMenuController.isInAllActionsMode());
+        assertMenuIsOpen(true);
+    }
+
+    private void assertPipMoveSuccessful(boolean expected, boolean actual) {
+        assertTrue("Should " + (expected ? "" : "not ") + "move PiP when the menu is in mode "
+                + mTvPipMenuController.getMenuModeString(), expected == actual);
+    }
+
+    private class TestTvPipMenuController extends TvPipMenuController {
+
+        TestTvPipMenuController() {
+            super(mContext, mMockTvPipBoundsState, mMockSystemWindows, mMockHandler);
+        }
+
+        @Override
+        TvPipMenuView createTvPipMenuView() {
+            return mMockTvPipMenuView;
+        }
+    }
+}
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 1d069b6..1e0c2cd 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -103,6 +103,8 @@
   <string name="get_dialog_title_use_sign_in_for">Use your saved sign-in for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g>?</string>
   <!-- This appears as the title of the dialog asking for user to make a choice from various previously saved credentials to sign in to the app. [CHAR LIMIT=200] -->
   <string name="get_dialog_title_choose_sign_in_for">Choose a saved sign-in for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
+  <!-- This appears as the title of the dialog asking for user to make a choice from various previously saved credentials to sign in to the app. [CHAR LIMIT=200] -->
+  <string name="get_dialog_title_choose_option_for">Choose an option for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g>?</string>
   <!-- This is a label for a button that links the user to different sign-in methods . [CHAR LIMIT=80] -->
   <string name="get_dialog_use_saved_passkey_for">Sign in another way</string>
   <!-- This is a label for a button that links the user to different sign-in methods. [CHAR LIMIT=80] -->
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetGenericCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetGenericCredentialComponents.kt
index 8b95b5e..ba48f2b 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetGenericCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetGenericCredentialComponents.kt
@@ -19,8 +19,31 @@
 import androidx.activity.compose.ManagedActivityResultLauncher
 import androidx.activity.result.ActivityResult
 import androidx.activity.result.IntentSenderRequest
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.material3.Divider
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import androidx.core.graphics.drawable.toBitmap
+import com.android.compose.rememberSystemUiController
 import com.android.credentialmanager.CredentialSelectorViewModel
+import com.android.credentialmanager.R
+import com.android.credentialmanager.common.BaseEntry
+import com.android.credentialmanager.common.ProviderActivityState
+import com.android.credentialmanager.common.ui.CredentialContainerCard
+import com.android.credentialmanager.common.ui.HeadlineIcon
+import com.android.credentialmanager.common.ui.HeadlineText
+import com.android.credentialmanager.common.ui.LargeLabelTextOnSurfaceVariant
+import com.android.credentialmanager.common.ui.ModalBottomSheet
+import com.android.credentialmanager.common.ui.SheetContainerCard
+import com.android.credentialmanager.common.ui.setBottomSheetSystemBarsColor
+import com.android.credentialmanager.logging.GetCredentialEvent
+import com.android.internal.logging.UiEventLogger
+
 
 @Composable
 fun GetGenericCredentialScreen(
@@ -28,5 +51,102 @@
         getCredentialUiState: GetCredentialUiState,
         providerActivityLauncher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
 ) {
-    // TODO(b/274129098): Implement Screen for mDocs
-}
\ No newline at end of file
+    val sysUiController = rememberSystemUiController()
+    setBottomSheetSystemBarsColor(sysUiController)
+    ModalBottomSheet(
+        sheetContent = {
+            when (viewModel.uiState.providerActivityState) {
+                ProviderActivityState.NOT_APPLICABLE -> {
+                    PrimarySelectionCardGeneric(
+                            requestDisplayInfo = getCredentialUiState.requestDisplayInfo,
+                            providerDisplayInfo = getCredentialUiState.providerDisplayInfo,
+                            providerInfoList = getCredentialUiState.providerInfoList,
+                            onEntrySelected = viewModel::getFlowOnEntrySelected,
+                            onLog = { viewModel.logUiEvent(it) },
+                    )
+                    viewModel.uiMetrics.log(GetCredentialEvent
+                            .CREDMAN_GET_CRED_SCREEN_PRIMARY_SELECTION)
+                }
+                ProviderActivityState.READY_TO_LAUNCH -> {
+                    // Launch only once per providerActivityState change so that the provider
+                    // UI will not be accidentally launched twice.
+                    LaunchedEffect(viewModel.uiState.providerActivityState) {
+                        viewModel.launchProviderUi(providerActivityLauncher)
+                    }
+                    viewModel.uiMetrics.log(GetCredentialEvent
+                            .CREDMAN_GET_CRED_PROVIDER_ACTIVITY_READY_TO_LAUNCH)
+                }
+                ProviderActivityState.PENDING -> {
+                    // Hide our content when the provider activity is active.
+                    viewModel.uiMetrics.log(GetCredentialEvent
+                            .CREDMAN_GET_CRED_PROVIDER_ACTIVITY_PENDING)
+                }
+            }
+        },
+        onDismiss = viewModel::onUserCancel,
+    )
+}
+
+@Composable
+fun PrimarySelectionCardGeneric(
+        requestDisplayInfo: RequestDisplayInfo,
+        providerDisplayInfo: ProviderDisplayInfo,
+        providerInfoList: List<ProviderInfo>,
+        onEntrySelected: (BaseEntry) -> Unit,
+        onLog: @Composable (UiEventLogger.UiEventEnum) -> Unit,
+) {
+    val sortedUserNameToCredentialEntryList =
+            providerDisplayInfo.sortedUserNameToCredentialEntryList
+    SheetContainerCard {
+        // When only one provider (not counting the remote-only provider) exists, display that
+        // provider's icon + name up top.
+        if (providerInfoList.size <= 2) { // It's only possible to be the single provider case
+            // if we are started with no more than 2 providers.
+            val nonRemoteProviderList = providerInfoList.filter(
+                { it.credentialEntryList.isNotEmpty() || it.authenticationEntryList.isNotEmpty() }
+            )
+            if (nonRemoteProviderList.size == 1) {
+                val providerInfo = nonRemoteProviderList.firstOrNull() // First should always work
+                // but just to be safe.
+                if (providerInfo != null) {
+                    item {
+                        HeadlineIcon(
+                                bitmap = providerInfo.icon.toBitmap().asImageBitmap(),
+                                tint = Color.Unspecified,
+                        )
+                    }
+                    item { Divider(thickness = 4.dp, color = Color.Transparent) }
+                    item { LargeLabelTextOnSurfaceVariant(text = providerInfo.displayName) }
+                    item { Divider(thickness = 16.dp, color = Color.Transparent) }
+                }
+            }
+        }
+
+        item {
+            HeadlineText(
+                    text = stringResource(
+                            R.string.get_dialog_title_choose_option_for,
+                            requestDisplayInfo.appName
+                    ),
+            )
+        }
+        item { Divider(thickness = 24.dp, color = Color.Transparent) }
+        item {
+            CredentialContainerCard {
+                Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
+                    // Show max 4 entries in this primary page
+                    sortedUserNameToCredentialEntryList.forEach {
+                        // TODO(b/275375861): fallback UI merges entries by account names.
+                        //  Need a strategy to be able to show all entries.
+                        CredentialEntryRow(
+                                credentialEntryInfo = it.sortedCredentialEntryList.first(),
+                                onEntrySelected = onEntrySelected,
+                                enforceOneLine = true,
+                        )
+                    }
+                }
+            }
+        }
+    }
+    onLog(GetCredentialEvent.CREDMAN_GET_CRED_PRIMARY_SELECTION_CARD)
+}
diff --git a/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java b/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java
index f305fd3..e92157e 100644
--- a/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java
+++ b/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java
@@ -47,7 +47,7 @@
  * Annotation processor for {@link SearchIndexable} that generates {@link SearchIndexableResources}
  * subclasses.
  */
-@SupportedSourceVersion(SourceVersion.RELEASE_11)
+@SupportedSourceVersion(SourceVersion.RELEASE_17)
 @SupportedAnnotationTypes({"com.android.settingslib.search.SearchIndexable"})
 public class IndexableProcessor extends AbstractProcessor {
 
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index a9d1464..f2f0fe9 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -114,6 +114,8 @@
                     Settings.Global.ADD_USERS_WHEN_LOCKED,
                     Settings.Global.AIRPLANE_MODE_ON,
                     Settings.Global.AIRPLANE_MODE_RADIOS,
+                    Settings.Global.SATELLITE_MODE_RADIOS,
+                    Settings.Global.SATELLITE_MODE_ENABLED,
                     Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS,
                     Settings.Global.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED,
                     Settings.Global.ALWAYS_FINISH_ACTIVITIES,
@@ -420,6 +422,7 @@
                     Settings.Global.RADIO_NFC,
                     Settings.Global.RADIO_WIFI,
                     Settings.Global.RADIO_WIMAX,
+                    Settings.Global.RADIO_UWB,
                     Settings.Global.REMOVE_GUEST_ON_EXIT,
                     Settings.Global.RECOMMENDED_NETWORK_EVALUATOR_CACHE_EXPIRY_MS,
                     Settings.Global.READ_EXTERNAL_STORAGE_ENFORCED_DEFAULT,
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/README.md b/packages/SystemUI/accessibility/accessibilitymenu/README.md
new file mode 100644
index 0000000..b7fc363
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/README.md
@@ -0,0 +1,40 @@
+The Accessibility Menu is an accessibility service
+that presents a large on-screen menu to control your Android device.
+This service can be enabled from the Accessibility page in the Settings app.
+You can control gestures, hardware buttons, navigation, and more. From the menu, you can:
+
+- Take screenshots
+- Lock your screen
+- Open the device's voice assistant
+- Open Quick Settings and Notifications
+- Turn volume up or down
+- Turn brightness up or down
+
+The UI consists of a `ViewPager` populated by multiple pages of shortcut buttons.
+In the settings for the menu, there is an option to display the buttons in a 3x3 grid per page,
+or a 2x2 grid with larger buttons.
+
+Upon activation, most buttons will close the menu while performing their function.
+The exception to this are buttons that adjust a value, like volume or brightness,
+where the user is likely to want to press the button multiple times.
+In addition, touching other parts of the screen or locking the phone through other means
+should dismiss the menu.
+
+A majority of the shortcuts correspond directly to an existing accessibility service global action
+(see `AccessibilityService#performGlobalAction()` constants) that is performed when pressed.
+Shortcuts that navigate to a different menu, such as Quick Settings, use an intent to do so.
+Shortcuts that adjust brightness or volume interface directly with
+`DisplayManager` & `AudioManager` respectively.
+
+To add a new shortcut:
+
+1. Add a value for the new shortcut to the `ShortcutId` enum in `A11yMenuShortcut`.
+2. Put an entry for the enum value into the `sShortcutResource` `HashMap` in `A11yMenuShortcut`.
+This will require resources for a drawable icon, a color for the icon,
+the displayed name of the shortcut and the desired text-to-speech output.
+3. Add the enum value to the `SHORTCUT_LIST_DEFAULT` & `LARGE_SHORTCUT_LIST_DEFAULT` arrays
+in `A11yMenuOverlayLayout`.
+4. For functionality, add a code block to the if-else chain in
+`AccessibilityMenuService.handleClick()`, detailing the effect of the shortcut.
+If you don't want the shortcut to close the menu,
+include a return statement at the end of the code block.
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java
index 4e70455..59911b2 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java
@@ -53,10 +53,6 @@
     /** Returns true if the gesture should be rejected. */
     boolean isFalseGesture();
 
-    public boolean swipedFarEnough(float translation, float viewSize);
-
-    public boolean swipedFastEnough(float translation, float velocity);
-
     @ProvidesInterface(version = SnoozeOption.VERSION)
     public interface SnoozeOption {
         public static final int VERSION = 2;
diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
index 2871cdf..4048a39 100644
--- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml
+++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
@@ -64,7 +64,8 @@
         android:layout_height="@dimen/keyguard_affordance_fixed_height"
         android:layout_width="@dimen/keyguard_affordance_fixed_width"
         android:layout_gravity="bottom|start"
-        android:scaleType="center"
+        android:scaleType="fitCenter"
+        android:padding="@dimen/keyguard_affordance_fixed_padding"
         android:tint="?android:attr/textColorPrimary"
         android:background="@drawable/keyguard_bottom_affordance_bg"
         android:foreground="@drawable/keyguard_bottom_affordance_selected_border"
@@ -77,7 +78,8 @@
         android:layout_height="@dimen/keyguard_affordance_fixed_height"
         android:layout_width="@dimen/keyguard_affordance_fixed_width"
         android:layout_gravity="bottom|end"
-        android:scaleType="center"
+        android:scaleType="fitCenter"
+        android:padding="@dimen/keyguard_affordance_fixed_padding"
         android:tint="?android:attr/textColorPrimary"
         android:background="@drawable/keyguard_bottom_affordance_bg"
         android:foreground="@drawable/keyguard_bottom_affordance_selected_border"
diff --git a/packages/SystemUI/res/layout/notification_snooze.xml b/packages/SystemUI/res/layout/notification_snooze.xml
index bb82f91..11ec025 100644
--- a/packages/SystemUI/res/layout/notification_snooze.xml
+++ b/packages/SystemUI/res/layout/notification_snooze.xml
@@ -46,6 +46,7 @@
             android:layout_toEndOf="@+id/snooze_option_default"
             android:layout_centerVertical="true"
             android:paddingTop="1dp"
+            android:importantForAccessibility="yes"
             android:tint="#9E9E9E" />
 
         <TextView
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 249fc86..0c0defa 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -793,6 +793,7 @@
     <dimen name="keyguard_affordance_fixed_height">48dp</dimen>
     <dimen name="keyguard_affordance_fixed_width">48dp</dimen>
     <dimen name="keyguard_affordance_fixed_radius">24dp</dimen>
+    <dimen name="keyguard_affordance_fixed_padding">12dp</dimen>
 
     <!-- Amount the button should shake when it's not long-pressed for long enough. -->
     <dimen name="keyguard_affordance_shake_amplitude">8dp</dimen>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
index f8cb38d..9f2333d8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
@@ -277,6 +277,7 @@
     @Override
     public void onResume(int reason) {
         mResumed = true;
+        reset();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index 0c17489..68b40ab 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -271,6 +271,12 @@
     }
 
     @Override
+    public void onResume(int reason) {
+        super.onResume(reason);
+        reset();
+    }
+
+    @Override
     public void onPause() {
         super.onPause();
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index fd47e39..f23bb0a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -38,12 +38,15 @@
     private LockPatternUtils mLockPatternUtils;
     private final FeatureFlags mFeatureFlags;
     private static final int DEFAULT_PIN_LENGTH = 6;
+    private static final int MIN_FAILED_PIN_ATTEMPTS = 5;
     private NumPadButton mBackspaceKey;
     private View mOkButton = mView.findViewById(R.id.key_enter);
 
     private int mUserId;
     private long mPinLength;
 
+    private int mPasswordFailedAttempts;
+
     protected KeyguardPinViewController(KeyguardPINView view,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             SecurityMode securityMode, LockPatternUtils lockPatternUtils,
@@ -82,8 +85,10 @@
     protected void onUserInput() {
         super.onUserInput();
         if (isAutoConfirmation()) {
+            updateOKButtonVisibility();
             updateBackSpaceVisibility();
-            if (mPasswordEntry.getText().length() == mPinLength) {
+            if (mPasswordEntry.getText().length() == mPinLength
+                    && mOkButton.getVisibility() == View.INVISIBLE) {
                 verifyPasswordAndUnlock();
             }
         }
@@ -101,7 +106,7 @@
             mUserId = KeyguardUpdateMonitor.getCurrentUser();
             mPinLength = mLockPatternUtils.getPinLength(mUserId);
             mBackspaceKey.setTransparentMode(/* isTransparentMode= */ isAutoConfirmation());
-            mOkButton.setVisibility(isAutoConfirmation() ? View.INVISIBLE : View.VISIBLE);
+            updateOKButtonVisibility();
             updateBackSpaceVisibility();
             mPasswordEntry.setUsePinShapes(true);
             mPasswordEntry.setIsPinHinting(isAutoConfirmation() && isPinHinting());
@@ -115,7 +120,18 @@
                 mKeyguardUpdateMonitor.needsSlowUnlockTransition(), finishRunnable);
     }
 
-    //
+
+    /**
+     * Updates the visibility of the OK button for auto confirm feature
+     */
+    private void updateOKButtonVisibility() {
+        mPasswordFailedAttempts = mLockPatternUtils.getCurrentFailedPasswordAttempts(mUserId);
+        if (isAutoConfirmation() && mPasswordFailedAttempts < MIN_FAILED_PIN_ATTEMPTS) {
+            mOkButton.setVisibility(View.INVISIBLE);
+        } else {
+            mOkButton.setVisibility(View.VISIBLE);
+        }
+    }
 
     /**
      *  Updates the visibility and the enabled state of the backspace.
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
index 68e1dd7..ddf1199 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
@@ -54,19 +54,23 @@
     private final Factory mKeyguardSecurityViewControllerFactory;
     private final FeatureFlags mFeatureFlags;
 
+    private final ViewMediatorCallback mViewMediatorCallback;
+
     @Inject
     protected KeyguardSecurityViewFlipperController(KeyguardSecurityViewFlipper view,
             LayoutInflater layoutInflater,
             AsyncLayoutInflater asyncLayoutInflater,
             KeyguardInputViewController.Factory keyguardSecurityViewControllerFactory,
             EmergencyButtonController.Factory emergencyButtonControllerFactory,
-            FeatureFlags featureFlags) {
+            FeatureFlags featureFlags,
+            ViewMediatorCallback viewMediatorCallback) {
         super(view);
         mKeyguardSecurityViewControllerFactory = keyguardSecurityViewControllerFactory;
         mLayoutInflater = layoutInflater;
         mEmergencyButtonControllerFactory = emergencyButtonControllerFactory;
         mAsyncLayoutInflater = asyncLayoutInflater;
         mFeatureFlags = featureFlags;
+        mViewMediatorCallback = viewMediatorCallback;
     }
 
     @Override
@@ -152,6 +156,7 @@
                                         keyguardSecurityCallback);
                         childController.init();
                         mChildren.add(childController);
+                        mViewMediatorCallback.setNeedsInput(childController.needsInput());
                         if (onViewInflatedListener != null) {
                             onViewInflatedListener.onViewInflated();
                         }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 1de3ddd..30321f7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -2476,8 +2476,7 @@
      * not enrolled udfps. This may be false if called before onAllAuthenticatorsRegistered.
      */
     public boolean isUdfpsSupported() {
-        return mAuthController.getUdfpsProps() != null
-                && !mAuthController.getUdfpsProps().isEmpty();
+        return mAuthController.isUdfpsSupported();
     }
 
     /**
@@ -2492,8 +2491,7 @@
      * not enrolled sfps. This may be false if called before onAllAuthenticatorsRegistered.
      */
     public boolean isSfpsSupported() {
-        return mAuthController.getSfpsProps() != null
-                && !mAuthController.getSfpsProps().isEmpty();
+        return mAuthController.isSfpsSupported();
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 64a9cc9..2503520 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -17,7 +17,6 @@
 package com.android.systemui;
 
 import static androidx.dynamicanimation.animation.DynamicAnimation.TRANSLATION_X;
-import static androidx.dynamicanimation.animation.DynamicAnimation.TRANSLATION_Y;
 import static androidx.dynamicanimation.animation.FloatPropertyCompat.createFloatPropertyCompat;
 
 import static com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS;
@@ -92,7 +91,6 @@
     private float mTouchSlopMultiplier;
 
     private final Callback mCallback;
-    private final int mSwipeDirection;
     private final VelocityTracker mVelocityTracker;
     private final FalsingManager mFalsingManager;
     private final FeatureFlags mFeatureFlags;
@@ -141,12 +139,10 @@
     private final ArrayMap<View, Animator> mDismissPendingMap = new ArrayMap<>();
 
     public SwipeHelper(
-            int swipeDirection, Callback callback, Resources resources,
-            ViewConfiguration viewConfiguration, FalsingManager falsingManager,
-            FeatureFlags featureFlags) {
+            Callback callback, Resources resources, ViewConfiguration viewConfiguration,
+            FalsingManager falsingManager, FeatureFlags featureFlags) {
         mCallback = callback;
         mHandler = new Handler();
-        mSwipeDirection = swipeDirection;
         mVelocityTracker = VelocityTracker.obtain();
         mPagingTouchSlop = viewConfiguration.getScaledPagingTouchSlop();
         mSlopMultiplier = viewConfiguration.getScaledAmbiguousGestureMultiplier();
@@ -179,22 +175,22 @@
     }
 
     private float getPos(MotionEvent ev) {
-        return mSwipeDirection == X ? ev.getX() : ev.getY();
+        return ev.getX();
     }
 
     private float getPerpendicularPos(MotionEvent ev) {
-        return mSwipeDirection == X ? ev.getY() : ev.getX();
+        return ev.getY();
     }
 
     protected float getTranslation(View v) {
-        return mSwipeDirection == X ? v.getTranslationX() : v.getTranslationY();
+        return v.getTranslationX();
     }
 
     private float getVelocity(VelocityTracker vt) {
-        return mSwipeDirection == X ? vt.getXVelocity() :
-                vt.getYVelocity();
+        return vt.getXVelocity();
     }
 
+
     protected Animator getViewTranslationAnimator(View view, float target,
             AnimatorUpdateListener listener) {
 
@@ -209,8 +205,7 @@
 
     protected Animator createTranslationAnimation(View view, float newPos,
             AnimatorUpdateListener listener) {
-        ObjectAnimator anim = ObjectAnimator.ofFloat(view,
-                mSwipeDirection == X ? View.TRANSLATION_X : View.TRANSLATION_Y, newPos);
+        ObjectAnimator anim = ObjectAnimator.ofFloat(view, View.TRANSLATION_X, newPos);
 
         if (listener != null) {
             anim.addUpdateListener(listener);
@@ -220,18 +215,13 @@
     }
 
     protected void setTranslation(View v, float translate) {
-        if (v == null) {
-            return;
-        }
-        if (mSwipeDirection == X) {
+        if (v != null) {
             v.setTranslationX(translate);
-        } else {
-            v.setTranslationY(translate);
         }
     }
 
     protected float getSize(View v) {
-        return mSwipeDirection == X ? v.getMeasuredWidth() : v.getMeasuredHeight();
+        return v.getMeasuredWidth();
     }
 
     public void setMinSwipeProgress(float minSwipeProgress) {
@@ -426,15 +416,12 @@
         float newPos;
         boolean isLayoutRtl = animView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
 
-        // if we use the Menu to dismiss an item in landscape, animate up
-        boolean animateUpForMenu = velocity == 0 && (getTranslation(animView) == 0 || isDismissAll)
-                && mSwipeDirection == Y;
         // if the language is rtl we prefer swiping to the left
         boolean animateLeftForRtl = velocity == 0 && (getTranslation(animView) == 0 || isDismissAll)
                 && isLayoutRtl;
         boolean animateLeft = (Math.abs(velocity) > getEscapeVelocity() && velocity < 0) ||
                 (getTranslation(animView) < 0 && !isDismissAll);
-        if (animateLeft || animateLeftForRtl || animateUpForMenu) {
+        if (animateLeft || animateLeftForRtl) {
             newPos = -getTotalTranslationLength(animView);
         } else {
             newPos = getTotalTranslationLength(animView);
@@ -576,8 +563,7 @@
                     startVelocity,
                     mSnapBackSpringConfig);
         }
-        return PhysicsAnimator.getInstance(target).spring(
-                mSwipeDirection == X ? TRANSLATION_X : TRANSLATION_Y, toPosition, startVelocity,
+        return PhysicsAnimator.getInstance(target).spring(TRANSLATION_X, toPosition, startVelocity,
                 mSnapBackSpringConfig);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 705fc8c..92344db 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -984,6 +984,36 @@
         return mSidefpsProps;
     }
 
+    /**
+     * @return true if udfps HW is supported on this device. Can return true even if the user has
+     * not enrolled udfps. This may be false if called before onAllAuthenticatorsRegistered.
+     */
+    public boolean isUdfpsSupported() {
+        return getUdfpsProps() != null && !getUdfpsProps().isEmpty();
+    }
+
+    /**
+     * @return true if sfps HW is supported on this device. Can return true even if the user has
+     * not enrolled sfps. This may be false if called before onAllAuthenticatorsRegistered.
+     */
+    public boolean isSfpsSupported() {
+        return getSfpsProps() != null && !getSfpsProps().isEmpty();
+    }
+
+    /**
+     * @return true if rear fps HW is supported on this device. Can return true even if the user has
+     * not enrolled sfps. This may be false if called before onAllAuthenticatorsRegistered.
+     */
+    public boolean isRearFpsSupported() {
+        for (FingerprintSensorPropertiesInternal prop: mFpProps) {
+            if (prop.sensorType == TYPE_REAR) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+
     private String getErrorString(@Modality int modality, int error, int vendorCode) {
         switch (modality) {
             case TYPE_FACE:
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index c98a62f..eb5d23a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -59,8 +59,6 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.recents.OverviewProxyService
 import com.android.systemui.util.concurrency.DelayableExecutor
@@ -90,7 +88,6 @@
     @Main private val handler: Handler,
     private val alternateBouncerInteractor: AlternateBouncerInteractor,
     @Application private val scope: CoroutineScope,
-    private val featureFlags: FeatureFlags,
     dumpManager: DumpManager
 ) : Dumpable {
     private val requests: HashSet<SideFpsUiRequestSource> = HashSet()
@@ -191,14 +188,12 @@
 
     private fun listenForAlternateBouncerVisibility() {
         alternateBouncerInteractor.setAlternateBouncerUIAvailable(true)
-        if (featureFlags.isEnabled(Flags.MODERN_ALTERNATE_BOUNCER)) {
-            scope.launch {
-                alternateBouncerInteractor.isVisible.collect { isVisible: Boolean ->
-                    if (isVisible) {
-                        show(SideFpsUiRequestSource.ALTERNATE_BOUNCER, REASON_AUTH_KEYGUARD)
-                    } else {
-                        hide(SideFpsUiRequestSource.ALTERNATE_BOUNCER)
-                    }
+        scope.launch {
+            alternateBouncerInteractor.isVisible.collect { isVisible: Boolean ->
+                if (isVisible) {
+                    show(SideFpsUiRequestSource.ALTERNATE_BOUNCER, REASON_AUTH_KEYGUARD)
+                } else {
+                    hide(SideFpsUiRequestSource.ALTERNATE_BOUNCER)
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
index 231e7a4..3e7d81a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
@@ -42,7 +42,6 @@
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.KeyguardViewManagerCallback
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.LegacyAlternateBouncer
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.OccludingAppBiometricUI
 import com.android.systemui.statusbar.phone.SystemUIDialogManager
 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
@@ -82,8 +81,6 @@
     ) {
     private val useExpandedOverlay: Boolean =
         featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)
-    private val isModernAlternateBouncerEnabled: Boolean =
-        featureFlags.isEnabled(Flags.MODERN_ALTERNATE_BOUNCER)
     private var showingUdfpsBouncer = false
     private var udfpsRequested = false
     private var qsExpansion = 0f
@@ -107,7 +104,7 @@
                 )
             }
         }
-    private var inputBouncerExpansion = 0f // only used for modernBouncer
+    private var inputBouncerExpansion = 0f
 
     private val stateListener: StatusBarStateController.StateListener =
         object : StatusBarStateController.StateListener {
@@ -251,7 +248,7 @@
             // that may make the view visible again.
             repeatOnLifecycle(Lifecycle.State.CREATED) {
                 listenForBouncerExpansion(this)
-                if (isModernAlternateBouncerEnabled) listenForAlternateBouncerVisibility(this)
+                listenForAlternateBouncerVisibility(this)
             }
         }
     }
@@ -295,7 +292,6 @@
         view.updatePadding()
         updateAlpha()
         updatePauseAuth()
-        keyguardViewManager.setLegacyAlternateBouncer(legacyAlternateBouncer)
         keyguardViewManager.setOccludingAppBiometricUI(occludingAppBiometricUI)
         lockScreenShadeTransitionController.udfpsKeyguardViewController = this
         activityLaunchAnimator.addListener(activityLaunchAnimatorListener)
@@ -309,7 +305,6 @@
         faceDetectRunning = false
         keyguardStateController.removeCallback(keyguardStateControllerCallback)
         statusBarStateController.removeCallback(stateListener)
-        keyguardViewManager.removeLegacyAlternateBouncer(legacyAlternateBouncer)
         keyguardViewManager.removeOccludingAppBiometricUI(occludingAppBiometricUI)
         keyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false)
         configurationController.removeCallback(configurationListener)
@@ -323,7 +318,6 @@
 
     override fun dump(pw: PrintWriter, args: Array<String>) {
         super.dump(pw, args)
-        pw.println("isModernAlternateBouncerEnabled=$isModernAlternateBouncerEnabled")
         pw.println("showingUdfpsAltBouncer=$showingUdfpsBouncer")
         pw.println(
             "altBouncerInteractor#isAlternateBouncerVisible=" +
@@ -473,22 +467,6 @@
     private fun updateScaleFactor() {
         udfpsController.mOverlayParams?.scaleFactor?.let { view.setScaleFactor(it) }
     }
-
-    private val legacyAlternateBouncer: LegacyAlternateBouncer =
-        object : LegacyAlternateBouncer {
-            override fun showAlternateBouncer(): Boolean {
-                return showUdfpsBouncer(true)
-            }
-
-            override fun hideAlternateBouncer(): Boolean {
-                return showUdfpsBouncer(false)
-            }
-
-            override fun isShowingAlternateBouncer(): Boolean {
-                return showingUdfpsBouncer
-            }
-        }
-
     companion object {
         const val TAG = "UdfpsKeyguardViewController"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 4fe219d..79a51d6 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -169,13 +169,6 @@
     @JvmField
     val LIGHT_REVEAL_MIGRATION = unreleasedFlag(218, "light_reveal_migration", teamfood = false)
 
-    /**
-     * Whether to use the new alternate bouncer architecture, a refactor of and eventual replacement
-     * of the Alternate/Authentication Bouncer. No visual UI changes.
-     */
-    // TODO(b/260619425): Tracking Bug
-    @JvmField val MODERN_ALTERNATE_BOUNCER = releasedFlag(219, "modern_alternate_bouncer")
-
     /** Flag to control the migration of face auth to modern architecture. */
     // TODO(b/262838215): Tracking bug
     @JvmField val FACE_AUTH_REFACTOR = unreleasedFlag(220, "face_auth_refactor")
@@ -191,7 +184,7 @@
     // flag for controlling auto pin confirmation and material u shapes in bouncer
     @JvmField
     val AUTO_PIN_CONFIRMATION =
-        unreleasedFlag(224, "auto_pin_confirmation", "auto_pin_confirmation")
+        releasedFlag(224, "auto_pin_confirmation", "auto_pin_confirmation", teamfood = true)
 
     // TODO(b/262859270): Tracking Bug
     @JvmField val FALSING_OFF_FOR_UNFOLDED = releasedFlag(225, "falsing_off_for_unfolded")
@@ -226,12 +219,16 @@
     /** Whether to inflate the bouncer view on a background thread. */
     // TODO(b/272091103): Tracking Bug
     @JvmField
-    val ASYNC_INFLATE_BOUNCER = unreleasedFlag(229, "async_inflate_bouncer", teamfood = false)
+    val ASYNC_INFLATE_BOUNCER = unreleasedFlag(229, "async_inflate_bouncer", teamfood = true)
 
     /** Whether to inflate the bouncer view on a background thread. */
     // TODO(b/273341787): Tracking Bug
     @JvmField
-    val PREVENT_BYPASS_KEYGUARD = unreleasedFlag(230, "prevent_bypass_keyguard")
+    val PREVENT_BYPASS_KEYGUARD = unreleasedFlag(230, "prevent_bypass_keyguard", teamfood = true)
+
+    /** Whether to use a new data source for intents to run on keyguard dismissal. */
+    @JvmField
+    val REFACTOR_KEYGUARD_DISMISS_INTENT = unreleasedFlag(231, "refactor_keyguard_dismiss_intent")
 
     // 300 - power menu
     // TODO(b/254512600): Tracking Bug
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
index 7c46684..4fa56ee 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
@@ -20,6 +20,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.systemui.Dumpable
+import com.android.systemui.biometrics.AuthController
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
@@ -29,6 +30,7 @@
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.stateIn
@@ -37,6 +39,17 @@
 interface DeviceEntryFingerprintAuthRepository {
     /** Whether the device entry fingerprint auth is locked out. */
     val isLockedOut: StateFlow<Boolean>
+
+    /**
+     * Whether the fingerprint sensor is currently listening, this doesn't mean that the user is
+     * actively authenticating.
+     */
+    val isRunning: Flow<Boolean>
+
+    /**
+     * Fingerprint sensor type present on the device, null if fingerprint sensor is not available.
+     */
+    val availableFpSensorType: BiometricType?
 }
 
 /**
@@ -50,6 +63,7 @@
 class DeviceEntryFingerprintAuthRepositoryImpl
 @Inject
 constructor(
+    val authController: AuthController,
     val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     @Application scope: CoroutineScope,
     dumpManager: DumpManager,
@@ -63,6 +77,12 @@
         pw.println("isLockedOut=${isLockedOut.value}")
     }
 
+    override val availableFpSensorType: BiometricType?
+        get() =
+            if (authController.isUdfpsSupported) BiometricType.UNDER_DISPLAY_FINGERPRINT
+            else if (authController.isSfpsSupported) BiometricType.SIDE_FINGERPRINT
+            else if (authController.isRearFpsSupported) BiometricType.REAR_FINGERPRINT else null
+
     override val isLockedOut: StateFlow<Boolean> =
         conflatedCallbackFlow {
                 val sendLockoutUpdate =
@@ -89,6 +109,32 @@
             }
             .stateIn(scope, started = SharingStarted.Eagerly, initialValue = false)
 
+    override val isRunning: Flow<Boolean>
+        get() = conflatedCallbackFlow {
+            val callback =
+                object : KeyguardUpdateMonitorCallback() {
+                    override fun onBiometricRunningStateChanged(
+                        running: Boolean,
+                        biometricSourceType: BiometricSourceType?
+                    ) {
+                        if (biometricSourceType == BiometricSourceType.FINGERPRINT) {
+                            trySendWithFailureLogging(
+                                running,
+                                TAG,
+                                "Fingerprint running state changed"
+                            )
+                        }
+                    }
+                }
+            keyguardUpdateMonitor.registerCallback(callback)
+            trySendWithFailureLogging(
+                keyguardUpdateMonitor.isFingerprintDetectionRunning,
+                TAG,
+                "Initial fingerprint running state"
+            )
+            awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
+        }
+
     companion object {
         const val TAG = "DeviceEntryFingerprintAuthRepositoryImpl"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt
index aad4a2d..9b94cdb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt
@@ -16,15 +16,11 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
-import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
 import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.LegacyAlternateBouncer
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
@@ -41,13 +37,7 @@
     private val biometricSettingsRepository: BiometricSettingsRepository,
     private val deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
     private val systemClock: SystemClock,
-    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
-    featureFlags: FeatureFlags,
 ) {
-    val isModernAlternateBouncerEnabled = featureFlags.isEnabled(Flags.MODERN_ALTERNATE_BOUNCER)
-    var legacyAlternateBouncer: LegacyAlternateBouncer? = null
-    var legacyAlternateBouncerVisibleTime: Long = NOT_VISIBLE
-
     var receivedDownTouch = false
     val isVisible: Flow<Boolean> = bouncerRepository.alternateBouncerVisible
 
@@ -68,21 +58,8 @@
      * @return whether alternateBouncer is visible
      */
     fun show(): Boolean {
-        return when {
-            isModernAlternateBouncerEnabled -> {
-                bouncerRepository.setAlternateVisible(canShowAlternateBouncerForFingerprint())
-                isVisibleState()
-            }
-            canShowAlternateBouncerForFingerprint() -> {
-                if (legacyAlternateBouncer?.showAlternateBouncer() == true) {
-                    legacyAlternateBouncerVisibleTime = systemClock.uptimeMillis()
-                    true
-                } else {
-                    false
-                }
-            }
-            else -> false
-        }
+        bouncerRepository.setAlternateVisible(canShowAlternateBouncerForFingerprint())
+        return isVisibleState()
     }
 
     /**
@@ -94,21 +71,13 @@
      */
     fun hide(): Boolean {
         receivedDownTouch = false
-        return if (isModernAlternateBouncerEnabled) {
-            val wasAlternateBouncerVisible = isVisibleState()
-            bouncerRepository.setAlternateVisible(false)
-            wasAlternateBouncerVisible && !isVisibleState()
-        } else {
-            legacyAlternateBouncer?.hideAlternateBouncer() ?: false
-        }
+        val wasAlternateBouncerVisible = isVisibleState()
+        bouncerRepository.setAlternateVisible(false)
+        return wasAlternateBouncerVisible && !isVisibleState()
     }
 
     fun isVisibleState(): Boolean {
-        return if (isModernAlternateBouncerEnabled) {
-            bouncerRepository.alternateBouncerVisible.value
-        } else {
-            legacyAlternateBouncer?.isShowingAlternateBouncer ?: false
-        }
+        return bouncerRepository.alternateBouncerVisible.value
     }
 
     fun setAlternateBouncerUIAvailable(isAvailable: Boolean) {
@@ -116,18 +85,13 @@
     }
 
     fun canShowAlternateBouncerForFingerprint(): Boolean {
-        return if (isModernAlternateBouncerEnabled) {
-            bouncerRepository.alternateBouncerUIAvailable.value &&
-                biometricSettingsRepository.isFingerprintEnrolled.value &&
-                biometricSettingsRepository.isStrongBiometricAllowed.value &&
-                biometricSettingsRepository.isFingerprintEnabledByDevicePolicy.value &&
-                !deviceEntryFingerprintAuthRepository.isLockedOut.value &&
-                !keyguardStateController.isUnlocked &&
-                !statusBarStateController.isDozing
-        } else {
-            legacyAlternateBouncer != null &&
-                keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(true)
-        }
+        return bouncerRepository.alternateBouncerUIAvailable.value &&
+            biometricSettingsRepository.isFingerprintEnrolled.value &&
+            biometricSettingsRepository.isStrongBiometricAllowed.value &&
+            biometricSettingsRepository.isFingerprintEnabledByDevicePolicy.value &&
+            !deviceEntryFingerprintAuthRepository.isLockedOut.value &&
+            !keyguardStateController.isUnlocked &&
+            !statusBarStateController.isDozing
     }
 
     /**
@@ -135,12 +99,8 @@
      * alternate bouncer and show the primary bouncer.
      */
     fun hasAlternateBouncerShownWithMinTime(): Boolean {
-        return if (isModernAlternateBouncerEnabled) {
-            (systemClock.uptimeMillis() - bouncerRepository.lastAlternateBouncerVisibleTime) >
-                MIN_VISIBILITY_DURATION_UNTIL_TOUCHES_DISMISS_ALTERNATE_BOUNCER_MS
-        } else {
-            systemClock.uptimeMillis() - legacyAlternateBouncerVisibleTime > 200
-        }
+        return (systemClock.uptimeMillis() - bouncerRepository.lastAlternateBouncerVisibleTime) >
+            MIN_VISIBILITY_DURATION_UNTIL_TOUCHES_DISMISS_ALTERNATE_BOUNCER_MS
     }
 
     private fun maybeHide() {
@@ -151,6 +111,5 @@
 
     companion object {
         private const val MIN_VISIBILITY_DURATION_UNTIL_TOUCHES_DISMISS_ALTERNATE_BOUNCER_MS = 200L
-        private const val NOT_VISIBLE = -1L
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index 35819e3..9606bcf 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -78,7 +78,7 @@
     private static final boolean DEBUG = true;
     private static final int HANDLE_BROADCAST_FAILED_DELAY = 3000;
 
-    private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
+    protected final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
     private final RecyclerView.LayoutManager mLayoutManager;
 
     final Context mContext;
@@ -102,11 +102,13 @@
     private int mListMaxHeight;
     private int mItemHeight;
     private WallpaperColors mWallpaperColors;
-    private Executor mExecutor;
     private boolean mShouldLaunchLeBroadcastDialog;
+    private boolean mIsLeBroadcastCallbackRegistered;
 
     MediaOutputBaseAdapter mAdapter;
 
+    protected Executor mExecutor;
+
     private final ViewTreeObserver.OnGlobalLayoutListener mDeviceListLayoutListener = () -> {
         ViewGroup.LayoutParams params = mDeviceListLayout.getLayoutParams();
         int totalItemsHeight = mAdapter.getItemCount() * mItemHeight;
@@ -274,17 +276,19 @@
     public void onStart() {
         super.onStart();
         mMediaOutputController.start(this);
-        if(isBroadcastSupported()) {
-            mMediaOutputController.registerLeBroadcastServiceCallBack(mExecutor,
+        if (isBroadcastSupported() && !mIsLeBroadcastCallbackRegistered) {
+            mMediaOutputController.registerLeBroadcastServiceCallback(mExecutor,
                     mBroadcastCallback);
+            mIsLeBroadcastCallbackRegistered = true;
         }
     }
 
     @Override
     public void onStop() {
         super.onStop();
-        if(isBroadcastSupported()) {
-            mMediaOutputController.unregisterLeBroadcastServiceCallBack(mBroadcastCallback);
+        if (isBroadcastSupported() && mIsLeBroadcastCallbackRegistered) {
+            mMediaOutputController.unregisterLeBroadcastServiceCallback(mBroadcastCallback);
+            mIsLeBroadcastCallbackRegistered = false;
         }
         mMediaOutputController.stop();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
index 12d6b7c..f0ff140 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
@@ -17,6 +17,10 @@
 package com.android.systemui.media.dialog;
 
 import android.app.AlertDialog;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeBroadcastAssistant;
+import android.bluetooth.BluetoothLeBroadcastMetadata;
+import android.bluetooth.BluetoothLeBroadcastReceiveState;
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.os.Bundle;
@@ -34,8 +38,11 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import androidx.annotation.NonNull;
 import androidx.core.graphics.drawable.IconCompat;
 
+import com.android.settingslib.media.BluetoothMediaDevice;
+import com.android.settingslib.media.MediaDevice;
 import com.android.settingslib.qrcode.QrCodeGenerator;
 import com.android.systemui.R;
 import com.android.systemui.broadcast.BroadcastSender;
@@ -49,7 +56,7 @@
  */
 @SysUISingleton
 public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
-    private static final String TAG = "BroadcastDialog";
+    private static final String TAG = "MediaOutputBroadcastDialog";
 
     private ViewStub mBroadcastInfoArea;
     private ImageView mBroadcastQrCodeView;
@@ -66,6 +73,7 @@
     private String mCurrentBroadcastName;
     private String mCurrentBroadcastCode;
     private boolean mIsStopbyUpdateBroadcastCode = false;
+
     private TextWatcher mTextWatcher = new TextWatcher() {
         @Override
         public void beforeTextChanged(CharSequence s, int start, int count, int after) {
@@ -105,6 +113,79 @@
         }
     };
 
+    private boolean mIsLeBroadcastAssistantCallbackRegistered;
+
+    private BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback =
+            new BluetoothLeBroadcastAssistant.Callback() {
+                @Override
+                public void onSearchStarted(int reason) {
+                    Log.d(TAG, "Assistant-onSearchStarted: " + reason);
+                }
+
+                @Override
+                public void onSearchStartFailed(int reason) {
+                    Log.d(TAG, "Assistant-onSearchStartFailed: " + reason);
+                }
+
+                @Override
+                public void onSearchStopped(int reason) {
+                    Log.d(TAG, "Assistant-onSearchStopped: " + reason);
+                }
+
+                @Override
+                public void onSearchStopFailed(int reason) {
+                    Log.d(TAG, "Assistant-onSearchStopFailed: " + reason);
+                }
+
+                @Override
+                public void onSourceFound(@NonNull BluetoothLeBroadcastMetadata source) {
+                    Log.d(TAG, "Assistant-onSourceFound:");
+                }
+
+                @Override
+                public void onSourceAdded(@NonNull BluetoothDevice sink, int sourceId, int reason) {
+                    Log.d(TAG, "Assistant-onSourceAdded: Device: " + sink
+                            + ", sourceId: " + sourceId);
+                    mMainThreadHandler.post(() -> refreshUi());
+                }
+
+                @Override
+                public void onSourceAddFailed(@NonNull BluetoothDevice sink,
+                        @NonNull BluetoothLeBroadcastMetadata source, int reason) {
+                    Log.d(TAG, "Assistant-onSourceAddFailed: Device: " + sink);
+                }
+
+                @Override
+                public void onSourceModified(@NonNull BluetoothDevice sink, int sourceId,
+                        int reason) {
+                    Log.d(TAG, "Assistant-onSourceModified:");
+                }
+
+                @Override
+                public void onSourceModifyFailed(@NonNull BluetoothDevice sink, int sourceId,
+                        int reason) {
+                    Log.d(TAG, "Assistant-onSourceModifyFailed:");
+                }
+
+                @Override
+                public void onSourceRemoved(@NonNull BluetoothDevice sink, int sourceId,
+                        int reason) {
+                    Log.d(TAG, "Assistant-onSourceRemoved:");
+                }
+
+                @Override
+                public void onSourceRemoveFailed(@NonNull BluetoothDevice sink, int sourceId,
+                        int reason) {
+                    Log.d(TAG, "Assistant-onSourceRemoveFailed:");
+                }
+
+                @Override
+                public void onReceiveStateChanged(@NonNull BluetoothDevice sink, int sourceId,
+                        @NonNull BluetoothLeBroadcastReceiveState state) {
+                    Log.d(TAG, "Assistant-onReceiveStateChanged:");
+                }
+            };
+
     static final int METADATA_BROADCAST_NAME = 0;
     static final int METADATA_BROADCAST_CODE = 1;
 
@@ -131,6 +212,27 @@
     }
 
     @Override
+    public void onStart() {
+        super.onStart();
+        if (!mIsLeBroadcastAssistantCallbackRegistered) {
+            mIsLeBroadcastAssistantCallbackRegistered = true;
+            mMediaOutputController.registerLeBroadcastAssistantServiceCallback(mExecutor,
+                    mBroadcastAssistantCallback);
+        }
+        connectBroadcastWithActiveDevice();
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        if (mIsLeBroadcastAssistantCallbackRegistered) {
+            mIsLeBroadcastAssistantCallbackRegistered = false;
+            mMediaOutputController.unregisterLeBroadcastAssistantServiceCallback(
+                    mBroadcastAssistantCallback);
+        }
+    }
+
+    @Override
     int getHeaderIconRes() {
         return 0;
     }
@@ -224,6 +326,7 @@
         mCurrentBroadcastCode = getBroadcastMetadataInfo(METADATA_BROADCAST_CODE);
         mBroadcastName.setText(mCurrentBroadcastName);
         mBroadcastCode.setText(mCurrentBroadcastCode);
+        refresh(false);
     }
 
     private void inflateBroadcastInfoArea() {
@@ -233,7 +336,7 @@
 
     private void setQrCodeView() {
         //get the Metadata, and convert to BT QR code format.
-        String broadcastMetadata = getBroadcastMetadata();
+        String broadcastMetadata = getLocalBroadcastMetadataQrCodeString();
         if (broadcastMetadata.isEmpty()) {
             //TDOD(b/226708424) Error handling for unable to generate the QR code bitmap
             return;
@@ -249,6 +352,33 @@
         }
     }
 
+    void connectBroadcastWithActiveDevice() {
+        //get the Metadata, and convert to BT QR code format.
+        BluetoothLeBroadcastMetadata broadcastMetadata = getBroadcastMetadata();
+        if (broadcastMetadata == null) {
+            Log.e(TAG, "Error: There is no broadcastMetadata.");
+            return;
+        }
+        MediaDevice mediaDevice = mMediaOutputController.getCurrentConnectedMediaDevice();
+        if (mediaDevice == null || !(mediaDevice instanceof BluetoothMediaDevice)
+                || !mediaDevice.isBLEDevice()) {
+            Log.e(TAG, "Error: There is no active BT LE device.");
+            return;
+        }
+        BluetoothDevice sink = ((BluetoothMediaDevice) mediaDevice).getCachedDevice().getDevice();
+        Log.d(TAG, "The broadcastMetadata broadcastId: " + broadcastMetadata.getBroadcastId()
+                + ", the device: " + sink.getAnonymizedAddress());
+
+        if (mMediaOutputController.isThereAnyBroadcastSourceIntoSinkDevice(sink)) {
+            Log.d(TAG, "The sink device has the broadcast source now.");
+            return;
+        }
+        if (!mMediaOutputController.addSourceIntoSinkDeviceWithBluetoothLeAssistant(sink,
+                broadcastMetadata, /*isGroupOp=*/ true)) {
+            Log.e(TAG, "Error: Source add failed");
+        }
+    }
+
     private void updateBroadcastCodeVisibility() {
         mBroadcastCode.setTransformationMethod(
                 mIsPasswordHide ? HideReturnsTransformationMethod.getInstance()
@@ -282,7 +412,11 @@
         mAlertDialog.show();
     }
 
-    private String getBroadcastMetadata() {
+    private String getLocalBroadcastMetadataQrCodeString() {
+        return mMediaOutputController.getLocalBroadcastMetadataQrCodeString();
+    }
+
+    private BluetoothLeBroadcastMetadata getBroadcastMetadata() {
         return mMediaOutputController.getBroadcastMetadata();
     }
 
@@ -314,6 +448,17 @@
     }
 
     @Override
+    public boolean isBroadcastSupported() {
+        boolean isBluetoothLeDevice = false;
+        if (mMediaOutputController.getCurrentConnectedMediaDevice() != null) {
+            isBluetoothLeDevice = mMediaOutputController.isBluetoothLeDevice(
+                    mMediaOutputController.getCurrentConnectedMediaDevice());
+        }
+
+        return mMediaOutputController.isBroadcastSupported() && isBluetoothLeDevice;
+    }
+
+    @Override
     public void handleLeBroadcastStarted() {
         mRetryCount = 0;
         if (mAlertDialog != null) {
@@ -332,6 +477,7 @@
 
     @Override
     public void handleLeBroadcastMetadataChanged() {
+        Log.d(TAG, "handleLeBroadcastMetadataChanged:");
         refreshUi();
     }
 
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 f3f17d1..9ebc8e4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -25,7 +25,11 @@
 import android.app.KeyguardManager;
 import android.app.Notification;
 import android.app.WallpaperColors;
+import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothLeBroadcast;
+import android.bluetooth.BluetoothLeBroadcastAssistant;
+import android.bluetooth.BluetoothLeBroadcastMetadata;
+import android.bluetooth.BluetoothLeBroadcastReceiveState;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.DialogInterface;
@@ -66,6 +70,7 @@
 import com.android.settingslib.Utils;
 import com.android.settingslib.bluetooth.BluetoothUtils;
 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastMetadata;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.settingslib.media.InfoMediaManager;
@@ -1049,7 +1054,7 @@
                 ALLOWLIST_DURATION_MS);
     }
 
-    String getBroadcastMetadata() {
+    String getLocalBroadcastMetadataQrCodeString() {
         LocalBluetoothLeBroadcast broadcast =
                 mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastProfile();
         if (broadcast == null) {
@@ -1061,6 +1066,17 @@
         return metadata != null ? metadata.convertToQrCodeString() : "";
     }
 
+    BluetoothLeBroadcastMetadata getBroadcastMetadata() {
+        LocalBluetoothLeBroadcast broadcast =
+                mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastProfile();
+        if (broadcast == null) {
+            Log.d(TAG, "getBroadcastMetadata: LE Audio Broadcast is null");
+            return null;
+        }
+
+        return broadcast.getLatestBluetoothLeBroadcastMetadata();
+    }
+
     boolean isActiveRemoteDevice(@NonNull MediaDevice device) {
         final List<String> features = device.getFeatures();
         return (features.contains(MediaRoute2Info.FEATURE_REMOTE_PLAYBACK)
@@ -1121,7 +1137,7 @@
         return true;
     }
 
-    void registerLeBroadcastServiceCallBack(
+    void registerLeBroadcastServiceCallback(
             @NonNull @CallbackExecutor Executor executor,
             @NonNull BluetoothLeBroadcast.Callback callback) {
         LocalBluetoothLeBroadcast broadcast =
@@ -1130,10 +1146,11 @@
             Log.d(TAG, "The broadcast profile is null");
             return;
         }
+        Log.d(TAG, "Register LE broadcast callback");
         broadcast.registerServiceCallBack(executor, callback);
     }
 
-    void unregisterLeBroadcastServiceCallBack(
+    void unregisterLeBroadcastServiceCallback(
             @NonNull BluetoothLeBroadcast.Callback callback) {
         LocalBluetoothLeBroadcast broadcast =
                 mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastProfile();
@@ -1141,9 +1158,59 @@
             Log.d(TAG, "The broadcast profile is null");
             return;
         }
+        Log.d(TAG, "Unregister LE broadcast callback");
         broadcast.unregisterServiceCallBack(callback);
     }
 
+    boolean isThereAnyBroadcastSourceIntoSinkDevice(BluetoothDevice sink) {
+        LocalBluetoothLeBroadcastAssistant assistant =
+                mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
+        if (assistant == null) {
+            Log.d(TAG, "The broadcast assistant profile is null");
+            return false;
+        }
+        List<BluetoothLeBroadcastReceiveState> sourceList = assistant.getAllSources(sink);
+        Log.d(TAG, "isThereAnyBroadcastSourceIntoSinkDevice: List size: " + sourceList.size());
+        return !sourceList.isEmpty();
+    }
+
+    boolean addSourceIntoSinkDeviceWithBluetoothLeAssistant(BluetoothDevice sink,
+            BluetoothLeBroadcastMetadata metadata, boolean isGroupOp) {
+        LocalBluetoothLeBroadcastAssistant assistant =
+                mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
+        if (assistant == null) {
+            Log.d(TAG, "The broadcast assistant profile is null");
+            return false;
+        }
+        assistant.addSource(sink, metadata, isGroupOp);
+        return true;
+    }
+
+    void registerLeBroadcastAssistantServiceCallback(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull BluetoothLeBroadcastAssistant.Callback callback) {
+        LocalBluetoothLeBroadcastAssistant assistant =
+                mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
+        if (assistant == null) {
+            Log.d(TAG, "The broadcast assistant profile is null");
+            return;
+        }
+        Log.d(TAG, "Register LE broadcast assistant callback");
+        assistant.registerServiceCallBack(executor, callback);
+    }
+
+    void unregisterLeBroadcastAssistantServiceCallback(
+            @NonNull BluetoothLeBroadcastAssistant.Callback callback) {
+        LocalBluetoothLeBroadcastAssistant assistant =
+                mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
+        if (assistant == null) {
+            Log.d(TAG, "The broadcast assistant profile is null");
+            return;
+        }
+        Log.d(TAG, "Unregister LE broadcast assistant callback");
+        assistant.unregisterServiceCallBack(callback);
+    }
+
     private boolean isPlayBackInfoLocal() {
         return mMediaController != null
                 && mMediaController.getPlaybackInfo() != null
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractor.kt b/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractor.kt
index 3a4ea3e..e352c61 100644
--- a/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractor.kt
@@ -182,6 +182,26 @@
                 interactionState = null
                 true
             }
+            MotionEvent.ACTION_POINTER_UP -> {
+                val removedPointerId = event.getPointerId(event.actionIndex)
+                if (removedPointerId == interactionState?.pointerId && event.pointerCount > 1) {
+                    // We removed the original pointer but there must be another pointer because the
+                    // gesture is still ongoing. Let's switch to that pointer.
+                    interactionState =
+                        event.firstUnremovedPointerId(removedPointerId)?.let { replacementPointerId
+                            ->
+                            interactionState?.copy(
+                                pointerId = replacementPointerId,
+                                // We want to update the currentY of our state so that the
+                                // transition to the next pointer doesn't report a big jump between
+                                // the Y coordinate of the removed pointer and the Y coordinate of
+                                // the replacement pointer.
+                                currentY = event.getY(replacementPointerId),
+                            )
+                        }
+                }
+                true
+            }
             MotionEvent.ACTION_CANCEL -> {
                 if (isDraggingShade()) {
                     // Our drag gesture was canceled by the system. This happens primarily in one of
@@ -219,4 +239,17 @@
     private fun isDraggingShade(): Boolean {
         return interactionState?.isDraggingShade ?: false
     }
+
+    /**
+     * Returns the index of the first pointer that is not [removedPointerId] or `null`, if there is
+     * no other pointer.
+     */
+    private fun MotionEvent.firstUnremovedPointerId(removedPointerId: Int): Int? {
+        return (0 until pointerCount)
+            .firstOrNull { pointerIndex ->
+                val pointerId = getPointerId(pointerIndex)
+                pointerId != removedPointerId
+            }
+            ?.let { pointerIndex -> getPointerId(pointerIndex) }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
index 0d5a3fd..a29eb3b 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
@@ -56,7 +56,8 @@
 internal const val MIN_DURATION_ACTIVE_BEFORE_INACTIVE_ANIMATION = 300L
 private const val MIN_DURATION_ACTIVE_AFTER_INACTIVE_ANIMATION = 130L
 private const val MIN_DURATION_CANCELLED_ANIMATION = 200L
-private const val MIN_DURATION_COMMITTED_ANIMATION = 120L
+private const val MIN_DURATION_COMMITTED_ANIMATION = 80L
+private const val MIN_DURATION_COMMITTED_AFTER_FLING_ANIMATION = 120L
 private const val MIN_DURATION_INACTIVE_BEFORE_FLUNG_ANIMATION = 50L
 private const val MIN_DURATION_FLING_ANIMATION = 160L
 
@@ -918,7 +919,7 @@
                 if (previousState == GestureState.FLUNG) {
                     updateRestingArrowDimens()
                     mainHandler.postDelayed(onEndSetGoneStateListener.runnable,
-                            MIN_DURATION_COMMITTED_ANIMATION)
+                            MIN_DURATION_COMMITTED_AFTER_FLING_ANIMATION)
                 } else {
                     mView.popScale(POP_ON_FLING_SCALE)
                     mainHandler.postDelayed(onAlphaEndSetGoneStateListener.runnable,
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
index 35b6c15..6ce6f0d 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
@@ -219,7 +219,7 @@
                         height = getDimen(R.dimen.navigation_edge_active_background_height),
                         edgeCornerRadius = getDimen(R.dimen.navigation_edge_active_edge_corners),
                         farCornerRadius = getDimen(R.dimen.navigation_edge_active_far_corners),
-                        widthSpring = createSpring(650f, 0.75f),
+                        widthSpring = createSpring(850f, 0.75f),
                         heightSpring = createSpring(10000f, 1f),
                         edgeCornerRadiusSpring = createSpring(600f, 0.36f),
                         farCornerRadiusSpring = createSpring(2500f, 0.855f),
@@ -274,8 +274,8 @@
                         farCornerRadiusSpring = flungCommittedFarCornerSpring,
                         alphaSpring = createSpring(1400f, 1f),
                 ),
-                scale = 0.85f,
-                scaleSpring = createSpring(6000f, 1f),
+                scale = 0.86f,
+                scaleSpring = createSpring(5700f, 1f),
         )
 
         flungIndicator = committedIndicator.copy(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
index 27fe747..a352f23 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
@@ -234,6 +234,14 @@
         })
     }
 
+    fun logNoPulsingNotificationHidden(entry: NotificationEntry) {
+        buffer.log(TAG, DEBUG, {
+            str1 = entry.logKey
+        }, {
+            "No pulsing: notification hidden on lock screen: $str1"
+        })
+    }
+
     fun logNoPulsingNotImportant(entry: NotificationEntry) {
         buffer.log(TAG, DEBUG, {
             str1 = entry.logKey
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
index bfb6416..9a1747a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
@@ -61,6 +61,12 @@
          */
         NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR(false),
         /**
+         * Notification should not FSI due to having suppressive BubbleMetadata. This blocks a
+         * potentially malicious use of flags that previously allowed apps to escalate a HUN to an
+         * FSI even while the device was unlocked.
+         */
+        NO_FSI_SUPPRESSIVE_BUBBLE_METADATA(false),
+        /**
          * Device screen is off, so the FSI should launch.
          */
         FSI_DEVICE_NOT_INTERACTIVE(true),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index 4aaa7ca..ca762fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -18,6 +18,7 @@
 
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
 import static com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD;
+import static com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_SUPPRESSIVE_BUBBLE_METADATA;
 import static com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR;
 import static com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.HUN_SNOOZE_BYPASSED_POTENTIALLY_SUPPRESSED_FSI;
 
@@ -82,6 +83,9 @@
         @UiEvent(doc = "FSI suppressed for suppressive GroupAlertBehavior")
         FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR(1235),
 
+        @UiEvent(doc = "FSI suppressed for suppressive BubbleMetadata")
+        FSI_SUPPRESSED_SUPPRESSIVE_BUBBLE_METADATA(1353),
+
         @UiEvent(doc = "FSI suppressed for requiring neither HUN nor keyguard")
         FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD(1236),
 
@@ -273,6 +277,16 @@
                     suppressedByDND);
         }
 
+        // If the notification has suppressive BubbleMetadata, block FSI and warn.
+        Notification.BubbleMetadata bubbleMetadata = sbn.getNotification().getBubbleMetadata();
+        if (bubbleMetadata != null && bubbleMetadata.isNotificationSuppressed()) {
+            // b/274759612: Detect and report an event when a notification has both an FSI and a
+            // suppressive BubbleMetadata, and now correctly block the FSI from firing.
+            return getDecisionGivenSuppression(
+                    FullScreenIntentDecision.NO_FSI_SUPPRESSIVE_BUBBLE_METADATA,
+                    suppressedByDND);
+        }
+
         // Notification is coming from a suspended package, block FSI
         if (entry.getRanking().isSuspended()) {
             return getDecisionGivenSuppression(FullScreenIntentDecision.NO_FSI_SUSPENDED,
@@ -351,6 +365,14 @@
                 mLogger.logNoFullscreenWarning(entry,
                         decision + ": GroupAlertBehavior will prevent HUN");
                 return;
+            case NO_FSI_SUPPRESSIVE_BUBBLE_METADATA:
+                android.util.EventLog.writeEvent(0x534e4554, "274759612", uid,
+                        "bubbleMetadata");
+                mUiEventLogger.log(FSI_SUPPRESSED_SUPPRESSIVE_BUBBLE_METADATA, uid,
+                        packageName);
+                mLogger.logNoFullscreenWarning(entry,
+                        decision + ": BubbleMetadata may prevent HUN");
+                return;
             case NO_FSI_NO_HUN_OR_KEYGUARD:
                 android.util.EventLog.writeEvent(0x534e4554, "231322873", uid,
                         "no hun or keyguard");
@@ -482,6 +504,12 @@
             return false;
         }
 
+        if (entry.getRanking().getLockscreenVisibilityOverride()
+                == Notification.VISIBILITY_PRIVATE) {
+            if (log) mLogger.logNoPulsingNotificationHidden(entry);
+            return false;
+        }
+
         if (entry.getImportance() < NotificationManager.IMPORTANCE_DEFAULT) {
             if (log) mLogger.logNoPulsingNotImportant(entry);
             return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java
index 797038d..ce6dd89 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java
@@ -110,7 +110,7 @@
 
     public void bind(@Nullable CharSequence title, @Nullable CharSequence text,
             @Nullable View contentView) {
-        mTitleView.setText(title.toString());
+        mTitleView.setText(title != null ? title.toString() : title);
         mTitleView.setVisibility(TextUtils.isEmpty(title) ? GONE : VISIBLE);
         if (TextUtils.isEmpty(text)) {
             mTextView.setVisibility(GONE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
index adbfa75..5f4c926 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
@@ -290,6 +290,9 @@
         int drawableId = show ? com.android.internal.R.drawable.ic_collapse_notification
                 : com.android.internal.R.drawable.ic_expand_notification;
         mExpandButton.setImageResource(drawableId);
+        mExpandButton.setContentDescription(mContext.getString(show
+                ? com.android.internal.R.string.expand_button_content_description_expanded
+                : com.android.internal.R.string.expand_button_content_description_collapsed));
         if (mExpanded != show) {
             mExpanded = show;
             animateSnoozeOptions(show);
@@ -373,6 +376,7 @@
         } else if (id == R.id.notification_snooze) {
             // Toggle snooze options
             showSnoozeOptions(!mExpanded);
+            mSnoozeView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
             mMetricsLogger.write(!mExpanded ? OPTIONS_OPEN_LOG : OPTIONS_CLOSE_LOG);
         } else {
             // Undo snooze was selected
@@ -401,6 +405,7 @@
     public View getContentView() {
         // Reset the view before use
         setSelected(mDefaultOption, false);
+        showSnoozeOptions(false);
         return this;
     }
 
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 769edf7..1b49717 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
@@ -56,7 +56,6 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.ExpandHelper;
 import com.android.systemui.Gefingerpoken;
-import com.android.systemui.SwipeHelper;
 import com.android.systemui.classifier.Classifier;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -748,7 +747,6 @@
                 !mKeyguardBypassController.getBypassEnabled());
 
         mSwipeHelper = mNotificationSwipeHelperBuilder
-                .setSwipeDirection(SwipeHelper.X)
                 .setNotificationCallback(mNotificationCallback)
                 .setOnMenuEventListener(mMenuEventListener)
                 .build();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
index b476b68..91f53b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
@@ -76,11 +76,10 @@
             ViewConfiguration viewConfiguration,
             FalsingManager falsingManager,
             FeatureFlags featureFlags,
-            int swipeDirection,
             NotificationCallback callback,
             NotificationMenuRowPlugin.OnMenuEventListener menuListener,
             NotificationRoundnessManager notificationRoundnessManager) {
-        super(swipeDirection, callback, resources, viewConfiguration, falsingManager, featureFlags);
+        super(callback, resources, viewConfiguration, falsingManager, featureFlags);
         mNotificationRoundnessManager = notificationRoundnessManager;
         mUseRoundnessSourceTypes = featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES);
         mMenuListener = menuListener;
@@ -416,22 +415,12 @@
     }
 
     @Override
-    public boolean swipedFastEnough(float translation, float viewSize) {
-        return swipedFastEnough();
-    }
-
-    @Override
     @VisibleForTesting
     protected boolean swipedFastEnough() {
         return super.swipedFastEnough();
     }
 
     @Override
-    public boolean swipedFarEnough(float translation, float viewSize) {
-        return swipedFarEnough();
-    }
-
-    @Override
     @VisibleForTesting
     protected boolean swipedFarEnough() {
         return super.swipedFarEnough();
@@ -554,7 +543,6 @@
         private final ViewConfiguration mViewConfiguration;
         private final FalsingManager mFalsingManager;
         private final FeatureFlags mFeatureFlags;
-        private int mSwipeDirection;
         private NotificationCallback mNotificationCallback;
         private NotificationMenuRowPlugin.OnMenuEventListener mOnMenuEventListener;
         private NotificationRoundnessManager mNotificationRoundnessManager;
@@ -570,11 +558,6 @@
             mNotificationRoundnessManager = notificationRoundnessManager;
         }
 
-        Builder setSwipeDirection(int swipeDirection) {
-            mSwipeDirection = swipeDirection;
-            return this;
-        }
-
         Builder setNotificationCallback(NotificationCallback notificationCallback) {
             mNotificationCallback = notificationCallback;
             return this;
@@ -588,7 +571,7 @@
 
         NotificationSwipeHelper build() {
             return new NotificationSwipeHelper(mResources, mViewConfiguration, mFalsingManager,
-                    mFeatureFlags, mSwipeDirection, mNotificationCallback, mOnMenuEventListener,
+                    mFeatureFlags, mNotificationCallback, mOnMenuEventListener,
                     mNotificationRoundnessManager);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index f06b5db..f9493f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -281,7 +281,6 @@
     private boolean mLastScreenOffAnimationPlaying;
     private float mQsExpansion;
     final Set<KeyguardViewManagerCallback> mCallbacks = new HashSet<>();
-    private boolean mIsModernAlternateBouncerEnabled;
     private boolean mIsBackAnimationEnabled;
     private final boolean mUdfpsNewTouchDetectionEnabled;
     private final UdfpsOverlayInteractor mUdfpsOverlayInteractor;
@@ -363,7 +362,6 @@
         mPrimaryBouncerView = primaryBouncerView;
         mFoldAodAnimationController = sysUIUnfoldComponent
                 .map(SysUIUnfoldComponent::getFoldAodAnimationController).orElse(null);
-        mIsModernAlternateBouncerEnabled = featureFlags.isEnabled(Flags.MODERN_ALTERNATE_BOUNCER);
         mAlternateBouncerInteractor = alternateBouncerInteractor;
         mIsBackAnimationEnabled =
                 featureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_BOUNCER_ANIM);
@@ -395,35 +393,6 @@
         registerListeners();
     }
 
-    /**
-     * Sets the given legacy alternate bouncer to null if it's the current alternate bouncer. Else,
-     * does nothing. Only used if modern alternate bouncer is NOT enabled.
-     */
-    public void removeLegacyAlternateBouncer(
-            @NonNull LegacyAlternateBouncer alternateBouncerLegacy) {
-        if (!mIsModernAlternateBouncerEnabled) {
-            if (Objects.equals(mAlternateBouncerInteractor.getLegacyAlternateBouncer(),
-                    alternateBouncerLegacy)) {
-                mAlternateBouncerInteractor.setLegacyAlternateBouncer(null);
-                hideAlternateBouncer(true);
-            }
-        }
-    }
-
-    /**
-     * Sets a new legacy alternate bouncer. Only used if modern alternate bouncer is NOT enabled.
-     */
-    public void setLegacyAlternateBouncer(@NonNull LegacyAlternateBouncer alternateBouncerLegacy) {
-        if (!mIsModernAlternateBouncerEnabled) {
-            if (!Objects.equals(mAlternateBouncerInteractor.getLegacyAlternateBouncer(),
-                    alternateBouncerLegacy)) {
-                mAlternateBouncerInteractor.setLegacyAlternateBouncer(alternateBouncerLegacy);
-                hideAlternateBouncer(true);
-            }
-        }
-
-    }
-
 
     /**
      * Sets the given OccludingAppBiometricUI to null if it's the current auth interceptor. Else,
@@ -1386,7 +1355,6 @@
 
     public void dump(PrintWriter pw) {
         pw.println("StatusBarKeyguardViewManager:");
-        pw.println("  mIsModernAlternateBouncerEnabled: " + mIsModernAlternateBouncerEnabled);
         pw.println("  mRemoteInputActive: " + mRemoteInputActive);
         pw.println("  mDozing: " + mDozing);
         pw.println("  mAfterKeyguardGoneAction: " + mAfterKeyguardGoneAction);
@@ -1585,28 +1553,6 @@
     }
 
     /**
-     * @deprecated Delegate used to send show and hide events to an alternate bouncer.
-     */
-    public interface LegacyAlternateBouncer {
-        /**
-         * Show alternate authentication bouncer.
-         * @return whether alternate auth method was newly shown
-         */
-        boolean showAlternateBouncer();
-
-        /**
-         * Hide alternate authentication bouncer
-         * @return whether the alternate auth method was newly hidden
-         */
-        boolean hideAlternateBouncer();
-
-        /**
-         * @return true if the alternate auth bouncer is showing
-         */
-        boolean isShowingAlternateBouncer();
-    }
-
-    /**
      * Delegate used to send show and hide events to an alternate authentication method instead of
      * the regular pin/pattern/password bouncer.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt
index 32c64f4..8c61ada 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt
@@ -36,6 +36,7 @@
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.settings.GlobalSettings
 import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.wrapper.BuildInfo
 import java.io.PrintWriter
 import java.util.concurrent.Executor
 import java.util.concurrent.atomic.AtomicBoolean
@@ -47,6 +48,7 @@
     private val globalSettings: GlobalSettings,
     private val userTracker: UserTracker,
     private val dumpManager: DumpManager,
+    private val buildInfo: BuildInfo,
     @Background private val backgroundHandler: Handler,
     @Main private val mainExecutor: Executor
 ) : DeviceProvisionedController,
@@ -187,7 +189,7 @@
     }
 
     override fun isFrpActive(): Boolean {
-        return frpActive.get()
+        return frpActive.get() && !buildInfo.isDebuggable
     }
 
     override fun isUserSetup(user: Int): Boolean {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
index 50645e5..7ce2b1c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
@@ -17,6 +17,7 @@
 package com.android.keyguard;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -151,10 +152,19 @@
                 false);
     }
 
-
     @Test
     public void testReset() {
         mKeyguardAbsKeyInputViewController.reset();
         verify(mKeyguardMessageAreaController).setMessage("", false);
+        verify(mAbsKeyInputView).resetPasswordText(false, false);
+        verify(mLockPatternUtils).getLockoutAttemptDeadline(anyInt());
+    }
+
+    @Test
+    public void onResume_Reset() {
+        mKeyguardAbsKeyInputViewController.onResume(KeyguardSecurityView.VIEW_REVEALED);
+        verify(mKeyguardMessageAreaController).setMessage("", false);
+        verify(mAbsKeyInputView).resetPasswordText(false, false);
+        verify(mLockPatternUtils).getLockoutAttemptDeadline(anyInt());
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
index 85dbdb8..6ae28b7 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
@@ -31,6 +31,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Mock
 import org.mockito.Mockito.never
@@ -119,4 +120,24 @@
     mKeyguardPatternViewController.startAppearAnimation()
     verify(mKeyguardMessageAreaController, never()).setMessage(anyString(), anyBoolean())
   }
+
+  @Test
+  fun reset() {
+    mKeyguardPatternViewController.reset()
+    verify(mLockPatternView).setInStealthMode(anyBoolean())
+    verify(mLockPatternView).enableInput()
+    verify(mLockPatternView).setEnabled(true)
+    verify(mLockPatternView).clearPattern()
+    verify(mLockPatternUtils).getLockoutAttemptDeadline(anyInt())
+  }
+
+  @Test
+  fun resume() {
+    mKeyguardPatternViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
+    verify(mLockPatternView).setInStealthMode(anyBoolean())
+    verify(mLockPatternView).enableInput()
+    verify(mLockPatternView).setEnabled(true)
+    verify(mLockPatternView).clearPattern()
+    verify(mLockPatternUtils).getLockoutAttemptDeadline(anyInt())
+  }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index a1af8e8..70476aa 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -129,10 +129,11 @@
     }
 
     @Test
-    fun startAppearAnimation_withAutoPinConfirmation() {
+    fun startAppearAnimation_withAutoPinConfirmationFailedPasswordAttemptsLessThan5() {
         `when`(featureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)).thenReturn(true)
         `when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6)
         `when`(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true)
+        `when`(lockPatternUtils.getCurrentFailedPasswordAttempts(anyInt())).thenReturn(3)
         `when`(passwordTextView.text).thenReturn("")
 
         pinViewController.startAppearAnimation()
@@ -141,4 +142,19 @@
         verify(passwordTextView).setUsePinShapes(true)
         verify(passwordTextView).setIsPinHinting(true)
     }
+
+    @Test
+    fun startAppearAnimation_withAutoPinConfirmationFailedPasswordAttemptsMoreThan5() {
+        `when`(featureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)).thenReturn(true)
+        `when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6)
+        `when`(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true)
+        `when`(lockPatternUtils.getCurrentFailedPasswordAttempts(anyInt())).thenReturn(6)
+        `when`(passwordTextView.text).thenReturn("")
+
+        pinViewController.startAppearAnimation()
+        verify(deleteButton).visibility = View.INVISIBLE
+        verify(enterButton).visibility = View.VISIBLE
+        verify(passwordTextView).setUsePinShapes(true)
+        verify(passwordTextView).setIsPinHinting(true)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
index afb54d2..eaf7b1e 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
@@ -35,6 +35,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.flags.FeatureFlags;
 
@@ -42,6 +43,7 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
@@ -76,6 +78,8 @@
     private KeyguardSecurityCallback mKeyguardSecurityCallback;
     @Mock
     private FeatureFlags mFeatureFlags;
+    @Mock
+    private ViewMediatorCallback mViewMediatorCallback;
 
     private KeyguardSecurityViewFlipperController mKeyguardSecurityViewFlipperController;
 
@@ -92,7 +96,7 @@
 
         mKeyguardSecurityViewFlipperController = new KeyguardSecurityViewFlipperController(mView,
                 mLayoutInflater, mAsyncLayoutInflater, mKeyguardSecurityViewControllerFactory,
-                mEmergencyButtonControllerFactory, mFeatureFlags);
+                mEmergencyButtonControllerFactory, mFeatureFlags, mViewMediatorCallback);
     }
 
     @Test
@@ -123,6 +127,19 @@
     }
 
     @Test
+    public void asynchronouslyInflateView_setNeedsInput() {
+        ArgumentCaptor<AsyncLayoutInflater.OnInflateFinishedListener> argumentCaptor =
+                ArgumentCaptor.forClass(AsyncLayoutInflater.OnInflateFinishedListener.class);
+        mKeyguardSecurityViewFlipperController.asynchronouslyInflateView(SecurityMode.PIN,
+                mKeyguardSecurityCallback, null);
+        verify(mAsyncLayoutInflater).inflate(anyInt(), eq(mView), argumentCaptor.capture());
+        argumentCaptor.getValue().onInflateFinished(
+                LayoutInflater.from(getContext()).inflate(R.layout.keyguard_password_view, null),
+                R.layout.keyguard_password_view, mView);
+        verify(mViewMediatorCallback).setNeedsInput(anyBoolean());
+    }
+
+    @Test
     public void onDensityOrFontScaleChanged() {
         mKeyguardSecurityViewFlipperController.clearViews();
         verify(mView).removeAllViews();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index a8b4254..4ed4e20 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -1360,9 +1360,7 @@
     public void startsListeningForSfps_whenKeyguardIsVisible_ifRequireInteractiveToAuthEnabled()
             throws RemoteException {
         // SFPS supported and enrolled
-        final ArrayList<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
-        props.add(newFingerprintSensorPropertiesInternal(TYPE_POWER_BUTTON));
-        when(mAuthController.getSfpsProps()).thenReturn(props);
+        when(mAuthController.isSfpsSupported()).thenReturn(true);
         when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
 
         // WHEN require interactive to auth is disabled, and keyguard is not awake
@@ -1401,9 +1399,7 @@
     public void notListeningForSfps_whenGoingToSleep_ifRequireInteractiveToAuthEnabled()
             throws RemoteException {
         // GIVEN SFPS supported and enrolled
-        final ArrayList<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
-        props.add(newFingerprintSensorPropertiesInternal(TYPE_POWER_BUTTON));
-        when(mAuthController.getSfpsProps()).thenReturn(props);
+        when(mAuthController.isSfpsSupported()).thenReturn(true);
         when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
 
         // GIVEN Preconditions for sfps auth to run
@@ -2843,8 +2839,7 @@
     }
 
     private void givenUdfpsSupported() {
-        Assert.assertFalse(mFingerprintSensorProperties.isEmpty());
-        when(mAuthController.getUdfpsProps()).thenReturn(mFingerprintSensorProperties);
+        when(mAuthController.isUdfpsSupported()).thenReturn(true);
         Assert.assertTrue(mKeyguardUpdateMonitor.isUdfpsSupported());
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
index 0ab675c..33345b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
@@ -51,13 +51,10 @@
 import android.view.WindowMetrics
 import androidx.test.filters.SmallTest
 import com.airbnb.lottie.LottieAnimationView
-import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.SysuiTestableContext
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags.MODERN_ALTERNATE_BOUNCER
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
@@ -113,7 +110,6 @@
 
     private lateinit var keyguardBouncerRepository: FakeKeyguardBouncerRepository
     private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
-    private val featureFlags = FakeFeatureFlags()
     private val executor = FakeExecutor(FakeSystemClock())
     private lateinit var overlayController: ISidefpsController
     private lateinit var sideFpsController: SideFpsController
@@ -134,7 +130,6 @@
 
     @Before
     fun setup() {
-        featureFlags.set(MODERN_ALTERNATE_BOUNCER, true)
         keyguardBouncerRepository = FakeKeyguardBouncerRepository()
         alternateBouncerInteractor =
             AlternateBouncerInteractor(
@@ -144,8 +139,6 @@
                 FakeBiometricSettingsRepository(),
                 FakeDeviceEntryFingerprintAuthRepository(),
                 FakeSystemClock(),
-                mock(KeyguardUpdateMonitor::class.java),
-                featureFlags,
             )
 
         context.addMockSystemService(DisplayManager::class.java, displayManager)
@@ -246,7 +239,6 @@
                 handler,
                 alternateBouncerInteractor,
                 TestCoroutineScope(),
-                featureFlags,
                 dumpManager,
             )
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
index dbbc266..a878aec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
@@ -147,7 +147,6 @@
 
     protected UdfpsKeyguardViewController createUdfpsKeyguardViewController(
             boolean useModernBouncer, boolean useExpandedOverlay) {
-        mFeatureFlags.set(Flags.MODERN_ALTERNATE_BOUNCER, useModernBouncer);
         mFeatureFlags.set(Flags.UDFPS_NEW_TOUCH_DETECTION, useExpandedOverlay);
         UdfpsKeyguardViewController controller = new UdfpsKeyguardViewController(
                 mView,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
index cefa9b1..b848413 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
@@ -21,9 +21,7 @@
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardSecurityModel
-import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.BouncerView
 import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
@@ -100,8 +98,6 @@
                 mock(BiometricSettingsRepository::class.java),
                 mock(DeviceEntryFingerprintAuthRepository::class.java),
                 mock(SystemClock::class.java),
-                mock(KeyguardUpdateMonitor::class.java),
-                mock(FeatureFlags::class.java)
             )
         return createUdfpsKeyguardViewController(
             /* useModernBouncer */ true, /* useExpandedOverlay */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
index 0519a44..70f766f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
@@ -21,6 +21,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.AuthController
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.util.mockito.whenever
@@ -37,6 +38,7 @@
 import org.mockito.ArgumentCaptor
 import org.mockito.Captor
 import org.mockito.Mock
+import org.mockito.Mockito.atLeastOnce
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
@@ -46,7 +48,9 @@
 class DeviceEntryFingerprintAuthRepositoryTest : SysuiTestCase() {
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     @Mock private lateinit var dumpManager: DumpManager
-    @Captor private lateinit var callbackCaptor: ArgumentCaptor<KeyguardUpdateMonitorCallback>
+    @Mock private lateinit var authController: AuthController
+    @Captor
+    private lateinit var updateMonitorCallback: ArgumentCaptor<KeyguardUpdateMonitorCallback>
 
     private lateinit var testScope: TestScope
 
@@ -59,6 +63,7 @@
 
         underTest =
             DeviceEntryFingerprintAuthRepositoryImpl(
+                authController,
                 keyguardUpdateMonitor,
                 testScope.backgroundScope,
                 dumpManager,
@@ -67,7 +72,7 @@
 
     @After
     fun tearDown() {
-        verify(keyguardUpdateMonitor).removeCallback(callbackCaptor.value)
+        //        verify(keyguardUpdateMonitor).removeCallback(updateMonitorCallback.value)
     }
 
     @Test
@@ -76,8 +81,8 @@
             val isLockedOutValue = collectLastValue(underTest.isLockedOut)
             runCurrent()
 
-            verify(keyguardUpdateMonitor).registerCallback(callbackCaptor.capture())
-            val callback = callbackCaptor.value
+            verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture())
+            val callback = updateMonitorCallback.value
             whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(true)
 
             callback.onLockedOutStateChanged(BiometricSourceType.FACE)
@@ -90,4 +95,63 @@
             callback.onLockedOutStateChanged(BiometricSourceType.FINGERPRINT)
             assertThat(isLockedOutValue()).isFalse()
         }
+
+    @Test
+    fun fpRunningStateIsPropagated() =
+        testScope.runTest {
+            val isRunning = collectLastValue(underTest.isRunning)
+            whenever(keyguardUpdateMonitor.isFingerprintDetectionRunning).thenReturn(true)
+
+            // Initial value is available
+            assertThat(isRunning()).isTrue()
+
+            verify(keyguardUpdateMonitor, atLeastOnce())
+                .registerCallback(updateMonitorCallback.capture())
+            invokeOnCallback {
+                it.onBiometricRunningStateChanged(false, BiometricSourceType.FINGERPRINT)
+            }
+
+            assertThat(isRunning()).isFalse()
+
+            invokeOnCallback { it.onBiometricRunningStateChanged(true, BiometricSourceType.FACE) }
+
+            assertThat(isRunning()).isFalse()
+
+            updateMonitorCallback.value.onBiometricRunningStateChanged(
+                true,
+                BiometricSourceType.FINGERPRINT
+            )
+            assertThat(isRunning()).isTrue()
+        }
+
+    private fun invokeOnCallback(action: (KeyguardUpdateMonitorCallback) -> Unit) {
+        updateMonitorCallback.allValues.forEach { action(it) }
+    }
+
+    @Test
+    fun enabledFingerprintTypeProvidesTheCorrectOutput() =
+        testScope.runTest {
+            whenever(authController.isSfpsSupported).thenReturn(true)
+            whenever(authController.isUdfpsSupported).thenReturn(false)
+            whenever(authController.isRearFpsSupported).thenReturn(false)
+
+            assertThat(underTest.availableFpSensorType).isEqualTo(BiometricType.SIDE_FINGERPRINT)
+
+            whenever(authController.isSfpsSupported).thenReturn(false)
+            whenever(authController.isUdfpsSupported).thenReturn(true)
+            whenever(authController.isRearFpsSupported).thenReturn(false)
+
+            assertThat(underTest.availableFpSensorType)
+                .isEqualTo(BiometricType.UNDER_DISPLAY_FINGERPRINT)
+
+            whenever(authController.isSfpsSupported).thenReturn(false)
+            whenever(authController.isUdfpsSupported).thenReturn(false)
+            whenever(authController.isRearFpsSupported).thenReturn(true)
+
+            assertThat(underTest.availableFpSensorType).isEqualTo(BiometricType.REAR_FINGERPRINT)
+
+            whenever(authController.isRearFpsSupported).thenReturn(false)
+
+            assertThat(underTest.availableFpSensorType).isNull()
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
index 86246f7..e7e5969 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
@@ -18,11 +18,8 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.ViewMediatorCallback
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
@@ -40,10 +37,8 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
 import org.mockito.Mock
 import org.mockito.Mockito.mock
-import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -58,9 +53,7 @@
     @Mock private lateinit var statusBarStateController: StatusBarStateController
     @Mock private lateinit var keyguardStateController: KeyguardStateController
     @Mock private lateinit var systemClock: SystemClock
-    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     @Mock private lateinit var bouncerLogger: TableLogBuffer
-    private lateinit var featureFlags: FakeFeatureFlags
 
     @Before
     fun setup() {
@@ -74,7 +67,6 @@
             )
         biometricSettingsRepository = FakeBiometricSettingsRepository()
         deviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
-        featureFlags = FakeFeatureFlags().apply { this.set(Flags.MODERN_ALTERNATE_BOUNCER, true) }
         underTest =
             AlternateBouncerInteractor(
                 statusBarStateController,
@@ -83,8 +75,6 @@
                 biometricSettingsRepository,
                 deviceEntryFingerprintAuthRepository,
                 systemClock,
-                keyguardUpdateMonitor,
-                featureFlags,
             )
     }
 
@@ -135,14 +125,6 @@
     }
 
     @Test
-    fun canShowAlternateBouncerForFingerprint_isDozing() {
-        givenCanShowAlternateBouncer()
-        whenever(statusBarStateController.isDozing).thenReturn(true)
-
-        assertFalse(underTest.canShowAlternateBouncerForFingerprint())
-    }
-
-    @Test
     fun show_whenCanShow() {
         givenCanShowAlternateBouncer()
 
@@ -182,42 +164,6 @@
         assertFalse(bouncerRepository.alternateBouncerVisible.value)
     }
 
-    @Test
-    fun onUnlockedIsFalse_doesNotHide() {
-        // GIVEN alternate bouncer is showing
-        bouncerRepository.setAlternateVisible(true)
-
-        val keyguardStateControllerCallbackCaptor =
-            ArgumentCaptor.forClass(KeyguardStateController.Callback::class.java)
-        verify(keyguardStateController).addCallback(keyguardStateControllerCallbackCaptor.capture())
-
-        // WHEN isUnlocked=false
-        givenCanShowAlternateBouncer()
-        whenever(keyguardStateController.isUnlocked).thenReturn(false)
-        keyguardStateControllerCallbackCaptor.value.onUnlockedChanged()
-
-        // THEN the alternate bouncer is still visible
-        assertTrue(bouncerRepository.alternateBouncerVisible.value)
-    }
-
-    @Test
-    fun onUnlockedChangedIsTrue_hide() {
-        // GIVEN alternate bouncer is showing
-        bouncerRepository.setAlternateVisible(true)
-
-        val keyguardStateControllerCallbackCaptor =
-            ArgumentCaptor.forClass(KeyguardStateController.Callback::class.java)
-        verify(keyguardStateController).addCallback(keyguardStateControllerCallbackCaptor.capture())
-
-        // WHEN isUnlocked=true
-        givenCanShowAlternateBouncer()
-        whenever(keyguardStateController.isUnlocked).thenReturn(true)
-        keyguardStateControllerCallbackCaptor.value.onUnlockedChanged()
-
-        // THEN the alternate bouncer is hidden
-        assertFalse(bouncerRepository.alternateBouncerVisible.value)
-    }
-
     private fun givenCanShowAlternateBouncer() {
         bouncerRepository.setAlternateBouncerUIAvailable(true)
         biometricSettingsRepository.setFingerprintEnrolled(true)
@@ -225,7 +171,6 @@
         biometricSettingsRepository.setFingerprintEnabledByDevicePolicy(true)
         deviceEntryFingerprintAuthRepository.setLockedOut(false)
         whenever(keyguardStateController.isUnlocked).thenReturn(false)
-        whenever(statusBarStateController.isDozing).thenReturn(false)
     }
 
     private fun givenCannotShowAlternateBouncer() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
new file mode 100644
index 0000000..891a6f8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
@@ -0,0 +1,195 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.media.dialog;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.KeyguardManager;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeBroadcastMetadata;
+import android.bluetooth.BluetoothLeBroadcastReceiveState;
+import android.media.AudioManager;
+import android.media.session.MediaSessionManager;
+import android.os.PowerExemptionManager;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+import com.android.settingslib.media.BluetoothMediaDevice;
+import com.android.settingslib.media.LocalMediaManager;
+import com.android.settingslib.media.MediaDevice;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.DialogLaunchAnimator;
+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.statusbar.notification.collection.notifcollection.CommonNotifCollection;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class MediaOutputBroadcastDialogTest extends SysuiTestCase {
+
+    private static final String TEST_PACKAGE = "test_package";
+
+    // Mock
+    private final MediaSessionManager mMediaSessionManager = mock(MediaSessionManager.class);
+    private final LocalBluetoothManager mLocalBluetoothManager = mock(LocalBluetoothManager.class);
+    private final LocalBluetoothProfileManager mLocalBluetoothProfileManager = mock(
+            LocalBluetoothProfileManager.class);
+    private final LocalBluetoothLeBroadcast mLocalBluetoothLeBroadcast = mock(
+            LocalBluetoothLeBroadcast.class);
+    private final LocalBluetoothLeBroadcastAssistant mLocalBluetoothLeBroadcastAssistant = mock(
+            LocalBluetoothLeBroadcastAssistant.class);
+    private final BluetoothLeBroadcastMetadata mBluetoothLeBroadcastMetadata = mock(
+            BluetoothLeBroadcastMetadata.class);
+    private final BluetoothLeBroadcastReceiveState mBluetoothLeBroadcastReceiveState = mock(
+            BluetoothLeBroadcastReceiveState.class);
+    private final ActivityStarter mStarter = mock(ActivityStarter.class);
+    private final BroadcastSender mBroadcastSender = mock(BroadcastSender.class);
+    private final LocalMediaManager mLocalMediaManager = mock(LocalMediaManager.class);
+    private final MediaDevice mBluetoothMediaDevice = mock(BluetoothMediaDevice.class);
+    private final BluetoothDevice mBluetoothDevice = mock(BluetoothDevice.class);
+    private final CachedBluetoothDevice mCachedBluetoothDevice = mock(CachedBluetoothDevice.class);
+    private final CommonNotifCollection mNotifCollection = mock(CommonNotifCollection.class);
+    private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
+    private final NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock(
+            NearbyMediaDevicesManager.class);
+    private final AudioManager mAudioManager = mock(AudioManager.class);
+    private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class);
+    private KeyguardManager mKeyguardManager = mock(KeyguardManager.class);
+    private FeatureFlags mFlags = mock(FeatureFlags.class);
+
+    private MediaOutputBroadcastDialog mMediaOutputBroadcastDialog;
+    private MediaOutputController mMediaOutputController;
+
+    @Before
+    public void setUp() {
+        when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager);
+        when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(null);
+        when(mLocalBluetoothProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(null);
+
+        mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE,
+                mMediaSessionManager, mLocalBluetoothManager, mStarter,
+                mNotifCollection, mDialogLaunchAnimator,
+                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+                mKeyguardManager, mFlags);
+        mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
+        mMediaOutputBroadcastDialog = new MediaOutputBroadcastDialog(mContext, false,
+                mBroadcastSender, mMediaOutputController);
+        mMediaOutputBroadcastDialog.show();
+    }
+
+    @After
+    public void tearDown() {
+        mMediaOutputBroadcastDialog.dismissDialog();
+    }
+
+    @Test
+    public void connectBroadcastWithActiveDevice_noBroadcastMetadata_failToAddSource() {
+        when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+                mLocalBluetoothLeBroadcast);
+        when(mLocalBluetoothLeBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(null);
+        when(mLocalBluetoothProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(
+                mLocalBluetoothLeBroadcastAssistant);
+
+        mMediaOutputBroadcastDialog.connectBroadcastWithActiveDevice();
+
+        verify(mLocalBluetoothLeBroadcastAssistant, never()).addSource(any(), any(), anyBoolean());
+    }
+
+    @Test
+    public void connectBroadcastWithActiveDevice_noConnectedMediaDevice_failToAddSource() {
+        when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+                mLocalBluetoothLeBroadcast);
+        when(mLocalBluetoothLeBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(
+                mBluetoothLeBroadcastMetadata);
+        when(mLocalBluetoothProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(
+                mLocalBluetoothLeBroadcastAssistant);
+        when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(null);
+
+        mMediaOutputBroadcastDialog.connectBroadcastWithActiveDevice();
+
+        verify(mLocalBluetoothLeBroadcastAssistant, never()).addSource(any(), any(), anyBoolean());
+    }
+
+    @Test
+    public void connectBroadcastWithActiveDevice_hasBroadcastSource_failToAddSource() {
+        when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+                mLocalBluetoothLeBroadcast);
+        when(mLocalBluetoothLeBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(
+                mBluetoothLeBroadcastMetadata);
+        when(mLocalBluetoothProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(
+                mLocalBluetoothLeBroadcastAssistant);
+        when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mBluetoothMediaDevice);
+        when(((BluetoothMediaDevice) mBluetoothMediaDevice).getCachedDevice())
+                .thenReturn(mCachedBluetoothDevice);
+        when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+        List<BluetoothLeBroadcastReceiveState> sourceList = new ArrayList<>();
+        sourceList.add(mBluetoothLeBroadcastReceiveState);
+        when(mLocalBluetoothLeBroadcastAssistant.getAllSources(mBluetoothDevice)).thenReturn(
+                sourceList);
+
+        mMediaOutputBroadcastDialog.connectBroadcastWithActiveDevice();
+
+        verify(mLocalBluetoothLeBroadcastAssistant, never()).addSource(any(), any(), anyBoolean());
+    }
+
+    @Test
+    public void connectBroadcastWithActiveDevice_noBroadcastSource_failToAddSource() {
+        when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+                mLocalBluetoothLeBroadcast);
+        when(mLocalBluetoothProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(
+                mLocalBluetoothLeBroadcastAssistant);
+        when(mLocalBluetoothLeBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(
+                mBluetoothLeBroadcastMetadata);
+        when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mBluetoothMediaDevice);
+        when(mBluetoothMediaDevice.isBLEDevice()).thenReturn(true);
+        when(((BluetoothMediaDevice) mBluetoothMediaDevice).getCachedDevice()).thenReturn(
+                mCachedBluetoothDevice);
+        when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+        List<BluetoothLeBroadcastReceiveState> sourceList = new ArrayList<>();
+        when(mLocalBluetoothLeBroadcastAssistant.getAllSources(mBluetoothDevice)).thenReturn(
+                sourceList);
+
+        mMediaOutputBroadcastDialog.connectBroadcastWithActiveDevice();
+
+        verify(mLocalBluetoothLeBroadcastAssistant, times(1)).addSource(any(), any(), anyBoolean());
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
index 09b00e2..ae6ced4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
@@ -19,12 +19,14 @@
 import static android.app.Notification.FLAG_BUBBLE;
 import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
 import static android.app.Notification.GROUP_ALERT_SUMMARY;
+import static android.app.Notification.VISIBILITY_PRIVATE;
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
 import static android.app.NotificationManager.IMPORTANCE_HIGH;
 import static android.app.NotificationManager.IMPORTANCE_LOW;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
+import static android.app.NotificationManager.VISIBILITY_NO_OVERRIDE;
 
 import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
@@ -222,10 +224,26 @@
         ensureStateForHeadsUpWhenDozing();
 
         NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+        modifyRanking(entry)
+                .setVisibilityOverride(VISIBILITY_NO_OVERRIDE)
+                .build();
+
         assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue();
     }
 
     @Test
+    public void testShouldHeadsUpWhenDozing_hiddenOnLockscreen() {
+        ensureStateForHeadsUpWhenDozing();
+
+        NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+        modifyRanking(entry)
+                .setVisibilityOverride(VISIBILITY_PRIVATE)
+                .build();
+
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+    }
+
+    @Test
     public void testShouldNotHeadsUpWhenDozing_pulseDisabled() {
         // GIVEN state for "heads up when dozing" is true
         ensureStateForHeadsUpWhenDozing();
@@ -638,6 +656,39 @@
     }
 
     @Test
+    public void testShouldNotFullScreen_isSuppressedByBubbleMetadata_withStrictFlag() {
+        when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
+        testShouldNotFullScreen_isSuppressedByBubbleMetadata();
+    }
+
+    @Test
+    public void testShouldNotFullScreen_isSuppressedByBubbleMetadata() {
+        NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
+        Notification.BubbleMetadata bubbleMetadata = new Notification.BubbleMetadata.Builder("foo")
+                .setSuppressNotification(true).build();
+        entry.getSbn().getNotification().setBubbleMetadata(bubbleMetadata);
+        when(mPowerManager.isInteractive()).thenReturn(false);
+        when(mStatusBarStateController.isDreaming()).thenReturn(true);
+        when(mStatusBarStateController.getState()).thenReturn(KEYGUARD);
+
+        assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
+                .isEqualTo(FullScreenIntentDecision.NO_FSI_SUPPRESSIVE_BUBBLE_METADATA);
+        assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
+                .isFalse();
+        verify(mLogger, never()).logNoFullscreen(any(), any());
+        verify(mLogger).logNoFullscreenWarning(entry,
+                "NO_FSI_SUPPRESSIVE_BUBBLE_METADATA: BubbleMetadata may prevent HUN");
+        verify(mLogger, never()).logFullscreen(any(), any());
+
+        assertThat(mUiEventLoggerFake.numLogs()).isEqualTo(1);
+        UiEventLoggerFake.FakeUiEvent fakeUiEvent = mUiEventLoggerFake.get(0);
+        assertThat(fakeUiEvent.eventId).isEqualTo(
+                NotificationInterruptEvent.FSI_SUPPRESSED_SUPPRESSIVE_BUBBLE_METADATA.getId());
+        assertThat(fakeUiEvent.uid).isEqualTo(entry.getSbn().getUid());
+        assertThat(fakeUiEvent.packageName).isEqualTo(entry.getSbn().getPackageName());
+    }
+
+    @Test
     public void testShouldFullScreen_notInteractive_withStrictFlag() throws Exception {
         when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
         testShouldFullScreen_notInteractive();
@@ -646,6 +697,9 @@
     @Test
     public void testShouldFullScreen_notInteractive() throws RemoteException {
         NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
+        Notification.BubbleMetadata bubbleMetadata = new Notification.BubbleMetadata.Builder("foo")
+                .setSuppressNotification(false).build();
+        entry.getSbn().getNotification().setBubbleMetadata(bubbleMetadata);
         when(mPowerManager.isInteractive()).thenReturn(false);
         when(mStatusBarStateController.isDreaming()).thenReturn(false);
         when(mStatusBarStateController.getState()).thenReturn(SHADE);
@@ -879,6 +933,7 @@
         NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
         Set<FullScreenIntentDecision> warnings = new HashSet<>(Arrays.asList(
                 FullScreenIntentDecision.NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR,
+                FullScreenIntentDecision.NO_FSI_SUPPRESSIVE_BUBBLE_METADATA,
                 FullScreenIntentDecision.NO_FSI_NO_HUN_OR_KEYGUARD
         ));
         for (FullScreenIntentDecision decision : FullScreenIntentDecision.values()) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
index 824eb4a..551499e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
@@ -43,7 +43,6 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.SwipeHelper;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.flags.FeatureFlags;
@@ -101,7 +100,6 @@
                 ViewConfiguration.get(mContext),
                 new FalsingManagerFake(),
                 mFeatureFlags,
-                SwipeHelper.X,
                 mCallback,
                 mListener,
                 mNotificationRoundnessManager));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt
index 6980a0b..6094135 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt
@@ -27,8 +27,10 @@
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.settings.FakeSettings
 import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.util.wrapper.BuildInfo
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
@@ -36,9 +38,9 @@
 import org.mockito.ArgumentCaptor
 import org.mockito.Captor
 import org.mockito.Mock
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -58,6 +60,8 @@
     private lateinit var dumpManager: DumpManager
     @Mock
     private lateinit var listener: DeviceProvisionedController.DeviceProvisionedListener
+    @Mock
+    private lateinit var buildInfo: BuildInfo
     @Captor
     private lateinit var userTrackerCallbackCaptor: ArgumentCaptor<UserTracker.Callback>
 
@@ -72,12 +76,13 @@
         mainExecutor = FakeExecutor(FakeSystemClock())
         settings = FakeSettings()
         `when`(userTracker.userId).thenReturn(START_USER)
-
+        whenever(buildInfo.isDebuggable).thenReturn(false)
         controller = DeviceProvisionedControllerImpl(
                 settings,
                 settings,
                 userTracker,
                 dumpManager,
+                buildInfo,
                 Handler(testableLooper.looper),
                 mainExecutor
         )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/Flow.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/Flow.kt
index e1ba074..ce8d93e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/Flow.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/Flow.kt
@@ -69,10 +69,10 @@
     flow: Flow<T>,
     context: CoroutineContext = EmptyCoroutineContext,
     start: CoroutineStart = CoroutineStart.DEFAULT,
-): FlowValues<T> {
+): FlowValue<List<T>> {
     val values = mutableListOf<T>()
     backgroundScope.launch(context, start) { flow.collect(values::add) }
-    return FlowValuesImpl {
+    return FlowValueImpl {
         runCurrent()
         values.toList()
     }
@@ -83,17 +83,7 @@
     operator fun invoke(): T
 }
 
-/** @see collectValues */
-interface FlowValues<T> : ReadOnlyProperty<Any?, List<T>> {
-    operator fun invoke(): List<T>
-}
-
 private class FlowValueImpl<T>(private val block: () -> T) : FlowValue<T> {
     override operator fun invoke(): T = block()
     override fun getValue(thisRef: Any?, property: KProperty<*>): T = invoke()
 }
-
-private class FlowValuesImpl<T>(private val block: () -> List<T>) : FlowValues<T> {
-    override operator fun invoke(): List<T> = block()
-    override fun getValue(thisRef: Any?, property: KProperty<*>): List<T> = invoke()
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt
index 5641832..00b1a40 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt
@@ -17,14 +17,22 @@
 
 package com.android.systemui.keyguard.data.repository
 
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 
 class FakeDeviceEntryFingerprintAuthRepository : DeviceEntryFingerprintAuthRepository {
-    private val _isLockedOut = MutableStateFlow<Boolean>(false)
+    private val _isLockedOut = MutableStateFlow(false)
     override val isLockedOut: StateFlow<Boolean> = _isLockedOut.asStateFlow()
 
+    private val _isRunning = MutableStateFlow(false)
+    override val isRunning: Flow<Boolean>
+        get() = _isRunning
+
+    override val availableFpSensorType: BiometricType?
+        get() = null
+
     fun setLockedOut(lockedOut: Boolean) {
         _isLockedOut.value = lockedOut
     }
diff --git a/services/accessibility/java/com/android/server/accessibility/FlashNotificationsController.java b/services/accessibility/java/com/android/server/accessibility/FlashNotificationsController.java
index fa30a6f..e605514 100644
--- a/services/accessibility/java/com/android/server/accessibility/FlashNotificationsController.java
+++ b/services/accessibility/java/com/android/server/accessibility/FlashNotificationsController.java
@@ -123,11 +123,9 @@
     private static final int SCREEN_DEFAULT_COLOR_WITH_ALPHA =
             SCREEN_DEFAULT_COLOR | SCREEN_DEFAULT_ALPHA;
 
-    // TODO(b/266775677): Make protected-broadcast intent
     @VisibleForTesting
     static final String ACTION_FLASH_NOTIFICATION_START_PREVIEW =
             "com.android.internal.intent.action.FLASH_NOTIFICATION_START_PREVIEW";
-    // TODO(b/266775677): Make protected-broadcast intent
     @VisibleForTesting
     static final String ACTION_FLASH_NOTIFICATION_STOP_PREVIEW =
             "com.android.internal.intent.action.FLASH_NOTIFICATION_STOP_PREVIEW";
@@ -143,13 +141,10 @@
     @VisibleForTesting
     static final int PREVIEW_TYPE_LONG = 1;
 
-    // TODO(b/266775683): Move to settings provider
     @VisibleForTesting
     static final String SETTING_KEY_CAMERA_FLASH_NOTIFICATION = "camera_flash_notification";
-    // TODO(b/266775683): Move to settings provider
     @VisibleForTesting
     static final String SETTING_KEY_SCREEN_FLASH_NOTIFICATION = "screen_flash_notification";
-    // TODO(b/266775683): Move to settings provider
     @VisibleForTesting
     static final String SETTING_KEY_SCREEN_FLASH_NOTIFICATION_COLOR =
             "screen_flash_notification_color_global";
diff --git a/services/core/java/com/android/server/SystemServerInitThreadPool.java b/services/core/java/com/android/server/SystemServerInitThreadPool.java
index 7f24c52..3d3535d 100644
--- a/services/core/java/com/android/server/SystemServerInitThreadPool.java
+++ b/services/core/java/com/android/server/SystemServerInitThreadPool.java
@@ -25,7 +25,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.ConcurrentUtils;
 import com.android.internal.util.Preconditions;
-import com.android.server.am.ActivityManagerService;
+import com.android.server.am.StackTracesDumpHelper;
 import com.android.server.utils.TimingsTraceAndSlog;
 
 import java.io.PrintWriter;
@@ -188,12 +188,12 @@
     }
 
     /**
-     * A helper function to call ActivityManagerService.dumpStackTraces().
+     * A helper function to call StackTracesDumpHelper.dumpStackTraces().
      */
     private static void dumpStackTraces() {
         final ArrayList<Integer> pids = new ArrayList<>();
         pids.add(Process.myPid());
-        ActivityManagerService.dumpStackTraces(pids,
+        StackTracesDumpHelper.dumpStackTraces(pids,
                 /* processCpuTracker= */null, /* lastPids= */null,
                 CompletableFuture.completedFuture(Watchdog.getInterestingNativePids()),
                 /* logExceptionCreatingFile= */null, /* subject= */null,
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index f652cb0..78d4708 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -1104,7 +1104,7 @@
             final NetworkCapabilities result = ncBuilder.build();
             final VcnUnderlyingNetworkPolicy policy = new VcnUnderlyingNetworkPolicy(
                     mTrackingNetworkCallback
-                            .requiresRestartForImmutableCapabilityChanges(result),
+                            .requiresRestartForImmutableCapabilityChanges(result, linkProperties),
                     result);
 
             logVdbg("getUnderlyingNetworkPolicy() called for caps: " + networkCapabilities
@@ -1354,19 +1354,29 @@
      * without requiring a Network restart.
      */
     private class TrackingNetworkCallback extends ConnectivityManager.NetworkCallback {
+        private final Object mLockObject = new Object();
         private final Map<Network, NetworkCapabilities> mCaps = new ArrayMap<>();
+        private final Map<Network, LinkProperties> mLinkProperties = new ArrayMap<>();
 
         @Override
         public void onCapabilitiesChanged(Network network, NetworkCapabilities caps) {
-            synchronized (mCaps) {
+            synchronized (mLockObject) {
                 mCaps.put(network, caps);
             }
         }
 
         @Override
+        public void onLinkPropertiesChanged(Network network, LinkProperties lp) {
+            synchronized (mLockObject) {
+                mLinkProperties.put(network, lp);
+            }
+        }
+
+        @Override
         public void onLost(Network network) {
-            synchronized (mCaps) {
+            synchronized (mLockObject) {
                 mCaps.remove(network);
+                mLinkProperties.remove(network);
             }
         }
 
@@ -1393,22 +1403,28 @@
             return true;
         }
 
-        private boolean requiresRestartForImmutableCapabilityChanges(NetworkCapabilities caps) {
+        private boolean requiresRestartForImmutableCapabilityChanges(
+                NetworkCapabilities caps, LinkProperties lp) {
             if (caps.getSubscriptionIds() == null) {
                 return false;
             }
 
-            synchronized (mCaps) {
-                for (NetworkCapabilities existing : mCaps.values()) {
-                    if (caps.getSubscriptionIds().equals(existing.getSubscriptionIds())
-                            && hasSameTransportsAndCapabilities(caps, existing)) {
-                        // Restart if any immutable capabilities have changed
-                        return existing.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)
+            synchronized (mLockObject) {
+                // Search for an existing network (using interfce names)
+                // TODO: Get network from NetworkFactory (if exists) for this match.
+                for (Entry<Network, LinkProperties> lpEntry : mLinkProperties.entrySet()) {
+                    if (lp.getInterfaceName() != null
+                            && !lp.getInterfaceName().isEmpty()
+                            && Objects.equals(
+                                    lp.getInterfaceName(), lpEntry.getValue().getInterfaceName())) {
+                        return mCaps.get(lpEntry.getKey())
+                                        .hasCapability(NET_CAPABILITY_NOT_RESTRICTED)
                                 != caps.hasCapability(NET_CAPABILITY_NOT_RESTRICTED);
                     }
                 }
             }
 
+            // If no network found, by definition does not need restart.
             return false;
         }
 
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index 03821db..62651dd 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -56,6 +56,7 @@
 import com.android.internal.os.ZygoteConnectionConstants;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.am.ActivityManagerService;
+import com.android.server.am.StackTracesDumpHelper;
 import com.android.server.am.TraceErrorLogger;
 import com.android.server.criticalevents.CriticalEventLog;
 import com.android.server.wm.SurfaceAnimationThread;
@@ -905,7 +906,7 @@
         report.append(ResourcePressureUtil.currentPsiState());
         ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(false);
         StringWriter tracesFileException = new StringWriter();
-        final File stack = ActivityManagerService.dumpStackTraces(
+        final File stack = StackTracesDumpHelper.dumpStackTraces(
                 pids, processCpuTracker, new SparseBooleanArray(),
                 CompletableFuture.completedFuture(getInterestingNativePids()), tracesFileException,
                 subject, criticalEvents, Runnable::run, /* latencyTracker= */null);
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 461103e..8fe61e7 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -73,6 +73,7 @@
 import static android.os.PowerExemptionManager.REASON_SYSTEM_UID;
 import static android.os.PowerExemptionManager.REASON_TEMP_ALLOWED_WHILE_IN_USE;
 import static android.os.PowerExemptionManager.REASON_UID_VISIBLE;
+import static android.os.PowerExemptionManager.REASON_UNKNOWN;
 import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
 import static android.os.PowerExemptionManager.getReasonCodeFromProcState;
 import static android.os.PowerExemptionManager.reasonCodeToString;
@@ -7319,9 +7320,10 @@
             r.mAllowWhileInUsePermissionInFgs = true;
         }
 
+        final @ReasonCode int allowWhileInUse;
         if (!r.mAllowWhileInUsePermissionInFgs
                 || (r.mAllowStartForeground == REASON_DENIED)) {
-            final @ReasonCode int allowWhileInUse = shouldAllowFgsWhileInUsePermissionLocked(
+            allowWhileInUse = shouldAllowFgsWhileInUsePermissionLocked(
                     callingPackage, callingPid, callingUid, r.app, backgroundStartPrivileges,
                     isBindService);
             if (!r.mAllowWhileInUsePermissionInFgs) {
@@ -7332,6 +7334,24 @@
                         allowWhileInUse, callingPackage, callingPid, callingUid, intent, r,
                         backgroundStartPrivileges, isBindService);
             }
+        } else {
+            allowWhileInUse = REASON_UNKNOWN;
+        }
+        // We want to allow scheduling user-initiated jobs when the app is running a
+        // foreground service that was started in the same conditions that allows for scheduling
+        // UI jobs. More explicitly, we want to allow scheduling UI jobs when the app is running
+        // an FGS that started when the app was in the TOP or a BAL-approved state.
+        // As of Android UDC, the conditions required for the while-in-use permissions
+        // are the same conditions that we want, so we piggyback on that logic.
+        // We use that as a shortcut if possible so we don't have to recheck all the conditions.
+        final boolean isFgs = r.isForeground || r.fgRequired;
+        if (isFgs) {
+            r.updateAllowUiJobScheduling(ActivityManagerService
+                    .doesReasonCodeAllowSchedulingUserInitiatedJobs(allowWhileInUse)
+                    || mAm.canScheduleUserInitiatedJobs(
+                            callingUid, callingPid, callingPackage, true));
+        } else {
+            r.updateAllowUiJobScheduling(false);
         }
     }
 
@@ -7342,6 +7362,7 @@
         r.mInfoTempFgsAllowListReason = null;
         r.mLoggedInfoAllowStartForeground = false;
         r.mLastSetFgsRestrictionTime = 0;
+        r.updateAllowUiJobScheduling(r.mAllowWhileInUsePermissionInFgs);
     }
 
     boolean canStartForegroundServiceLocked(int callingPid, int callingUid, String callingPackage) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 65c4d75..713b993 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -114,7 +114,6 @@
 import static android.provider.Settings.Global.ALWAYS_FINISH_ACTIVITIES;
 import static android.provider.Settings.Global.DEBUG_APP;
 import static android.provider.Settings.Global.WAIT_FOR_DEBUGGER;
-import static android.text.format.DateUtils.DAY_IN_MILLIS;
 import static android.util.FeatureFlagUtils.SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS;
 import static android.view.Display.INVALID_DISPLAY;
 
@@ -123,7 +122,6 @@
 import static com.android.internal.util.FrameworkStatsLog.UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__NEW_MUTABLE_IMPLICIT_PENDING_INTENT_RETRIEVED;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALLOWLISTS;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ANR;
 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;
@@ -372,7 +370,6 @@
 import android.util.PrintWriterPrinter;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
@@ -409,7 +406,6 @@
 import com.android.internal.os.TimeoutRecord;
 import com.android.internal.os.TransferPipe;
 import com.android.internal.os.Zygote;
-import com.android.internal.os.anr.AnrLatencyTracker;
 import com.android.internal.policy.AttributeCache;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.ArrayUtils;
@@ -488,14 +484,10 @@
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.nio.charset.StandardCharsets;
-import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
-import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -506,18 +498,13 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.UUID;
-import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
-import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.BiFunction;
 import java.util.function.Consumer;
-import java.util.function.Supplier;
 
 public class ActivityManagerService extends IActivityManager.Stub
         implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback, ActivityManagerGlobalLock {
@@ -555,8 +542,6 @@
 
     static final String SYSTEM_USER_HOME_NEEDED = "ro.system_user_home_needed";
 
-    public static final String ANR_TRACE_DIR = "/data/anr";
-
     // Maximum number of receivers an app can register.
     private static final int MAX_RECEIVERS_ALLOWED_PER_APP = 1000;
 
@@ -618,10 +603,6 @@
     private static final int MAX_BUGREPORT_TITLE_SIZE = 100;
     private static final int MAX_BUGREPORT_DESCRIPTION_SIZE = 150;
 
-    private static final int NATIVE_DUMP_TIMEOUT_MS =
-            2000 * Build.HW_TIMEOUT_MULTIPLIER; // 2 seconds;
-    private static final int JAVA_DUMP_MINIMUM_SIZE = 100; // 100 bytes.
-
     OomAdjuster mOomAdjuster;
 
     static final String EXTRA_TITLE = "android.intent.extra.TITLE";
@@ -3428,432 +3409,6 @@
         }
     }
 
-    /**
-     * If a stack trace dump file is configured, dump process stack traces.
-     * @param firstPids of dalvik VM processes to dump stack traces for first
-     * @param lastPids of dalvik VM processes to dump stack traces for last
-     * @param nativePids optional list of native pids to dump stack crawls
-     * @param logExceptionCreatingFile optional writer to which we log errors creating the file
-     * @param auxiliaryTaskExecutor executor to execute auxiliary tasks on
-     * @param latencyTracker the latency tracker instance of the current ANR.
-     */
-    public static File dumpStackTraces(ArrayList<Integer> firstPids,
-            ProcessCpuTracker processCpuTracker, SparseBooleanArray lastPids,
-            Future<ArrayList<Integer>> nativePidsFuture, StringWriter logExceptionCreatingFile,
-            @NonNull Executor auxiliaryTaskExecutor, AnrLatencyTracker latencyTracker) {
-        return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePidsFuture,
-                logExceptionCreatingFile, null, null, null, auxiliaryTaskExecutor, latencyTracker);
-    }
-
-    /**
-     * If a stack trace dump file is configured, dump process stack traces.
-     * @param firstPids of dalvik VM processes to dump stack traces for first
-     * @param lastPids of dalvik VM processes to dump stack traces for last
-     * @param nativePids optional list of native pids to dump stack crawls
-     * @param logExceptionCreatingFile optional writer to which we log errors creating the file
-     * @param subject optional line related to the error
-     * @param criticalEventSection optional lines containing recent critical events.
-     * @param auxiliaryTaskExecutor executor to execute auxiliary tasks on
-     * @param latencyTracker the latency tracker instance of the current ANR.
-     */
-    public static File dumpStackTraces(ArrayList<Integer> firstPids,
-            ProcessCpuTracker processCpuTracker, SparseBooleanArray lastPids,
-            Future<ArrayList<Integer>> nativePidsFuture, StringWriter logExceptionCreatingFile,
-            String subject, String criticalEventSection, @NonNull Executor auxiliaryTaskExecutor,
-            AnrLatencyTracker latencyTracker) {
-        return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePidsFuture,
-                logExceptionCreatingFile, null, subject, criticalEventSection,
-                auxiliaryTaskExecutor, latencyTracker);
-    }
-
-    /**
-     * @param firstPidEndOffset Optional, when it's set, it receives the start/end offset
-     *                        of the very first pid to be dumped.
-     */
-    /* package */ static File dumpStackTraces(ArrayList<Integer> firstPids,
-            ProcessCpuTracker processCpuTracker, SparseBooleanArray lastPids,
-            Future<ArrayList<Integer>> nativePidsFuture, StringWriter logExceptionCreatingFile,
-            AtomicLong firstPidEndOffset, String subject, String criticalEventSection,
-            @NonNull Executor auxiliaryTaskExecutor, AnrLatencyTracker latencyTracker) {
-        try {
-
-            if (latencyTracker != null) {
-                latencyTracker.dumpStackTracesStarted();
-            }
-
-            Slog.i(TAG, "dumpStackTraces pids=" + lastPids);
-
-            // Measure CPU usage as soon as we're called in order to get a realistic sampling
-            // of the top users at the time of the request.
-            Supplier<ArrayList<Integer>> extraPidsSupplier = processCpuTracker != null
-                    ? () -> getExtraPids(processCpuTracker, lastPids, latencyTracker) : null;
-            Future<ArrayList<Integer>> extraPidsFuture = null;
-            if (extraPidsSupplier != null) {
-                extraPidsFuture =
-                        CompletableFuture.supplyAsync(extraPidsSupplier, auxiliaryTaskExecutor);
-            }
-
-            final File tracesDir = new File(ANR_TRACE_DIR);
-
-            // NOTE: We should consider creating the file in native code atomically once we've
-            // gotten rid of the old scheme of dumping and lot of the code that deals with paths
-            // can be removed.
-            File tracesFile;
-            try {
-                tracesFile = createAnrDumpFile(tracesDir);
-            } catch (IOException e) {
-                Slog.w(TAG, "Exception creating ANR dump file:", e);
-                if (logExceptionCreatingFile != null) {
-                    logExceptionCreatingFile.append(
-                            "----- Exception creating ANR dump file -----\n");
-                    e.printStackTrace(new PrintWriter(logExceptionCreatingFile));
-                }
-                if (latencyTracker != null) {
-                    latencyTracker.anrSkippedDumpStackTraces();
-                }
-                return null;
-            }
-
-            if (subject != null || criticalEventSection != null) {
-                appendtoANRFile(tracesFile.getAbsolutePath(),
-                        (subject != null ? "Subject: " + subject + "\n\n" : "")
-                        + (criticalEventSection != null ? criticalEventSection : ""));
-            }
-
-            long firstPidEndPos = dumpStackTraces(
-                    tracesFile.getAbsolutePath(), firstPids, nativePidsFuture,
-                    extraPidsFuture, latencyTracker);
-            if (firstPidEndOffset != null) {
-                firstPidEndOffset.set(firstPidEndPos);
-            }
-            // Each set of ANR traces is written to a separate file and dumpstate will process
-            // all such files and add them to a captured bug report if they're recent enough.
-            maybePruneOldTraces(tracesDir);
-
-            return tracesFile;
-        } finally {
-            if (latencyTracker != null) {
-                latencyTracker.dumpStackTracesEnded();
-            }
-        }
-
-    }
-
-    @GuardedBy("ActivityManagerService.class")
-    private static SimpleDateFormat sAnrFileDateFormat;
-    static final String ANR_FILE_PREFIX = "anr_";
-
-    private static ArrayList<Integer> getExtraPids(ProcessCpuTracker processCpuTracker,
-            SparseBooleanArray lastPids, AnrLatencyTracker latencyTracker) {
-        if (latencyTracker != null) {
-            latencyTracker.processCpuTrackerMethodsCalled();
-        }
-        ArrayList<Integer> extraPids = new ArrayList<>();
-        processCpuTracker.init();
-        try {
-            Thread.sleep(200);
-        } catch (InterruptedException ignored) {
-        }
-
-        processCpuTracker.update();
-
-        // We'll take the stack crawls of just the top apps using CPU.
-        final int workingStatsNumber = processCpuTracker.countWorkingStats();
-        for (int i = 0; i < workingStatsNumber && extraPids.size() < 2; i++) {
-            ProcessCpuTracker.Stats stats = processCpuTracker.getWorkingStats(i);
-            if (lastPids.indexOfKey(stats.pid) >= 0) {
-                if (DEBUG_ANR) {
-                    Slog.d(TAG, "Collecting stacks for extra pid " + stats.pid);
-                }
-
-                extraPids.add(stats.pid);
-            } else {
-                Slog.i(TAG,
-                        "Skipping next CPU consuming process, not a java proc: "
-                        + stats.pid);
-            }
-        }
-        if (latencyTracker != null) {
-            latencyTracker.processCpuTrackerMethodsReturned();
-        }
-        return extraPids;
-    }
-
-    private static synchronized File createAnrDumpFile(File tracesDir) throws IOException {
-        if (sAnrFileDateFormat == null) {
-            sAnrFileDateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS");
-        }
-
-        final String formattedDate = sAnrFileDateFormat.format(new Date());
-        final File anrFile = new File(tracesDir, ANR_FILE_PREFIX + formattedDate);
-
-        if (anrFile.createNewFile()) {
-            FileUtils.setPermissions(anrFile.getAbsolutePath(), 0600, -1, -1); // -rw-------
-            return anrFile;
-        } else {
-            throw new IOException("Unable to create ANR dump file: createNewFile failed");
-        }
-    }
-
-    /**
-     * Prune all trace files that are more than a day old.
-     *
-     * NOTE: It might make sense to move this functionality to tombstoned eventually, along with a
-     * shift away from anr_XX and tombstone_XX to a more descriptive name. We do it here for now
-     * since it's the system_server that creates trace files for most ANRs.
-     */
-    private static void maybePruneOldTraces(File tracesDir) {
-        final File[] files = tracesDir.listFiles();
-        if (files == null) return;
-
-        final int max = SystemProperties.getInt("tombstoned.max_anr_count", 64);
-        final long now = System.currentTimeMillis();
-        try {
-            Arrays.sort(files, Comparator.comparingLong(File::lastModified).reversed());
-            for (int i = 0; i < files.length; ++i) {
-                if (i > max || (now - files[i].lastModified()) > DAY_IN_MILLIS) {
-                    if (!files[i].delete()) {
-                        Slog.w(TAG, "Unable to prune stale trace file: " + files[i]);
-                    }
-                }
-            }
-        } catch (IllegalArgumentException e) {
-            // The modification times changed while we were sorting. Bail...
-            // https://issuetracker.google.com/169836837
-            Slog.w(TAG, "tombstone modification times changed while sorting; not pruning", e);
-        }
-    }
-
-    /**
-     * Dump java traces for process {@code pid} to the specified file. If java trace dumping
-     * fails, a native backtrace is attempted. Note that the timeout {@code timeoutMs} only applies
-     * to the java section of the trace, a further {@code NATIVE_DUMP_TIMEOUT_MS} might be spent
-     * attempting to obtain native traces in the case of a failure. Returns the total time spent
-     * capturing traces.
-     */
-    private static long dumpJavaTracesTombstoned(int pid, String fileName, long timeoutMs) {
-        final long timeStart = SystemClock.elapsedRealtime();
-        int headerSize = writeUptimeStartHeaderForPid(pid, fileName);
-        boolean javaSuccess = Debug.dumpJavaBacktraceToFileTimeout(pid, fileName,
-                (int) (timeoutMs / 1000));
-        if (javaSuccess) {
-            // Check that something is in the file, actually. Try-catch should not be necessary,
-            // but better safe than sorry.
-            try {
-                long size = new File(fileName).length();
-                if ((size - headerSize) < JAVA_DUMP_MINIMUM_SIZE) {
-                    Slog.w(TAG, "Successfully created Java ANR file is empty!");
-                    javaSuccess = false;
-                }
-            } catch (Exception e) {
-                Slog.w(TAG, "Unable to get ANR file size", e);
-                javaSuccess = false;
-            }
-        }
-        if (!javaSuccess) {
-            Slog.w(TAG, "Dumping Java threads failed, initiating native stack dump.");
-            if (!Debug.dumpNativeBacktraceToFileTimeout(pid, fileName,
-                    (NATIVE_DUMP_TIMEOUT_MS / 1000))) {
-                Slog.w(TAG, "Native stack dump failed!");
-            }
-        }
-
-        return SystemClock.elapsedRealtime() - timeStart;
-    }
-
-    private static int appendtoANRFile(String fileName, String text) {
-        try (FileOutputStream fos = new FileOutputStream(fileName, true)) {
-            byte[] header = text.getBytes(StandardCharsets.UTF_8);
-            fos.write(header);
-            return header.length;
-        } catch (IOException e) {
-            Slog.w(TAG, "Exception writing to ANR dump file:", e);
-            return 0;
-        }
-    }
-
-    /*
-     * Writes a header containing the process id and the current system uptime.
-     */
-    private static int writeUptimeStartHeaderForPid(int pid, String fileName) {
-        return appendtoANRFile(fileName, "----- dumping pid: " + pid + " at "
-            + SystemClock.uptimeMillis() + "\n");
-    }
-
-
-    /**
-     * @return The end offset of the trace of the very first PID
-     */
-    public static long dumpStackTraces(String tracesFile,
-            ArrayList<Integer> firstPids, Future<ArrayList<Integer>> nativePidsFuture,
-            Future<ArrayList<Integer>> extraPidsFuture, AnrLatencyTracker latencyTracker) {
-
-        Slog.i(TAG, "Dumping to " + tracesFile);
-
-        // We don't need any sort of inotify based monitoring when we're dumping traces via
-        // tombstoned. Data is piped to an "intercept" FD installed in tombstoned so we're in full
-        // control of all writes to the file in question.
-
-        // We must complete all stack dumps within 20 seconds.
-        long remainingTime = 20 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
-
-        // As applications are usually interested with the ANR stack traces, but we can't share with
-        // them the stack traces other than their own stacks. So after the very first PID is
-        // dumped, remember the current file size.
-        long firstPidEnd = -1;
-
-        // First collect all of the stacks of the most important pids.
-        if (firstPids != null) {
-            if (latencyTracker != null) {
-                latencyTracker.dumpingFirstPidsStarted();
-            }
-
-            int num = firstPids.size();
-            for (int i = 0; i < num; i++) {
-                final int pid = firstPids.get(i);
-                // We don't copy ANR traces from the system_server intentionally.
-                final boolean firstPid = i == 0 && MY_PID != pid;
-                if (latencyTracker != null) {
-                    latencyTracker.dumpingPidStarted(pid);
-                }
-
-                Slog.i(TAG, "Collecting stacks for pid " + pid);
-                final long timeTaken = dumpJavaTracesTombstoned(pid, tracesFile,
-                                                                remainingTime);
-                if (latencyTracker != null) {
-                    latencyTracker.dumpingPidEnded();
-                }
-
-                remainingTime -= timeTaken;
-                if (remainingTime <= 0) {
-                    Slog.e(TAG, "Aborting stack trace dump (current firstPid=" + pid
-                            + "); deadline exceeded.");
-                    return firstPidEnd;
-                }
-
-                if (firstPid) {
-                    firstPidEnd = new File(tracesFile).length();
-                    // Full latency dump
-                    if (latencyTracker != null) {
-                        appendtoANRFile(tracesFile,
-                                latencyTracker.dumpAsCommaSeparatedArrayWithHeader());
-                    }
-                }
-                if (DEBUG_ANR) {
-                    Slog.d(TAG, "Done with pid " + firstPids.get(i) + " in " + timeTaken + "ms");
-                }
-            }
-            if (latencyTracker != null) {
-                latencyTracker.dumpingFirstPidsEnded();
-            }
-        }
-
-        // Next collect the stacks of the native pids
-        ArrayList<Integer> nativePids = collectPids(nativePidsFuture, "native pids");
-
-        Slog.i(TAG, "dumpStackTraces nativepids=" + nativePids);
-
-        if (nativePids != null) {
-            if (latencyTracker != null) {
-                latencyTracker.dumpingNativePidsStarted();
-            }
-            for (int pid : nativePids) {
-                Slog.i(TAG, "Collecting stacks for native pid " + pid);
-                final long nativeDumpTimeoutMs = Math.min(NATIVE_DUMP_TIMEOUT_MS, remainingTime);
-
-                if (latencyTracker != null) {
-                    latencyTracker.dumpingPidStarted(pid);
-                }
-                final long start = SystemClock.elapsedRealtime();
-                Debug.dumpNativeBacktraceToFileTimeout(
-                        pid, tracesFile, (int) (nativeDumpTimeoutMs / 1000));
-                final long timeTaken = SystemClock.elapsedRealtime() - start;
-                if (latencyTracker != null) {
-                    latencyTracker.dumpingPidEnded();
-                }
-                remainingTime -= timeTaken;
-                if (remainingTime <= 0) {
-                    Slog.e(TAG, "Aborting stack trace dump (current native pid=" + pid +
-                        "); deadline exceeded.");
-                    return firstPidEnd;
-                }
-
-                if (DEBUG_ANR) {
-                    Slog.d(TAG, "Done with native pid " + pid + " in " + timeTaken + "ms");
-                }
-            }
-            if (latencyTracker != null) {
-                latencyTracker.dumpingNativePidsEnded();
-            }
-        }
-
-        // Lastly, dump stacks for all extra PIDs from the CPU tracker.
-        ArrayList<Integer> extraPids = collectPids(extraPidsFuture, "extra pids");
-
-        if (extraPidsFuture != null) {
-            try {
-                extraPids = extraPidsFuture.get();
-            } catch (ExecutionException e) {
-                Slog.w(TAG, "Failed to collect extra pids", e.getCause());
-            } catch (InterruptedException e) {
-                Slog.w(TAG, "Interrupted while collecting extra pids", e);
-            }
-        }
-        Slog.i(TAG, "dumpStackTraces extraPids=" + extraPids);
-
-        if (extraPids != null) {
-            if (latencyTracker != null) {
-                latencyTracker.dumpingExtraPidsStarted();
-            }
-            for (int pid : extraPids) {
-                Slog.i(TAG, "Collecting stacks for extra pid " + pid);
-                if (latencyTracker != null) {
-                    latencyTracker.dumpingPidStarted(pid);
-                }
-                final long timeTaken = dumpJavaTracesTombstoned(pid, tracesFile, remainingTime);
-                if (latencyTracker != null) {
-                    latencyTracker.dumpingPidEnded();
-                }
-                remainingTime -= timeTaken;
-                if (remainingTime <= 0) {
-                    Slog.e(TAG, "Aborting stack trace dump (current extra pid=" + pid +
-                            "); deadline exceeded.");
-                    return firstPidEnd;
-                }
-
-                if (DEBUG_ANR) {
-                    Slog.d(TAG, "Done with extra pid " + pid + " in " + timeTaken + "ms");
-                }
-            }
-            if (latencyTracker != null) {
-                latencyTracker.dumpingExtraPidsEnded();
-            }
-        }
-        // Append the dumping footer with the current uptime
-        appendtoANRFile(tracesFile, "----- dumping ended at " + SystemClock.uptimeMillis() + "\n");
-        Slog.i(TAG, "Done dumping");
-
-        return firstPidEnd;
-    }
-
-    private static ArrayList<Integer> collectPids(Future<ArrayList<Integer>> pidsFuture,
-            String logName) {
-
-        ArrayList<Integer> pids = null;
-
-        if (pidsFuture == null) {
-            return pids;
-        }
-        try {
-            pids = pidsFuture.get();
-        } catch (ExecutionException e) {
-            Slog.w(TAG, "Failed to collect " + logName, e.getCause());
-        } catch (InterruptedException e) {
-            Slog.w(TAG, "Interrupted while collecting " + logName , e);
-        }
-        return pids;
-    }
-
     @Override
     public boolean clearApplicationUserData(final String packageName, boolean keepState,
             final IPackageDataObserver observer, int userId) {
@@ -6563,7 +6118,7 @@
      * This is a shortcut and <b>DOES NOT</b> include all reasons.
      * Use {@link #canScheduleUserInitiatedJobs(int, int, String)} to cover all cases.
      */
-    private boolean doesReasonCodeAllowSchedulingUserInitiatedJobs(int reasonCode) {
+    static boolean doesReasonCodeAllowSchedulingUserInitiatedJobs(int reasonCode) {
         switch (reasonCode) {
             case REASON_PROC_STATE_PERSISTENT:
             case REASON_PROC_STATE_PERSISTENT_UI:
@@ -6621,6 +6176,16 @@
             }
         }
 
+        final ProcessServiceRecord psr = pr.mServices;
+        if (psr != null && psr.hasForegroundServices()) {
+            for (int s = psr.numberOfExecutingServices() - 1; s >= 0; --s) {
+                final ServiceRecord sr = psr.getExecutingServiceAt(s);
+                if (sr.isForeground && sr.mAllowUiJobScheduling) {
+                    return true;
+                }
+            }
+        }
+
         return false;
     }
 
@@ -6630,6 +6195,11 @@
      */
     // TODO(262260570): log allow reason to an atom
     private boolean canScheduleUserInitiatedJobs(int uid, int pid, String pkgName) {
+        return canScheduleUserInitiatedJobs(uid, pid, pkgName, false);
+    }
+
+    boolean canScheduleUserInitiatedJobs(int uid, int pid, String pkgName,
+            boolean skipWhileInUseCheck) {
         synchronized (this) {
             final ProcessRecord processRecord;
             synchronized (mPidsSelfLocked) {
@@ -6659,7 +6229,7 @@
             // As of Android UDC, the conditions required to grant a while-in-use permission
             // covers the majority of those cases, and so we piggyback on that logic as the base.
             // Missing cases are added after.
-            if (mServices.canAllowWhileInUsePermissionInFgsLocked(
+            if (!skipWhileInUseCheck && mServices.canAllowWhileInUsePermissionInFgsLocked(
                     pid, uid, pkgName, processRecord, backgroundStartPrivileges)) {
                 return true;
             }
diff --git a/services/core/java/com/android/server/am/AppExitInfoTracker.java b/services/core/java/com/android/server/am/AppExitInfoTracker.java
index 1ba3266..4443636 100644
--- a/services/core/java/com/android/server/am/AppExitInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppExitInfoTracker.java
@@ -1153,7 +1153,7 @@
         final ArraySet<String> allFiles = new ArraySet();
         final File[] files = mProcExitStoreDir.listFiles((f) -> {
             final String name = f.getName();
-            boolean trace = name.startsWith(ActivityManagerService.ANR_FILE_PREFIX)
+            boolean trace = name.startsWith(StackTracesDumpHelper.ANR_FILE_PREFIX)
                     && name.endsWith(APP_TRACE_FILE_SUFFIX);
             if (trace) {
                 allFiles.add(name);
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
index 2517c9c1..1d48cb2 100644
--- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -493,7 +493,7 @@
         StringWriter tracesFileException = new StringWriter();
         // To hold the start and end offset to the ANR trace file respectively.
         final AtomicLong firstPidEndOffset = new AtomicLong(-1);
-        File tracesFile = ActivityManagerService.dumpStackTraces(firstPids,
+        File tracesFile = StackTracesDumpHelper.dumpStackTraces(firstPids,
                 isSilentAnr ? null : processCpuTracker, isSilentAnr ? null : lastPids,
                 nativePidsFuture, tracesFileException, firstPidEndOffset, annotation,
                 criticalEventLog, auxiliaryTaskExecutor, latencyTracker);
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 35f71f7..4e401b2 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -2574,7 +2574,10 @@
                     + ", " + reason);
             app.setPendingStart(false);
             killProcessQuiet(pid);
-            Process.killProcessGroup(app.uid, app.getPid());
+            final int appPid = app.getPid();
+            if (appPid != 0) {
+                Process.killProcessGroup(app.uid, appPid);
+            }
             noteAppKill(app, ApplicationExitInfo.REASON_OTHER,
                     ApplicationExitInfo.SUBREASON_INVALID_START, reason);
             return false;
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 4defdc6..18ef66f 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -176,6 +176,8 @@
     boolean mAllowWhileInUsePermissionInFgs;
     // A copy of mAllowWhileInUsePermissionInFgs's value when the service is entering FGS state.
     boolean mAllowWhileInUsePermissionInFgsAtEntering;
+    /** Allow scheduling user-initiated jobs from the background. */
+    boolean mAllowUiJobScheduling;
 
     // the most recent package that start/bind this service.
     String mRecentCallingPackage;
@@ -607,6 +609,7 @@
         }
         pw.print(prefix); pw.print("allowWhileInUsePermissionInFgs=");
                 pw.println(mAllowWhileInUsePermissionInFgs);
+        pw.print(prefix); pw.print("allowUiJobScheduling="); pw.println(mAllowUiJobScheduling);
         pw.print(prefix); pw.print("recentCallingPackage=");
                 pw.println(mRecentCallingPackage);
         pw.print(prefix); pw.print("recentCallingUid=");
@@ -1024,7 +1027,17 @@
                 ams.mConstants.SERVICE_BG_ACTIVITY_START_TIMEOUT);
     }
 
+    void updateAllowUiJobScheduling(boolean allowUiJobScheduling) {
+        if (mAllowUiJobScheduling == allowUiJobScheduling) {
+            return;
+        }
+        mAllowUiJobScheduling = allowUiJobScheduling;
+    }
+
     private void setAllowedBgActivityStartsByStart(BackgroundStartPrivileges newValue) {
+        if (mBackgroundStartPrivilegesByStartMerged == newValue) {
+            return;
+        }
         mBackgroundStartPrivilegesByStartMerged = newValue;
         updateParentProcessBgActivityStartsToken();
     }
diff --git a/services/core/java/com/android/server/am/StackTracesDumpHelper.java b/services/core/java/com/android/server/am/StackTracesDumpHelper.java
new file mode 100644
index 0000000..9373328
--- /dev/null
+++ b/services/core/java/com/android/server/am/StackTracesDumpHelper.java
@@ -0,0 +1,483 @@
+/*
+ * 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.
+ */
+
+package com.android.server.am;
+
+import static android.text.format.DateUtils.DAY_IN_MILLIS;
+
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ANR;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+
+import android.annotation.NonNull;
+import android.os.Build;
+import android.os.Debug;
+import android.os.FileUtils;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.util.Slog;
+import android.util.SparseBooleanArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.ProcessCpuTracker;
+import com.android.internal.os.anr.AnrLatencyTracker;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Supplier;
+
+
+/**
+ * A helper for dumping stack traces.
+ */
+public class StackTracesDumpHelper {
+    static final String TAG = TAG_WITH_CLASS_NAME ? "StackTracesDumpHelper" : TAG_AM;
+
+    @GuardedBy("StackTracesDumpHelper.class")
+    private static final SimpleDateFormat ANR_FILE_DATE_FORMAT =
+            new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS");
+
+    static final String ANR_FILE_PREFIX = "anr_";
+    public static final String ANR_TRACE_DIR = "/data/anr";
+
+    private static final int NATIVE_DUMP_TIMEOUT_MS =
+            2000 * Build.HW_TIMEOUT_MULTIPLIER; // 2 seconds;
+    private static final int JAVA_DUMP_MINIMUM_SIZE = 100; // 100 bytes.
+
+    /**
+     * If a stack trace dump file is configured, dump process stack traces.
+     * @param firstPids of dalvik VM processes to dump stack traces for first
+     * @param lastPids of dalvik VM processes to dump stack traces for last
+     * @param nativePidsFuture optional future for a list of native pids to dump stack crawls
+     * @param logExceptionCreatingFile optional writer to which we log errors creating the file
+     * @param auxiliaryTaskExecutor executor to execute auxiliary tasks on
+     * @param latencyTracker the latency tracker instance of the current ANR.
+     */
+    public static File dumpStackTraces(ArrayList<Integer> firstPids,
+            ProcessCpuTracker processCpuTracker, SparseBooleanArray lastPids,
+            Future<ArrayList<Integer>> nativePidsFuture, StringWriter logExceptionCreatingFile,
+            @NonNull Executor auxiliaryTaskExecutor, AnrLatencyTracker latencyTracker) {
+        return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePidsFuture,
+                logExceptionCreatingFile, null, null, null, auxiliaryTaskExecutor, latencyTracker);
+    }
+
+    /**
+     * @param subject the subject of the dumped traces
+     * @param criticalEventSection the critical event log, passed as a string
+     */
+    public static File dumpStackTraces(ArrayList<Integer> firstPids,
+            ProcessCpuTracker processCpuTracker, SparseBooleanArray lastPids,
+            Future<ArrayList<Integer>> nativePidsFuture, StringWriter logExceptionCreatingFile,
+            String subject, String criticalEventSection, @NonNull Executor auxiliaryTaskExecutor,
+            AnrLatencyTracker latencyTracker) {
+        return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePidsFuture,
+                logExceptionCreatingFile, null, subject, criticalEventSection,
+                auxiliaryTaskExecutor, latencyTracker);
+    }
+
+    /**
+     * @param firstPidEndOffset Optional, when it's set, it receives the start/end offset
+     *                        of the very first pid to be dumped.
+     */
+    /* package */ static File dumpStackTraces(ArrayList<Integer> firstPids,
+            ProcessCpuTracker processCpuTracker, SparseBooleanArray lastPids,
+            Future<ArrayList<Integer>> nativePidsFuture, StringWriter logExceptionCreatingFile,
+            AtomicLong firstPidEndOffset, String subject, String criticalEventSection,
+            @NonNull Executor auxiliaryTaskExecutor, AnrLatencyTracker latencyTracker) {
+        try {
+
+            if (latencyTracker != null) {
+                latencyTracker.dumpStackTracesStarted();
+            }
+
+            Slog.i(TAG, "dumpStackTraces pids=" + lastPids);
+
+            // Measure CPU usage as soon as we're called in order to get a realistic sampling
+            // of the top users at the time of the request.
+            Supplier<ArrayList<Integer>> extraPidsSupplier = processCpuTracker != null
+                    ? () -> getExtraPids(processCpuTracker, lastPids, latencyTracker) : null;
+            Future<ArrayList<Integer>> extraPidsFuture = null;
+            if (extraPidsSupplier != null) {
+                extraPidsFuture =
+                        CompletableFuture.supplyAsync(extraPidsSupplier, auxiliaryTaskExecutor);
+            }
+
+            final File tracesDir = new File(ANR_TRACE_DIR);
+
+            // NOTE: We should consider creating the file in native code atomically once we've
+            // gotten rid of the old scheme of dumping and lot of the code that deals with paths
+            // can be removed.
+            File tracesFile;
+            try {
+                tracesFile = createAnrDumpFile(tracesDir);
+            } catch (IOException e) {
+                Slog.w(TAG, "Exception creating ANR dump file:", e);
+                if (logExceptionCreatingFile != null) {
+                    logExceptionCreatingFile.append(
+                            "----- Exception creating ANR dump file -----\n");
+                    e.printStackTrace(new PrintWriter(logExceptionCreatingFile));
+                }
+                if (latencyTracker != null) {
+                    latencyTracker.anrSkippedDumpStackTraces();
+                }
+                return null;
+            }
+
+            if (subject != null || criticalEventSection != null) {
+                appendtoANRFile(tracesFile.getAbsolutePath(),
+                        (subject != null ? "Subject: " + subject + "\n\n" : "")
+                        + (criticalEventSection != null ? criticalEventSection : ""));
+            }
+
+            long firstPidEndPos = dumpStackTraces(
+                    tracesFile.getAbsolutePath(), firstPids, nativePidsFuture,
+                    extraPidsFuture, latencyTracker);
+            if (firstPidEndOffset != null) {
+                firstPidEndOffset.set(firstPidEndPos);
+            }
+            // Each set of ANR traces is written to a separate file and dumpstate will process
+            // all such files and add them to a captured bug report if they're recent enough.
+            maybePruneOldTraces(tracesDir);
+
+            return tracesFile;
+        } finally {
+            if (latencyTracker != null) {
+                latencyTracker.dumpStackTracesEnded();
+            }
+        }
+
+    }
+
+    /**
+     * @return The end offset of the trace of the very first PID
+     */
+    public static long dumpStackTraces(String tracesFile,
+            ArrayList<Integer> firstPids, Future<ArrayList<Integer>> nativePidsFuture,
+            Future<ArrayList<Integer>> extraPidsFuture, AnrLatencyTracker latencyTracker) {
+
+        Slog.i(TAG, "Dumping to " + tracesFile);
+
+        // We don't need any sort of inotify based monitoring when we're dumping traces via
+        // tombstoned. Data is piped to an "intercept" FD installed in tombstoned so we're in full
+        // control of all writes to the file in question.
+
+        // We must complete all stack dumps within 20 seconds.
+        long remainingTime = 20 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
+
+        // As applications are usually interested with the ANR stack traces, but we can't share with
+        // them the stack traces other than their own stacks. So after the very first PID is
+        // dumped, remember the current file size.
+        long firstPidEnd = -1;
+
+        // First collect all of the stacks of the most important pids.
+        if (firstPids != null) {
+            if (latencyTracker != null) {
+                latencyTracker.dumpingFirstPidsStarted();
+            }
+
+            int num = firstPids.size();
+            for (int i = 0; i < num; i++) {
+                final int pid = firstPids.get(i);
+                // We don't copy ANR traces from the system_server intentionally.
+                final boolean firstPid = i == 0 && ActivityManagerService.MY_PID != pid;
+                if (latencyTracker != null) {
+                    latencyTracker.dumpingPidStarted(pid);
+                }
+
+                Slog.i(TAG, "Collecting stacks for pid " + pid);
+                final long timeTaken = dumpJavaTracesTombstoned(pid, tracesFile,
+                                                                remainingTime);
+                if (latencyTracker != null) {
+                    latencyTracker.dumpingPidEnded();
+                }
+
+                remainingTime -= timeTaken;
+                if (remainingTime <= 0) {
+                    Slog.e(TAG, "Aborting stack trace dump (current firstPid=" + pid
+                            + "); deadline exceeded.");
+                    return firstPidEnd;
+                }
+
+                if (firstPid) {
+                    firstPidEnd = new File(tracesFile).length();
+                    // Full latency dump
+                    if (latencyTracker != null) {
+                        appendtoANRFile(tracesFile,
+                                latencyTracker.dumpAsCommaSeparatedArrayWithHeader());
+                    }
+                }
+                if (DEBUG_ANR) {
+                    Slog.d(TAG, "Done with pid " + firstPids.get(i) + " in " + timeTaken + "ms");
+                }
+            }
+            if (latencyTracker != null) {
+                latencyTracker.dumpingFirstPidsEnded();
+            }
+        }
+
+        // Next collect the stacks of the native pids
+        ArrayList<Integer> nativePids = collectPids(nativePidsFuture, "native pids");
+
+        Slog.i(TAG, "dumpStackTraces nativepids=" + nativePids);
+
+        if (nativePids != null) {
+            if (latencyTracker != null) {
+                latencyTracker.dumpingNativePidsStarted();
+            }
+            for (int pid : nativePids) {
+                Slog.i(TAG, "Collecting stacks for native pid " + pid);
+                final long nativeDumpTimeoutMs = Math.min(NATIVE_DUMP_TIMEOUT_MS, remainingTime);
+
+                if (latencyTracker != null) {
+                    latencyTracker.dumpingPidStarted(pid);
+                }
+                final long start = SystemClock.elapsedRealtime();
+                Debug.dumpNativeBacktraceToFileTimeout(
+                        pid, tracesFile, (int) (nativeDumpTimeoutMs / 1000));
+                final long timeTaken = SystemClock.elapsedRealtime() - start;
+                if (latencyTracker != null) {
+                    latencyTracker.dumpingPidEnded();
+                }
+                remainingTime -= timeTaken;
+                if (remainingTime <= 0) {
+                    Slog.e(TAG, "Aborting stack trace dump (current native pid=" + pid
+                            + "); deadline exceeded.");
+                    return firstPidEnd;
+                }
+
+                if (DEBUG_ANR) {
+                    Slog.d(TAG, "Done with native pid " + pid + " in " + timeTaken + "ms");
+                }
+            }
+            if (latencyTracker != null) {
+                latencyTracker.dumpingNativePidsEnded();
+            }
+        }
+
+        // Lastly, dump stacks for all extra PIDs from the CPU tracker.
+        ArrayList<Integer> extraPids = collectPids(extraPidsFuture, "extra pids");
+
+        if (extraPidsFuture != null) {
+            try {
+                extraPids = extraPidsFuture.get();
+            } catch (ExecutionException e) {
+                Slog.w(TAG, "Failed to collect extra pids", e.getCause());
+            } catch (InterruptedException e) {
+                Slog.w(TAG, "Interrupted while collecting extra pids", e);
+            }
+        }
+        Slog.i(TAG, "dumpStackTraces extraPids=" + extraPids);
+
+        if (extraPids != null) {
+            if (latencyTracker != null) {
+                latencyTracker.dumpingExtraPidsStarted();
+            }
+            for (int pid : extraPids) {
+                Slog.i(TAG, "Collecting stacks for extra pid " + pid);
+                if (latencyTracker != null) {
+                    latencyTracker.dumpingPidStarted(pid);
+                }
+                final long timeTaken = dumpJavaTracesTombstoned(pid, tracesFile, remainingTime);
+                if (latencyTracker != null) {
+                    latencyTracker.dumpingPidEnded();
+                }
+                remainingTime -= timeTaken;
+                if (remainingTime <= 0) {
+                    Slog.e(TAG, "Aborting stack trace dump (current extra pid=" + pid
+                            + "); deadline exceeded.");
+                    return firstPidEnd;
+                }
+
+                if (DEBUG_ANR) {
+                    Slog.d(TAG, "Done with extra pid " + pid + " in " + timeTaken + "ms");
+                }
+            }
+            if (latencyTracker != null) {
+                latencyTracker.dumpingExtraPidsEnded();
+            }
+        }
+        // Append the dumping footer with the current uptime
+        appendtoANRFile(tracesFile, "----- dumping ended at " + SystemClock.uptimeMillis() + "\n");
+        Slog.i(TAG, "Done dumping");
+
+        return firstPidEnd;
+    }
+
+    private static synchronized File createAnrDumpFile(File tracesDir) throws IOException {
+        final String formattedDate = ANR_FILE_DATE_FORMAT.format(new Date());
+        final File anrFile = new File(tracesDir, ANR_FILE_PREFIX + formattedDate);
+
+        if (anrFile.createNewFile()) {
+            FileUtils.setPermissions(anrFile.getAbsolutePath(), 0600, -1, -1); // -rw-------
+            return anrFile;
+        } else {
+            throw new IOException("Unable to create ANR dump file: createNewFile failed");
+        }
+    }
+
+    private static ArrayList<Integer> getExtraPids(ProcessCpuTracker processCpuTracker,
+            SparseBooleanArray lastPids, AnrLatencyTracker latencyTracker) {
+        if (latencyTracker != null) {
+            latencyTracker.processCpuTrackerMethodsCalled();
+        }
+        ArrayList<Integer> extraPids = new ArrayList<>();
+        processCpuTracker.init();
+        try {
+            Thread.sleep(200);
+        } catch (InterruptedException ignored) {
+        }
+
+        processCpuTracker.update();
+
+        // We'll take the stack crawls of just the top apps using CPU.
+        final int workingStatsNumber = processCpuTracker.countWorkingStats();
+        for (int i = 0; i < workingStatsNumber && extraPids.size() < 2; i++) {
+            ProcessCpuTracker.Stats stats = processCpuTracker.getWorkingStats(i);
+            if (lastPids.indexOfKey(stats.pid) >= 0) {
+                if (DEBUG_ANR) {
+                    Slog.d(TAG, "Collecting stacks for extra pid " + stats.pid);
+                }
+
+                extraPids.add(stats.pid);
+            } else {
+                Slog.i(TAG,
+                        "Skipping next CPU consuming process, not a java proc: "
+                        + stats.pid);
+            }
+        }
+        if (latencyTracker != null) {
+            latencyTracker.processCpuTrackerMethodsReturned();
+        }
+        return extraPids;
+    }
+
+    /**
+     * Prune all trace files that are more than a day old.
+     *
+     * NOTE: It might make sense to move this functionality to tombstoned eventually, along with a
+     * shift away from anr_XX and tombstone_XX to a more descriptive name. We do it here for now
+     * since it's the system_server that creates trace files for most ANRs.
+     */
+    private static void maybePruneOldTraces(File tracesDir) {
+        final File[] files = tracesDir.listFiles();
+        if (files == null) return;
+
+        final int max = SystemProperties.getInt("tombstoned.max_anr_count", 64);
+        final long now = System.currentTimeMillis();
+        try {
+            Arrays.sort(files, Comparator.comparingLong(File::lastModified).reversed());
+            for (int i = 0; i < files.length; ++i) {
+                if (i > max || (now - files[i].lastModified()) > DAY_IN_MILLIS) {
+                    if (!files[i].delete()) {
+                        Slog.w(TAG, "Unable to prune stale trace file: " + files[i]);
+                    }
+                }
+            }
+        } catch (IllegalArgumentException e) {
+            // The modification times changed while we were sorting. Bail...
+            // https://issuetracker.google.com/169836837
+            Slog.w(TAG, "tombstone modification times changed while sorting; not pruning", e);
+        }
+    }
+    /**
+     * Dump java traces for process {@code pid} to the specified file. If java trace dumping
+     * fails, a native backtrace is attempted. Note that the timeout {@code timeoutMs} only applies
+     * to the java section of the trace, a further {@code NATIVE_DUMP_TIMEOUT_MS} might be spent
+     * attempting to obtain native traces in the case of a failure. Returns the total time spent
+     * capturing traces.
+     */
+    private static long dumpJavaTracesTombstoned(int pid, String fileName, long timeoutMs) {
+        final long timeStart = SystemClock.elapsedRealtime();
+        int headerSize = writeUptimeStartHeaderForPid(pid, fileName);
+        boolean javaSuccess = Debug.dumpJavaBacktraceToFileTimeout(pid, fileName,
+                (int) (timeoutMs / 1000));
+        if (javaSuccess) {
+            // Check that something is in the file, actually. Try-catch should not be necessary,
+            // but better safe than sorry.
+            try {
+                long size = new File(fileName).length();
+                if ((size - headerSize) < JAVA_DUMP_MINIMUM_SIZE) {
+                    Slog.w(TAG, "Successfully created Java ANR file is empty!");
+                    javaSuccess = false;
+                }
+            } catch (Exception e) {
+                Slog.w(TAG, "Unable to get ANR file size", e);
+                javaSuccess = false;
+            }
+        }
+        if (!javaSuccess) {
+            Slog.w(TAG, "Dumping Java threads failed, initiating native stack dump.");
+            if (!Debug.dumpNativeBacktraceToFileTimeout(pid, fileName,
+                    (NATIVE_DUMP_TIMEOUT_MS / 1000))) {
+                Slog.w(TAG, "Native stack dump failed!");
+            }
+        }
+
+        return SystemClock.elapsedRealtime() - timeStart;
+    }
+
+    private static int appendtoANRFile(String fileName, String text) {
+        try (FileOutputStream fos = new FileOutputStream(fileName, true)) {
+            byte[] header = text.getBytes(StandardCharsets.UTF_8);
+            fos.write(header);
+            return header.length;
+        } catch (IOException e) {
+            Slog.w(TAG, "Exception writing to ANR dump file:", e);
+            return 0;
+        }
+    }
+
+    /*
+     * Writes a header containing the process id and the current system uptime.
+     */
+    private static int writeUptimeStartHeaderForPid(int pid, String fileName) {
+        return appendtoANRFile(fileName, "----- dumping pid: " + pid + " at "
+            + SystemClock.uptimeMillis() + "\n");
+    }
+
+    private static ArrayList<Integer> collectPids(Future<ArrayList<Integer>> pidsFuture,
+            String logName) {
+
+        ArrayList<Integer> pids = null;
+
+        if (pidsFuture == null) {
+            return pids;
+        }
+        try {
+            pids = pidsFuture.get();
+        } catch (ExecutionException e) {
+            Slog.w(TAG, "Failed to collect " + logName, e.getCause());
+        } catch (InterruptedException e) {
+            Slog.w(TAG, "Interrupted while collecting " + logName , e);
+        }
+        return pids;
+    }
+
+}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index ad5ae5f..745e404 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -4600,7 +4600,8 @@
         public void onDesiredDisplayModeSpecsChanged() {
             synchronized (mSyncRoot) {
                 mChanged = false;
-                mLogicalDisplayMapper.forEachLocked(mSpecsChangedConsumer);
+                mLogicalDisplayMapper.forEachLocked(mSpecsChangedConsumer,
+                        /* includeDisabled= */ false);
                 if (mChanged) {
                     scheduleTraversalLocked(false);
                     mChanged = false;
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 656882f..f5859ee 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1634,7 +1634,6 @@
             mAppliedAutoBrightness = false;
             brightnessAdjustmentFlags = 0;
         }
-
         // Use default brightness when dozing unless overridden.
         if ((Float.isNaN(brightnessState))
                 && Display.isDozeState(state)) {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 3e01222..5306ac0 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -64,6 +64,7 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.RingBuffer;
 import com.android.server.LocalServices;
 import com.android.server.am.BatteryStatsService;
@@ -72,6 +73,7 @@
 import com.android.server.display.brightness.BrightnessReason;
 import com.android.server.display.brightness.BrightnessUtils;
 import com.android.server.display.brightness.DisplayBrightnessController;
+import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy;
 import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal;
 import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsListener;
 import com.android.server.display.layout.Layout;
@@ -209,9 +211,6 @@
     // True if auto-brightness should be used.
     private boolean mUseSoftwareAutoBrightnessConfig;
 
-    // True if the brightness config has changed and the short-term model needs to be reset
-    private boolean mShouldResetShortTermModel;
-
     // Whether or not the color fade on screen on / off is enabled.
     private final boolean mColorFadeEnabled;
 
@@ -296,12 +295,8 @@
     // If the last recorded screen state was dozing or not.
     private boolean mDozing;
 
-    // Remembers whether certain kinds of brightness adjustments
-    // were recently applied so that we can decide how to transition.
-    private boolean mAppliedAutoBrightness;
     private boolean mAppliedDimming;
     private boolean mAppliedLowPower;
-    private boolean mAppliedTemporaryAutoBrightnessAdjustment;
     private boolean mAppliedThrottling;
 
     // Reason for which the brightness was last changed. See {@link BrightnessReason} for more
@@ -359,6 +354,11 @@
     // Tracks and manages the display state of the associated display.
     private final DisplayStateController mDisplayStateController;
 
+
+    // Responsible for evaluating and tracking the automatic brightness relevant states.
+    // Todo: This is a temporary workaround. Ideally DPC2 should never talk to the strategies
+    private final AutomaticBrightnessStrategy mAutomaticBrightnessStrategy;
+
     // A record of state for skipping brightness ramps.
     private int mSkipRampState = RAMP_STATE_SKIP_NONE;
 
@@ -385,24 +385,6 @@
     @Nullable
     private BrightnessMappingStrategy mIdleModeBrightnessMapper;
 
-    // The current brightness configuration.
-    @Nullable
-    private BrightnessConfiguration mBrightnessConfiguration;
-
-    // The last auto brightness adjustment that was set by the user and not temporary. Set to
-    // Float.NaN when an auto-brightness adjustment hasn't been recorded yet.
-    private float mAutoBrightnessAdjustment;
-
-    // The pending auto brightness adjustment that will take effect on the next power state update.
-    private float mPendingAutoBrightnessAdjustment;
-
-    // The temporary auto brightness adjustment. Typically set when a user is interacting with the
-    // adjustment slider but hasn't settled on a choice yet. Set to
-    // PowerManager.BRIGHTNESS_INVALID_FLOAT when there's no temporary adjustment set.
-    private float mTemporaryAutoBrightnessAdjustment;
-
-    private boolean mUseAutoBrightness;
-
     private boolean mIsRbcActive;
 
     // Animators.
@@ -454,6 +436,7 @@
                 () -> updatePowerState(), mDisplayId, mSensorManager);
         mHighBrightnessModeMetadata = hbmMetadata;
         mDisplayStateController = new DisplayStateController(mDisplayPowerProximityStateController);
+        mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(context, mDisplayId);
         mTag = "DisplayPowerController2[" + mDisplayId + "]";
         mBrightnessThrottlingDataId = logicalDisplay.getBrightnessThrottlingDataIdLocked();
 
@@ -555,9 +538,6 @@
 
         mBrightnessBucketsInDozeConfig = resources.getBoolean(
                 R.bool.config_displayBrightnessBucketsInDoze);
-        mAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting();
-        mTemporaryAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT;
-        mPendingAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT;
 
         mBootCompleted = bootCompleted;
     }
@@ -1038,6 +1018,8 @@
             mDisplayBrightnessController.setAutomaticBrightnessController(
                     mAutomaticBrightnessController);
 
+            mAutomaticBrightnessStrategy
+                    .setAutomaticBrightnessController(mAutomaticBrightnessController);
             mBrightnessEventRingBuffer =
                     new RingBuffer<>(BrightnessEvent.class, RINGBUFFER_MAX);
 
@@ -1168,7 +1150,6 @@
         final boolean mustNotify;
         final int previousPolicy;
         boolean mustInitialize = false;
-        int brightnessAdjustmentFlags = 0;
         mBrightnessReasonTemp.set(null);
         mTempBrightnessEvent.reset();
         SparseArray<DisplayPowerControllerInterface> displayBrightnessFollowers;
@@ -1208,7 +1189,8 @@
                 .updateDisplayState(mPowerRequest, mIsEnabled, mIsInTransition);
 
         if (mScreenOffBrightnessSensorController != null) {
-            mScreenOffBrightnessSensorController.setLightSensorEnabled(mUseAutoBrightness
+            mScreenOffBrightnessSensorController
+                    .setLightSensorEnabled(mAutomaticBrightnessStrategy.shouldUseAutoBrightness()
                     && mIsEnabled && (state == Display.STATE_OFF || (state == Display.STATE_DOZE
                     && !mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig()))
                     && mLeadDisplayId == Layout.NO_LEAD_DISPLAY);
@@ -1222,7 +1204,6 @@
         // Animate the screen state change unless already animating.
         // The transition may be deferred, so after this point we will use the
         // actual state instead of the desired one.
-        final int oldState = mPowerState.getScreenState();
         animateScreenStateChange(state, mDisplayStateController.shouldPerformScreenOffTransition());
         state = mPowerState.getScreenState();
 
@@ -1231,112 +1212,59 @@
         float brightnessState = displayBrightnessState.getBrightness();
         float rawBrightnessState = displayBrightnessState.getBrightness();
         mBrightnessReasonTemp.set(displayBrightnessState.getBrightnessReason());
-
-        final boolean autoBrightnessEnabledInDoze =
-                mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig()
-                        && Display.isDozeState(state);
-        final boolean autoBrightnessEnabled = mUseAutoBrightness
-                && (state == Display.STATE_ON || autoBrightnessEnabledInDoze)
-                && (Float.isNaN(brightnessState)
-                        || mBrightnessReasonTemp.getReason() == BrightnessReason.REASON_TEMPORARY
-                        || mBrightnessReasonTemp.getReason() == BrightnessReason.REASON_BOOST)
-                && mAutomaticBrightnessController != null
-                && mBrightnessReasonTemp.getReason() != BrightnessReason.REASON_FOLLOWER;
-        final boolean autoBrightnessDisabledDueToDisplayOff = mUseAutoBrightness
-                && !(state == Display.STATE_ON || autoBrightnessEnabledInDoze);
-        final int autoBrightnessState = autoBrightnessEnabled
-                ? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
-                : autoBrightnessDisabledDueToDisplayOff
-                        ? AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE
-                        : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED;
-
         final boolean userSetBrightnessChanged = mDisplayBrightnessController
                 .updateUserSetScreenBrightness();
-
-        final boolean autoBrightnessAdjustmentChanged = updateAutoBrightnessAdjustment();
-
-        // Use the autobrightness adjustment override if set.
-        final float autoBrightnessAdjustment;
-        if (!Float.isNaN(mTemporaryAutoBrightnessAdjustment)) {
-            autoBrightnessAdjustment = mTemporaryAutoBrightnessAdjustment;
-            brightnessAdjustmentFlags = BrightnessReason.ADJUSTMENT_AUTO_TEMP;
-            mAppliedTemporaryAutoBrightnessAdjustment = true;
-        } else {
-            autoBrightnessAdjustment = mAutoBrightnessAdjustment;
-            brightnessAdjustmentFlags = BrightnessReason.ADJUSTMENT_AUTO;
-            mAppliedTemporaryAutoBrightnessAdjustment = false;
-        }
+        // Take note if the short term model was already active before applying the current
+        // request changes.
+        final boolean wasShortTermModelActive =
+                mAutomaticBrightnessStrategy.isShortTermModelActive();
+        mAutomaticBrightnessStrategy.setAutoBrightnessState(state,
+                mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig(),
+                brightnessState, mBrightnessReasonTemp.getReason(), mPowerRequest.policy,
+                mDisplayBrightnessController.getLastUserSetScreenBrightness(),
+                userSetBrightnessChanged);
 
         // If the brightness is already set then it's been overridden by something other than the
         // user, or is a temporary adjustment.
         boolean userInitiatedChange = (Float.isNaN(brightnessState))
-                && (autoBrightnessAdjustmentChanged || userSetBrightnessChanged);
-        boolean wasShortTermModelActive = false;
-        // Configure auto-brightness.
-        if (mAutomaticBrightnessController != null) {
-            wasShortTermModelActive = mAutomaticBrightnessController.hasUserDataPoints();
-            mAutomaticBrightnessController.configure(autoBrightnessState,
-                    mBrightnessConfiguration,
-                    mDisplayBrightnessController.getLastUserSetScreenBrightness(),
-                    userSetBrightnessChanged, autoBrightnessAdjustment,
-                    autoBrightnessAdjustmentChanged, mPowerRequest.policy,
-                    mShouldResetShortTermModel);
-            mShouldResetShortTermModel = false;
-        }
-        mHbmController.setAutoBrightnessEnabled(mUseAutoBrightness
+                && (mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged()
+                || userSetBrightnessChanged);
+
+        mHbmController.setAutoBrightnessEnabled(mAutomaticBrightnessStrategy
+                .shouldUseAutoBrightness()
                 ? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
                 : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED);
 
-        if (mBrightnessTracker != null) {
-            mBrightnessTracker.setShouldCollectColorSample(mBrightnessConfiguration != null
-                    && mBrightnessConfiguration.shouldCollectColorSamples());
-        }
-
         boolean updateScreenBrightnessSetting = false;
         float currentBrightnessSetting = mDisplayBrightnessController.getCurrentBrightness();
         // Apply auto-brightness.
         boolean slowChange = false;
+        int brightnessAdjustmentFlags = 0;
         if (Float.isNaN(brightnessState)) {
-            float newAutoBrightnessAdjustment = autoBrightnessAdjustment;
-            if (autoBrightnessEnabled) {
-                rawBrightnessState = mAutomaticBrightnessController
-                        .getRawAutomaticScreenBrightness();
-                brightnessState = mAutomaticBrightnessController.getAutomaticScreenBrightness(
-                        mTempBrightnessEvent);
-                newAutoBrightnessAdjustment =
-                        mAutomaticBrightnessController.getAutomaticScreenBrightnessAdjustment();
-            }
-            if (BrightnessUtils.isValidBrightnessValue(brightnessState)
-                    || brightnessState == PowerManager.BRIGHTNESS_OFF_FLOAT) {
-                // Use current auto-brightness value and slowly adjust to changes.
-                brightnessState = clampScreenBrightness(brightnessState);
-                if (mAppliedAutoBrightness && !autoBrightnessAdjustmentChanged) {
-                    slowChange = true; // slowly adapt to auto-brightness
+            if (mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()) {
+                brightnessState = mAutomaticBrightnessStrategy.getAutomaticScreenBrightness();
+                if (BrightnessUtils.isValidBrightnessValue(brightnessState)
+                        || brightnessState == PowerManager.BRIGHTNESS_OFF_FLOAT) {
+                    rawBrightnessState = mAutomaticBrightnessController
+                            .getRawAutomaticScreenBrightness();
+                    brightnessState = clampScreenBrightness(brightnessState);
+                    // slowly adapt to auto-brightness
+                    slowChange = mAutomaticBrightnessStrategy.hasAppliedAutoBrightness()
+                            && !mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged();
+                    brightnessAdjustmentFlags =
+                            mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentReasonsFlags();
+                    updateScreenBrightnessSetting = currentBrightnessSetting != brightnessState;
+                    mBrightnessReasonTemp.setReason(BrightnessReason.REASON_AUTOMATIC);
+                    if (mScreenOffBrightnessSensorController != null) {
+                        mScreenOffBrightnessSensorController.setLightSensorEnabled(false);
+                    }
                 }
-                updateScreenBrightnessSetting = currentBrightnessSetting != brightnessState;
-                mAppliedAutoBrightness = true;
-                mBrightnessReasonTemp.setReason(BrightnessReason.REASON_AUTOMATIC);
-                if (mScreenOffBrightnessSensorController != null) {
-                    mScreenOffBrightnessSensorController.setLightSensorEnabled(false);
-                }
-            } else {
-                mAppliedAutoBrightness = false;
-            }
-            if (autoBrightnessAdjustment != newAutoBrightnessAdjustment) {
-                // If the autobrightness controller has decided to change the adjustment value
-                // used, make sure that's reflected in settings.
-                putAutoBrightnessAdjustmentSetting(newAutoBrightnessAdjustment);
-            } else {
-                // Adjustment values resulted in no change
-                brightnessAdjustmentFlags = 0;
             }
         } else {
             // Any non-auto-brightness values such as override or temporary should still be subject
             // to clamping so that they don't go beyond the current max as specified by HBM
             // Controller.
             brightnessState = clampScreenBrightness(brightnessState);
-            mAppliedAutoBrightness = false;
-            brightnessAdjustmentFlags = 0;
         }
 
         // Use default brightness when dozing unless overridden.
@@ -1349,7 +1277,7 @@
 
         // The ALS is not available yet - use the screen off sensor to determine the initial
         // brightness
-        if (Float.isNaN(brightnessState) && autoBrightnessEnabled
+        if (Float.isNaN(brightnessState) && mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()
                 && mScreenOffBrightnessSensorController != null) {
             rawBrightnessState =
                     mScreenOffBrightnessSensorController.getAutomaticScreenBrightness();
@@ -1466,7 +1394,8 @@
         boolean brightnessAdjusted = false;
         final boolean brightnessIsTemporary =
                 (mBrightnessReason.getReason() == BrightnessReason.REASON_TEMPORARY)
-                        || mAppliedTemporaryAutoBrightnessAdjustment;
+                        || mAutomaticBrightnessStrategy
+                        .isTemporaryAutoBrightnessAdjustmentApplied();
         if (!mPendingScreenOff) {
             if (mSkipScreenOnBrightnessRamp) {
                 if (state == Display.STATE_ON) {
@@ -1524,6 +1453,7 @@
 
             final float currentBrightness = mPowerState.getScreenBrightness();
             final float currentSdrBrightness = mPowerState.getSdrScreenBrightness();
+
             if (BrightnessUtils.isValidBrightnessValue(animateValue)
                     && (animateValue != currentBrightness
                     || sdrAnimateValue != currentSdrBrightness)) {
@@ -1605,7 +1535,8 @@
         mTempBrightnessEvent.setWasShortTermModelActive(wasShortTermModelActive);
         mTempBrightnessEvent.setDisplayBrightnessStrategyName(displayBrightnessState
                 .getDisplayBrightnessStrategyName());
-        mTempBrightnessEvent.setAutomaticBrightnessEnabled(mUseAutoBrightness);
+        mTempBrightnessEvent.setAutomaticBrightnessEnabled(mAutomaticBrightnessStrategy
+                .shouldUseAutoBrightness());
         // Temporary is what we use during slider interactions. We avoid logging those so that
         // we don't spam logcat when the slider is being used.
         boolean tempToTempTransition =
@@ -2151,13 +2082,12 @@
         mDisplayBrightnessController
                 .setPendingScreenBrightness(mDisplayBrightnessController
                         .getScreenBrightnessSetting());
-        mPendingAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting();
+        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(userSwitch);
         if (userSwitch) {
             // Don't treat user switches as user initiated change.
             mDisplayBrightnessController
                     .setAndNotifyCurrentScreenBrightness(mDisplayBrightnessController
                             .getPendingScreenBrightness());
-            updateAutoBrightnessAdjustment();
             if (mAutomaticBrightnessController != null) {
                 mAutomaticBrightnessController.resetShortTermModel();
             }
@@ -2171,8 +2101,8 @@
                 Settings.System.SCREEN_BRIGHTNESS_MODE,
                 Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, UserHandle.USER_CURRENT);
         mHandler.postAtTime(() -> {
-            mUseAutoBrightness = screenBrightnessModeSetting
-                    == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC;
+            mAutomaticBrightnessStrategy.setUseAutoBrightness(screenBrightnessModeSetting
+                    == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
             updatePowerState();
         }, mClock.uptimeMillis());
     }
@@ -2220,33 +2150,10 @@
         sendUpdatePowerState();
     }
 
-    private void putAutoBrightnessAdjustmentSetting(float adjustment) {
-        if (mDisplayId == Display.DEFAULT_DISPLAY) {
-            mAutoBrightnessAdjustment = adjustment;
-            Settings.System.putFloatForUser(mContext.getContentResolver(),
-                    Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, adjustment,
-                    UserHandle.USER_CURRENT);
-        }
-    }
-
-    private boolean updateAutoBrightnessAdjustment() {
-        if (Float.isNaN(mPendingAutoBrightnessAdjustment)) {
-            return false;
-        }
-        if (mAutoBrightnessAdjustment == mPendingAutoBrightnessAdjustment) {
-            mPendingAutoBrightnessAdjustment = Float.NaN;
-            return false;
-        }
-        mAutoBrightnessAdjustment = mPendingAutoBrightnessAdjustment;
-        mPendingAutoBrightnessAdjustment = Float.NaN;
-        mTemporaryAutoBrightnessAdjustment = Float.NaN;
-        return true;
-    }
-
     private void notifyBrightnessTrackerChanged(float brightness, boolean userInitiated,
             boolean wasShortTermModelActive) {
         final float brightnessInNits = mDisplayBrightnessController.convertToNits(brightness);
-        if (mUseAutoBrightness && brightnessInNits >= 0.0f
+        if (mAutomaticBrightnessStrategy.shouldUseAutoBrightness() && brightnessInNits >= 0.0f
                 && mAutomaticBrightnessController != null && mBrightnessTracker != null) {
             // We only want to track changes on devices that can actually map the display backlight
             // values into a physical brightness unit since the value provided by the API is in
@@ -2329,16 +2236,10 @@
         pw.println();
         pw.println("Display Power Controller Thread State:");
         pw.println("  mPowerRequest=" + mPowerRequest);
-        pw.println("  mAutoBrightnessAdjustment=" + mAutoBrightnessAdjustment);
         pw.println("  mBrightnessReason=" + mBrightnessReason);
-        pw.println("  mTemporaryAutoBrightnessAdjustment=" + mTemporaryAutoBrightnessAdjustment);
-        pw.println("  mPendingAutoBrightnessAdjustment=" + mPendingAutoBrightnessAdjustment);
-        pw.println("  mAppliedAutoBrightness=" + mAppliedAutoBrightness);
         pw.println("  mAppliedDimming=" + mAppliedDimming);
         pw.println("  mAppliedLowPower=" + mAppliedLowPower);
         pw.println("  mAppliedThrottling=" + mAppliedThrottling);
-        pw.println("  mAppliedTemporaryAutoBrightnessAdjustment="
-                + mAppliedTemporaryAutoBrightnessAdjustment);
         pw.println("  mDozing=" + mDozing);
         pw.println("  mSkipRampState=" + skipRampStateToString(mSkipRampState));
         pw.println("  mScreenOnBlockStartRealTime=" + mScreenOnBlockStartRealTime);
@@ -2349,6 +2250,8 @@
         pw.println("  mReportedToPolicy="
                 + reportedToPolicyToString(mReportedScreenStateToPolicy));
         pw.println("  mIsRbcActive=" + mIsRbcActive);
+        IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "    ");
+        mAutomaticBrightnessStrategy.dump(ipw);
 
         if (mScreenBrightnessRampAnimator != null) {
             pw.println("  mScreenBrightnessRampAnimator.isAnimating()="
@@ -2580,8 +2483,15 @@
                     }
                     break;
                 case MSG_CONFIGURE_BRIGHTNESS:
-                    mBrightnessConfiguration = (BrightnessConfiguration) msg.obj;
-                    mShouldResetShortTermModel = msg.arg1 == 1;
+                    BrightnessConfiguration brightnessConfiguration =
+                            (BrightnessConfiguration) msg.obj;
+                    mAutomaticBrightnessStrategy.setBrightnessConfiguration(brightnessConfiguration,
+                            msg.arg1 == 1);
+                    if (mBrightnessTracker != null) {
+                        mBrightnessTracker
+                                .setShouldCollectColorSample(brightnessConfiguration != null
+                                        && brightnessConfiguration.shouldCollectColorSamples());
+                    }
                     updatePowerState();
                     break;
 
@@ -2593,7 +2503,8 @@
                     break;
 
                 case MSG_SET_TEMPORARY_AUTO_BRIGHTNESS_ADJUSTMENT:
-                    mTemporaryAutoBrightnessAdjustment = Float.intBitsToFloat(msg.arg1);
+                    mAutomaticBrightnessStrategy
+                            .setTemporaryAutoBrightnessAdjustment(Float.intBitsToFloat(msg.arg1));
                     updatePowerState();
                     break;
 
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 250ba43..424eedc 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -302,9 +302,16 @@
     }
 
     public void forEachLocked(Consumer<LogicalDisplay> consumer) {
+        forEachLocked(consumer, /* includeDisabled= */ true);
+    }
+
+    public void forEachLocked(Consumer<LogicalDisplay> consumer, boolean includeDisabled) {
         final int count = mLogicalDisplays.size();
         for (int i = 0; i < count; i++) {
-            consumer.accept(mLogicalDisplays.valueAt(i));
+            LogicalDisplay display = mLogicalDisplays.valueAt(i);
+            if (display.isEnabledLocked() || includeDisabled) {
+                consumer.accept(display);
+            }
         }
     }
 
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
new file mode 100644
index 0000000..f6cf866
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
@@ -0,0 +1,404 @@
+/*
+ * 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.
+ */
+package com.android.server.display.brightness.strategy;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.display.BrightnessConfiguration;
+import android.os.PowerManager;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.view.Display;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.AutomaticBrightnessController;
+import com.android.server.display.brightness.BrightnessReason;
+import com.android.server.display.brightness.BrightnessUtils;
+
+import java.io.PrintWriter;
+
+/**
+ * Helps manage the brightness based on the ambient environment (Ambient Light/lux sensor) using
+ * mappings from lux to nits to brightness, configured in the
+ * {@link com.android.server.display.DisplayDeviceConfig} class. This class inherently assumes
+ * that it is being executed from the power thread, and hence doesn't synchronize
+ * any of its resources
+ */
+public class AutomaticBrightnessStrategy {
+    private final Context mContext;
+    // The DisplayId of the associated logical display
+    private final int mDisplayId;
+    // The last auto brightness adjustment that was set by the user and is not temporary. Set to
+    // Float.NaN when an auto-brightness adjustment hasn't been recorded yet.
+    private float mAutoBrightnessAdjustment;
+    // The pending auto brightness adjustment that will take effect on the next power state update.
+    private float mPendingAutoBrightnessAdjustment;
+    // The temporary auto brightness adjustment. This was historically used when a user interacts
+    // with the adjustment slider but hasn't settled on a choice yet.
+    // Set to PowerManager.BRIGHTNESS_INVALID_FLOAT when there's no temporary adjustment set.
+    private float mTemporaryAutoBrightnessAdjustment;
+    // Indicates if the temporary auto brightness adjustment has been applied while updating the
+    // associated display brightness
+    private boolean mAppliedTemporaryAutoBrightnessAdjustment;
+    // Indicates if the auto brightness adjustment has happened.
+    private boolean mAutoBrightnessAdjustmentChanged;
+    // Indicates the reasons for the auto-brightness adjustment
+    private int mAutoBrightnessAdjustmentReasonsFlags = 0;
+    // Indicates if the short term model should be reset before fetching the new brightness
+    // Todo(273543270): Short term model is an internal information of
+    //  AutomaticBrightnessController and shouldn't be exposed outside of that class
+    private boolean mShouldResetShortTermModel = false;
+    // Remembers whether the auto-brightness has been applied in the latest brightness update.
+    private boolean mAppliedAutoBrightness = false;
+    // The controller for the automatic brightness level.
+    @Nullable
+    private AutomaticBrightnessController mAutomaticBrightnessController;
+    // The system setting denoting if the auto-brightness for the current user is enabled or not
+    private boolean mUseAutoBrightness = false;
+    // Indicates if the auto-brightness is currently enabled or not. It's possible that even if
+    // the user has enabled the auto-brightness from the settings, it is disabled because the
+    // display is off
+    private boolean mIsAutoBrightnessEnabled = false;
+    // If the auto-brightness model for the last manual changes done by the user.
+    private boolean mIsShortTermModelActive = false;
+
+    // The BrightnessConfiguration currently being used
+    // Todo(273543270): BrightnessConfiguration is an internal implementation detail of
+    //  AutomaticBrightnessController, and AutomaticBrightnessStrategy shouldn't be aware of its
+    //  existence.
+    @Nullable
+    private BrightnessConfiguration mBrightnessConfiguration;
+
+    public AutomaticBrightnessStrategy(Context context, int displayId) {
+        mContext = context;
+        mDisplayId = displayId;
+        mAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting();
+        mPendingAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+        mTemporaryAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+    }
+
+    /**
+     * Sets up the automatic brightness states of this class. Also configures
+     * AutomaticBrightnessController accounting for any manual changes made by the user.
+     */
+    public void setAutoBrightnessState(int targetDisplayState,
+            boolean allowAutoBrightnessWhileDozingConfig,
+            float brightnessState, int brightnessReason, int policy,
+            float lastUserSetScreenBrightness, boolean userSetBrightnessChanged) {
+        final boolean autoBrightnessEnabledInDoze =
+                allowAutoBrightnessWhileDozingConfig
+                        && Display.isDozeState(targetDisplayState);
+        mIsAutoBrightnessEnabled = shouldUseAutoBrightness()
+                && (targetDisplayState == Display.STATE_ON || autoBrightnessEnabledInDoze)
+                && (Float.isNaN(brightnessState)
+                || brightnessReason == BrightnessReason.REASON_TEMPORARY
+                || brightnessReason == BrightnessReason.REASON_BOOST)
+                && mAutomaticBrightnessController != null
+                && brightnessReason != BrightnessReason.REASON_FOLLOWER;
+        final boolean autoBrightnessDisabledDueToDisplayOff = shouldUseAutoBrightness()
+                && !(targetDisplayState == Display.STATE_ON || autoBrightnessEnabledInDoze);
+        final int autoBrightnessState = mIsAutoBrightnessEnabled
+                ? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
+                : autoBrightnessDisabledDueToDisplayOff
+                        ? AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE
+                        : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED;
+
+        accommodateUserBrightnessChanges(userSetBrightnessChanged, lastUserSetScreenBrightness,
+                policy, mBrightnessConfiguration, autoBrightnessState);
+    }
+
+    public boolean isAutoBrightnessEnabled() {
+        return mIsAutoBrightnessEnabled;
+    }
+
+    /**
+     * Updates the {@link BrightnessConfiguration} that is currently being used by the associated
+     * display.
+     */
+    public void setBrightnessConfiguration(BrightnessConfiguration brightnessConfiguration,
+            boolean shouldResetShortTermModel) {
+        mBrightnessConfiguration = brightnessConfiguration;
+        setShouldResetShortTermModel(shouldResetShortTermModel);
+    }
+
+    /**
+     * Promotes the pending auto-brightness adjustments which are yet to be applied to the current
+     * adjustments. Note that this is not applying the new adjustments to the AutoBrightness mapping
+     * strategies, but is only accommodating the changes in this class.
+     */
+    public boolean processPendingAutoBrightnessAdjustments() {
+        mAutoBrightnessAdjustmentChanged = false;
+        if (Float.isNaN(mPendingAutoBrightnessAdjustment)) {
+            return false;
+        }
+        if (mAutoBrightnessAdjustment == mPendingAutoBrightnessAdjustment) {
+            mPendingAutoBrightnessAdjustment = Float.NaN;
+            return false;
+        }
+        mAutoBrightnessAdjustment = mPendingAutoBrightnessAdjustment;
+        mPendingAutoBrightnessAdjustment = Float.NaN;
+        mTemporaryAutoBrightnessAdjustment = Float.NaN;
+        mAutoBrightnessAdjustmentChanged = true;
+        return true;
+    }
+
+    /**
+     * Updates the associated AutomaticBrightnessController
+     */
+    public void setAutomaticBrightnessController(
+            AutomaticBrightnessController automaticBrightnessController) {
+        if (automaticBrightnessController == mAutomaticBrightnessController) {
+            return;
+        }
+        if (mAutomaticBrightnessController != null) {
+            mAutomaticBrightnessController.stop();
+        }
+        mAutomaticBrightnessController = automaticBrightnessController;
+    }
+
+    /**
+     * Returns if the auto-brightness of the associated display has been enabled or not
+     */
+    public boolean shouldUseAutoBrightness() {
+        return mUseAutoBrightness;
+    }
+
+    /**
+     * Sets the auto-brightness state of the associated display. Called when the user makes a change
+     * in the system setting to enable/disable the auto-brightness.
+     */
+    public void setUseAutoBrightness(boolean useAutoBrightness) {
+        mUseAutoBrightness = useAutoBrightness;
+    }
+
+    /**
+     * Returns if the user made brightness change events(Typically when they interact with the
+     * brightness slider) were accommodated in the auto-brightness mapping strategies. This doesn't
+     * account for the latest changes that have been made by the user.
+     */
+    public boolean isShortTermModelActive() {
+        return mIsShortTermModelActive;
+    }
+
+    /**
+     * Sets the pending auto-brightness adjustments in the system settings. Executed
+     * when there is a change in the brightness system setting, or when there is a user switch.
+     */
+    public void updatePendingAutoBrightnessAdjustments(boolean userSwitch) {
+        final float adj = Settings.System.getFloatForUser(mContext.getContentResolver(),
+                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.0f, UserHandle.USER_CURRENT);
+        mPendingAutoBrightnessAdjustment = Float.isNaN(adj) ? Float.NaN
+                : BrightnessUtils.clampAbsoluteBrightness(adj);
+        if (userSwitch) {
+            processPendingAutoBrightnessAdjustments();
+        }
+    }
+
+    /**
+     * Sets the temporary auto-brightness adjustments
+     */
+    public void setTemporaryAutoBrightnessAdjustment(float temporaryAutoBrightnessAdjustment) {
+        mTemporaryAutoBrightnessAdjustment = temporaryAutoBrightnessAdjustment;
+    }
+
+    /**
+     * Dumps the state of this class.
+     */
+    public void dump(PrintWriter writer) {
+        writer.println("AutomaticBrightnessStrategy:");
+        writer.println("  mDisplayId=" + mDisplayId);
+        writer.println("  mAutoBrightnessAdjustment=" + mAutoBrightnessAdjustment);
+        writer.println("  mPendingAutoBrightnessAdjustment=" + mPendingAutoBrightnessAdjustment);
+        writer.println(
+                "  mTemporaryAutoBrightnessAdjustment=" + mTemporaryAutoBrightnessAdjustment);
+        writer.println("  mShouldResetShortTermModel=" + mShouldResetShortTermModel);
+        writer.println("  mAppliedAutoBrightness=" + mAppliedAutoBrightness);
+        writer.println("  mAutoBrightnessAdjustmentChanged=" + mAutoBrightnessAdjustmentChanged);
+        writer.println("  mAppliedTemporaryAutoBrightnessAdjustment="
+                + mAppliedTemporaryAutoBrightnessAdjustment);
+        writer.println("  mUseAutoBrightness=" + mUseAutoBrightness);
+        writer.println("  mWasShortTermModelActive=" + mIsShortTermModelActive);
+        writer.println("  mAutoBrightnessAdjustmentReasonsFlags="
+                + mAutoBrightnessAdjustmentReasonsFlags);
+    }
+
+    /**
+     * Indicates if any auto-brightness adjustments have happened since the last auto-brightness was
+     * set.
+     */
+    public boolean getAutoBrightnessAdjustmentChanged() {
+        return mAutoBrightnessAdjustmentChanged;
+    }
+
+    /**
+     * Returns whether the latest temporary auto-brightness adjustments have been applied or not
+     */
+    public boolean isTemporaryAutoBrightnessAdjustmentApplied() {
+        return mAppliedTemporaryAutoBrightnessAdjustment;
+    }
+
+    /**
+     * Evaluates the target automatic brightness of the associated display.
+     */
+    public float getAutomaticScreenBrightness() {
+        float brightness = (mAutomaticBrightnessController != null)
+                ? mAutomaticBrightnessController.getAutomaticScreenBrightness()
+                : PowerManager.BRIGHTNESS_INVALID_FLOAT;
+        adjustAutomaticBrightnessStateIfValid(brightness);
+        return brightness;
+    }
+
+    /**
+     * Gets the auto-brightness adjustment flag change reason
+     */
+    public int getAutoBrightnessAdjustmentReasonsFlags() {
+        return mAutoBrightnessAdjustmentReasonsFlags;
+    }
+
+    /**
+     * Returns if the auto brightness has been applied
+     */
+    public boolean hasAppliedAutoBrightness() {
+        return mAppliedAutoBrightness;
+    }
+
+    /**
+     * Used to adjust the state of this class when the automatic brightness value for the
+     * associated display is valid
+     */
+    @VisibleForTesting
+    void adjustAutomaticBrightnessStateIfValid(float brightnessState) {
+        mAutoBrightnessAdjustmentReasonsFlags = isTemporaryAutoBrightnessAdjustmentApplied()
+                ? BrightnessReason.ADJUSTMENT_AUTO_TEMP
+                : BrightnessReason.ADJUSTMENT_AUTO;
+        mAppliedAutoBrightness = BrightnessUtils.isValidBrightnessValue(brightnessState)
+                || brightnessState == PowerManager.BRIGHTNESS_OFF_FLOAT;
+        float newAutoBrightnessAdjustment =
+                (mAutomaticBrightnessController != null)
+                        ? mAutomaticBrightnessController.getAutomaticScreenBrightnessAdjustment()
+                        : 0.0f;
+        if (!Float.isNaN(newAutoBrightnessAdjustment)
+                && mAutoBrightnessAdjustment != newAutoBrightnessAdjustment) {
+            // If the auto-brightness controller has decided to change the adjustment value
+            // used, make sure that's reflected in settings.
+            putAutoBrightnessAdjustmentSetting(newAutoBrightnessAdjustment);
+        } else {
+            mAutoBrightnessAdjustmentReasonsFlags = 0;
+        }
+    }
+
+    /**
+     * Sets up the system to reset the short term model. Note that this will not reset the model
+     * right away, but ensures that the reset happens whenever the next brightness change happens
+     */
+    @VisibleForTesting
+    void setShouldResetShortTermModel(boolean shouldResetShortTermModel) {
+        mShouldResetShortTermModel = shouldResetShortTermModel;
+    }
+
+    @VisibleForTesting
+    boolean shouldResetShortTermModel() {
+        return mShouldResetShortTermModel;
+    }
+
+    @VisibleForTesting
+    float getAutoBrightnessAdjustment() {
+        return mAutoBrightnessAdjustment;
+    }
+
+    @VisibleForTesting
+    float getPendingAutoBrightnessAdjustment() {
+        return mPendingAutoBrightnessAdjustment;
+    }
+
+    @VisibleForTesting
+    float getTemporaryAutoBrightnessAdjustment() {
+        return mTemporaryAutoBrightnessAdjustment;
+    }
+
+    @VisibleForTesting
+    void putAutoBrightnessAdjustmentSetting(float adjustment) {
+        if (mDisplayId == Display.DEFAULT_DISPLAY) {
+            mAutoBrightnessAdjustment = adjustment;
+            Settings.System.putFloatForUser(mContext.getContentResolver(),
+                    Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, adjustment,
+                    UserHandle.USER_CURRENT);
+        }
+    }
+
+    /**
+     * Sets if the auto-brightness is applied on the latest brightness change.
+     */
+    @VisibleForTesting
+    void setAutoBrightnessApplied(boolean autoBrightnessApplied) {
+        mAppliedAutoBrightness = autoBrightnessApplied;
+    }
+
+    /**
+     * Accommodates the latest manual changes made by the user. Also updates {@link
+     * AutomaticBrightnessController} about the changes and configures it accordingly.
+     */
+    @VisibleForTesting
+    void accommodateUserBrightnessChanges(boolean userSetBrightnessChanged,
+            float lastUserSetScreenBrightness, int policy,
+            BrightnessConfiguration brightnessConfiguration, int autoBrightnessState) {
+        // Update the pending auto-brightness adjustments if any. This typically checks and adjusts
+        // the state of the class if the user moves the brightness slider and has settled to a
+        // different value
+        processPendingAutoBrightnessAdjustments();
+        // Update the temporary auto-brightness adjustments if any. This typically checks and
+        // adjusts the state of this class if the user is in the process of moving the brightness
+        // slider, but hasn't settled to any value yet
+        float autoBrightnessAdjustment = updateTemporaryAutoBrightnessAdjustments();
+        mIsShortTermModelActive = false;
+        // Configure auto-brightness.
+        if (mAutomaticBrightnessController != null) {
+            // Accommodate user changes if any in the auto-brightness model
+            mAutomaticBrightnessController.configure(autoBrightnessState,
+                    brightnessConfiguration,
+                    lastUserSetScreenBrightness,
+                    userSetBrightnessChanged, autoBrightnessAdjustment,
+                    mAutoBrightnessAdjustmentChanged, policy, mShouldResetShortTermModel);
+            mShouldResetShortTermModel = false;
+            // We take note if the user brightness point is still being used in the current
+            // auto-brightness model.
+            mIsShortTermModelActive = mAutomaticBrightnessController.hasUserDataPoints();
+        }
+    }
+
+    /**
+     * Evaluates if there are any temporary auto-brightness adjustments which is not applied yet.
+     * Temporary brightness adjustments happen when the user moves the brightness slider in the
+     * auto-brightness mode, but hasn't settled to a value yet
+     */
+    private float updateTemporaryAutoBrightnessAdjustments() {
+        mAppliedTemporaryAutoBrightnessAdjustment =
+                !Float.isNaN(mTemporaryAutoBrightnessAdjustment);
+        // We do not update the mAutoBrightnessAdjustment with mTemporaryAutoBrightnessAdjustment
+        // since we have not settled to a value yet
+        return mAppliedTemporaryAutoBrightnessAdjustment
+                ? mTemporaryAutoBrightnessAdjustment : mAutoBrightnessAdjustment;
+    }
+
+    /**
+     * Returns the auto-brightness adjustment that is set in the system setting.
+     */
+    private float getAutoBrightnessAdjustmentSetting() {
+        final float adj = Settings.System.getFloatForUser(mContext.getContentResolver(),
+                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.0f, UserHandle.USER_CURRENT);
+        return Float.isNaN(adj) ? 0.0f : BrightnessUtils.clampAbsoluteBrightness(adj);
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiEarcLocalDeviceTx.java b/services/core/java/com/android/server/hdmi/HdmiEarcLocalDeviceTx.java
index abb8439..9058c98 100644
--- a/services/core/java/com/android/server/hdmi/HdmiEarcLocalDeviceTx.java
+++ b/services/core/java/com/android/server/hdmi/HdmiEarcLocalDeviceTx.java
@@ -80,7 +80,7 @@
     protected void handleEarcStateChange(@Constants.EarcStatus int status) {
         int oldEarcStatus;
         synchronized (mLock) {
-            HdmiLogger.debug(TAG, "eARC state change [old:%b new %b]", mEarcStatus,
+            HdmiLogger.debug("eARC state change [old:%b new %b]", mEarcStatus,
                     status);
             oldEarcStatus = mEarcStatus;
             mEarcStatus = status;
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index efc4f11..d0669e7 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -547,6 +547,10 @@
         mBatteryController.systemRunning();
         mKeyboardBacklightController.systemRunning();
         mKeyRemapper.systemRunning();
+
+        mNative.setStylusPointerIconEnabled(
+                Objects.requireNonNull(mContext.getSystemService(InputManager.class))
+                        .isStylusPointerIconEnabled());
     }
 
     private void reloadDeviceAliases() {
@@ -2749,13 +2753,6 @@
         return null;
     }
 
-    // Native callback.
-    @SuppressWarnings("unused")
-    private boolean isStylusPointerIconEnabled() {
-        return Objects.requireNonNull(mContext.getSystemService(InputManager.class))
-                .isStylusPointerIconEnabled();
-    }
-
     private static class PointerDisplayIdChangedArgs {
         final int mPointerDisplayId;
         final float mXPosition;
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index 4d4a87e..72c7dad 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -1261,30 +1261,45 @@
 
     private static boolean isLayoutCompatibleWithLanguageTag(KeyboardLayout layout,
             @NonNull String languageTag) {
-        final int[] scriptsFromLanguageTag = UScript.getCode(Locale.forLanguageTag(languageTag));
-        if (scriptsFromLanguageTag.length == 0) {
-            // If no scripts inferred from languageTag then allowing the layout
-            return true;
-        }
-        LocaleList locales = layout.getLocales();
-        if (locales.isEmpty()) {
+        LocaleList layoutLocales = layout.getLocales();
+        if (layoutLocales.isEmpty()) {
             // KCM file doesn't have an associated language tag. This can be from
             // a 3rd party app so need to include it as a potential layout.
             return true;
         }
-        for (int i = 0; i < locales.size(); i++) {
-            final Locale locale = locales.get(i);
-            if (locale == null) {
-                continue;
-            }
-            int[] scripts = UScript.getCode(locale);
-            if (scripts != null && haveCommonValue(scripts, scriptsFromLanguageTag)) {
+        // Match derived Script codes
+        final int[] scriptsFromLanguageTag = getScriptCodes(Locale.forLanguageTag(languageTag));
+        if (scriptsFromLanguageTag.length == 0) {
+            // If no scripts inferred from languageTag then allowing the layout
+            return true;
+        }
+        for (int i = 0; i < layoutLocales.size(); i++) {
+            final Locale locale = layoutLocales.get(i);
+            int[] scripts = getScriptCodes(locale);
+            if (haveCommonValue(scripts, scriptsFromLanguageTag)) {
                 return true;
             }
         }
         return false;
     }
 
+    private static int[] getScriptCodes(@Nullable Locale locale) {
+        if (locale == null) {
+            return new int[0];
+        }
+        if (!TextUtils.isEmpty(locale.getScript())) {
+            int scriptCode = UScript.getCodeFromName(locale.getScript());
+            if (scriptCode != UScript.INVALID_CODE) {
+                return new int[]{scriptCode};
+            }
+        }
+        int[] scripts = UScript.getCode(locale);
+        if (scripts != null) {
+            return scripts;
+        }
+        return new int[0];
+    }
+
     private static boolean haveCommonValue(int[] arr1, int[] arr2) {
         for (int a1 : arr1) {
             for (int a2 : arr2) {
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 5395302d..a0918e4 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -234,6 +234,9 @@
      */
     float[] getMouseCursorPosition();
 
+    /** Set whether showing a pointer icon for styluses is enabled. */
+    void setStylusPointerIconEnabled(boolean enabled);
+
     /** The native implementation of InputManagerService methods. */
     class NativeImpl implements NativeInputManagerService {
         /** Pointer to native input manager service object, used by native code. */
@@ -478,5 +481,8 @@
 
         @Override
         public native float[] getMouseCursorPosition();
+
+        @Override
+        public native void setStylusPointerIconEnabled(boolean enabled);
     }
 }
diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
index ceb9706..61fe654 100644
--- a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
+++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
@@ -284,7 +284,7 @@
 
     void setWindowState(IBinder windowToken, @NonNull ImeTargetWindowState newState) {
         final ImeTargetWindowState state = mRequestWindowStateMap.get(windowToken);
-        if (state != null && newState.hasEdiorFocused()) {
+        if (state != null && newState.hasEditorFocused()) {
             // Inherit the last requested IME visible state when the target window is still
             // focused with an editor.
             newState.setRequestedImeVisible(state.mRequestedImeVisible);
@@ -340,7 +340,7 @@
         // state is ALWAYS_HIDDEN or STATE_HIDDEN with forward navigation).
         // Because the app might leverage these flags to hide soft-keyboard with showing their own
         // UI for input.
-        if (state.hasEdiorFocused() && shouldRestoreImeVisibility(state)) {
+        if (state.hasEditorFocused() && shouldRestoreImeVisibility(state)) {
             if (DEBUG) Slog.v(TAG, "Will show input to restore visibility");
             // Inherit the last requested IME visible state when the target window is still
             // focused with an editor.
@@ -352,7 +352,7 @@
 
         switch (softInputVisibility) {
             case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED:
-                if (state.hasImeFocusChanged() && (!state.hasEdiorFocused() || !doAutoShow)) {
+                if (state.hasImeFocusChanged() && (!state.hasEditorFocused() || !doAutoShow)) {
                     if (WindowManager.LayoutParams.mayUseInputMethod(state.getWindowFlags())) {
                         // There is no focus view, and this window will
                         // be behind any soft input window, so hide the
@@ -361,7 +361,7 @@
                         return new ImeVisibilityResult(STATE_HIDE_IME_NOT_ALWAYS,
                                 SoftInputShowHideReason.HIDE_UNSPECIFIED_WINDOW);
                     }
-                } else if (state.hasEdiorFocused() && doAutoShow && isForwardNavigation) {
+                } else if (state.hasEditorFocused() && doAutoShow && isForwardNavigation) {
                     // There is a focus view, and we are navigating forward
                     // into the window, so show the input window for the user.
                     // We only do this automatically if the window can resize
@@ -437,7 +437,7 @@
                         SoftInputShowHideReason.HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR);
             }
         }
-        if (!state.hasEdiorFocused() && mInputShown && state.isStartInputByGainFocus()
+        if (!state.hasEditorFocused() && mInputShown && state.isStartInputByGainFocus()
                 && mService.mInputMethodDeviceConfigs.shouldHideImeWhenNoEditorFocus()) {
             // Hide the soft-keyboard when the system do nothing for softInputModeState
             // of the window being gained focus without an editor. This behavior benefits
@@ -620,7 +620,7 @@
             return mImeFocusChanged;
         }
 
-        boolean hasEdiorFocused() {
+        boolean hasEditorFocused() {
             return mHasFocusedEditor;
         }
 
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 16155a0..5ea2ca4 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -17,6 +17,8 @@
 package com.android.server.media;
 
 import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
 import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Context;
@@ -55,6 +57,8 @@
 import android.util.Log;
 import android.view.KeyEvent;
 
+import com.android.server.LocalServices;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -422,6 +426,13 @@
      */
     @Override
     public void close() {
+        // Log the session's active state
+        // to measure usage of foreground service resources
+        int callingUid = Binder.getCallingUid();
+        int callingPid = Binder.getCallingPid();
+        LocalServices.getService(ActivityManagerInternal.class)
+                .logFgsApiEnd(ActivityManager.FOREGROUND_SERVICE_API_TYPE_MEDIA_PLAYBACK,
+                callingUid, callingPid);
         synchronized (mLock) {
             if (mDestroyed) {
                 return;
@@ -884,8 +895,22 @@
 
         @Override
         public void setActive(boolean active) throws RemoteException {
+            // Log the session's active state
+            // to measure usage of foreground service resources
+            int callingUid = Binder.getCallingUid();
+            int callingPid = Binder.getCallingPid();
+            if (active) {
+                LocalServices.getService(ActivityManagerInternal.class)
+                        .logFgsApiBegin(ActivityManager.FOREGROUND_SERVICE_API_TYPE_MEDIA_PLAYBACK,
+                                callingUid, callingPid);
+            } else {
+                LocalServices.getService(ActivityManagerInternal.class)
+                        .logFgsApiEnd(ActivityManager.FOREGROUND_SERVICE_API_TYPE_MEDIA_PLAYBACK,
+                                callingUid, callingPid);
+            }
+
             mIsActive = active;
-            final long token = Binder.clearCallingIdentity();
+            long token = Binder.clearCallingIdentity();
             try {
                 mService.onSessionActiveStateChanged(MediaSessionRecord.this);
             } finally {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 431925c..a4eb417 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -3328,10 +3328,10 @@
             }
 
             if (!isUiContext && displayId == Display.DEFAULT_DISPLAY
-                    && UserManager.isVisibleBackgroundUsersEnabled()) {
-                // When the caller is a visible background user using a non-ui context (like the
+                    && mUm.isVisibleBackgroundUsersSupported()) {
+                // When the caller is a visible background user using a non-UI context (like the
                 // application context), the Toast must be displayed in the display the user was
-                // started visible on
+                // started visible on.
                 int userId = UserHandle.getUserId(callingUid);
                 int userDisplayId = mUmInternal.getMainDisplayAssignedToUser(userId);
                 if (displayId != userDisplayId) {
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index e698c4b..0605345 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -39,14 +39,18 @@
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.LocalLog;
 import android.util.Pair;
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.DumpUtils;
 import com.android.server.SystemConfig;
+import com.android.server.utils.Slogf;
 
 import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.Objects;
 import java.util.OptionalInt;
 
@@ -56,7 +60,11 @@
  * <p>Delegates the actualy generation to a native implementation of {@code IDumpstate}.
  */
 class BugreportManagerServiceImpl extends IDumpstate.Stub {
+
+    private static final int LOCAL_LOG_SIZE = 20;
     private static final String TAG = "BugreportManagerService";
+    private static final boolean DEBUG = false;
+
     private static final String BUGREPORT_SERVICE = "bugreportd";
     private static final long DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS = 30 * 1000;
 
@@ -64,12 +72,22 @@
     private final Context mContext;
     private final AppOpsManager mAppOps;
     private final TelephonyManager mTelephonyManager;
-    private final ArraySet<String> mBugreportWhitelistedPackages;
+    private final ArraySet<String> mBugreportAllowlistedPackages;
     private final BugreportFileManager mBugreportFileManager;
 
+
     @GuardedBy("mLock")
     private OptionalInt mPreDumpedDataUid = OptionalInt.empty();
 
+    // Attributes below are just Used for dump() purposes
+    @Nullable
+    @GuardedBy("mLock")
+    private DumpstateListener mCurrentDumpstateListener;
+    @GuardedBy("mLock")
+    private int mNumberFinishedBugreports;
+    @GuardedBy("mLock")
+    private final LocalLog mFinishedBugreports = new LocalLog(LOCAL_LOG_SIZE);
+
     /** Helper class for associating previously generated bugreports with their callers. */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     static class BugreportFileManager {
@@ -77,11 +95,8 @@
         private final Object mLock = new Object();
 
         @GuardedBy("mLock")
-        private final ArrayMap<Pair<Integer, String>, ArraySet<String>> mBugreportFiles;
-
-        BugreportFileManager() {
-            mBugreportFiles = new ArrayMap<>();
-        }
+        private final ArrayMap<Pair<Integer, String>, ArraySet<String>> mBugreportFiles =
+                new ArrayMap<>();
 
         /**
          * Checks that a given file was generated on behalf of the given caller. If the file was
@@ -159,11 +174,9 @@
         mAppOps = mContext.getSystemService(AppOpsManager.class);
         mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
         mBugreportFileManager = new BugreportFileManager();
-        mBugreportWhitelistedPackages =
-                injector.getAllowlistedPackages();
+        mBugreportAllowlistedPackages = injector.getAllowlistedPackages();
     }
 
-
     @Override
     @RequiresPermission(android.Manifest.permission.DUMP)
     public void preDumpUiData(String callingPackage) {
@@ -196,6 +209,7 @@
             Binder.restoreCallingIdentity(identity);
         }
 
+        Slogf.i(TAG, "Starting bugreport for %s / %d", callingPackage, callingUid);
         synchronized (mLock) {
             startBugreportLocked(callingUid, callingPackage, bugreportFd, screenshotFd,
                     bugreportMode, bugreportFlags, listener, isScreenshotRequested);
@@ -208,6 +222,7 @@
         int callingUid = Binder.getCallingUid();
         enforcePermission(callingPackage, callingUid, true /* checkCarrierPrivileges */);
 
+        Slogf.i(TAG, "Cancelling bugreport for %s / %d", callingPackage, callingUid);
         synchronized (mLock) {
             IDumpstate ds = getDumpstateBinderServiceLocked();
             if (ds == null) {
@@ -234,6 +249,7 @@
         int callingUid = Binder.getCallingUid();
         enforcePermission(callingPackage, callingUid, false);
 
+        Slogf.i(TAG, "Retrieving bugreport for %s / %d", callingPackage, callingUid);
         try {
             mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
                     new Pair<>(callingUid, callingPackage), bugreportFile);
@@ -260,8 +276,9 @@
             }
 
             // Wrap the listener so we can intercept binder events directly.
-            IDumpstateListener myListener = new DumpstateListener(listener, ds,
-                    new Pair<>(callingUid, callingPackage));
+            DumpstateListener myListener = new DumpstateListener(listener, ds,
+                    new Pair<>(callingUid, callingPackage), /* reportFinishedFile= */ true);
+            setCurrentDumpstateListenerLocked(myListener);
             try {
                 ds.retrieveBugreport(callingUid, callingPackage, bugreportFd,
                         bugreportFile, myListener);
@@ -271,6 +288,16 @@
         }
     }
 
+    @GuardedBy("mLock")
+    private void setCurrentDumpstateListenerLocked(DumpstateListener listener) {
+        if (mCurrentDumpstateListener != null) {
+            Slogf.w(TAG, "setCurrentDumpstateListenerLocked(%s): called when "
+                    + "mCurrentDumpstateListener is already set (%s)", listener,
+                    mCurrentDumpstateListener);
+        }
+        mCurrentDumpstateListener = listener;
+    }
+
     private void validateBugreportMode(@BugreportParams.BugreportMode int mode) {
         if (mode != BugreportParams.BUGREPORT_MODE_FULL
                 && mode != BugreportParams.BUGREPORT_MODE_INTERACTIVE
@@ -299,7 +326,7 @@
 
         // To gain access through the DUMP permission, the OEM has to allow this package explicitly
         // via sysconfig and privileged permissions.
-        if (mBugreportWhitelistedPackages.contains(callingPackage)
+        if (mBugreportAllowlistedPackages.contains(callingPackage)
                 && mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
                         == PackageManager.PERMISSION_GRANTED) {
             return;
@@ -436,7 +463,7 @@
             }
         }
 
-        boolean isConsentDeferred =
+        boolean reportFinishedFile =
                 (bugreportFlags & BugreportParams.BUGREPORT_FLAG_DEFER_CONSENT) != 0;
 
         IDumpstate ds = startAndGetDumpstateBinderServiceLocked();
@@ -446,9 +473,9 @@
             return;
         }
 
-        // Wrap the listener so we can intercept binder events directly.
-        IDumpstateListener myListener = new DumpstateListener(listener, ds,
-                isConsentDeferred ? new Pair<>(callingUid, callingPackage) : null);
+        DumpstateListener myListener = new DumpstateListener(listener, ds,
+                new Pair<>(callingUid, callingPackage), reportFinishedFile);
+        setCurrentDumpstateListenerLocked(myListener);
         try {
             ds.startBugreport(callingUid, callingPackage, bugreportFd, screenshotFd, bugreportMode,
                     bugreportFlags, myListener, isScreenshotRequested);
@@ -522,6 +549,56 @@
         SystemProperties.set("ctl.stop", BUGREPORT_SERVICE);
     }
 
+    @RequiresPermission(android.Manifest.permission.DUMP)
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+
+        pw.printf("Allow-listed packages: %s\n", mBugreportAllowlistedPackages);
+
+        synchronized (mLock) {
+            pw.print("Pre-dumped data UID: ");
+            if (mPreDumpedDataUid.isEmpty()) {
+                pw.println("none");
+            } else {
+                pw.println(mPreDumpedDataUid.getAsInt());
+            }
+
+            if (mCurrentDumpstateListener == null) {
+                pw.println("Not taking a bug report");
+            } else {
+                mCurrentDumpstateListener.dump(pw);
+            }
+
+            if (mNumberFinishedBugreports == 0) {
+                pw.println("No finished bugreports");
+            } else {
+                pw.printf("%d finished bugreport%s. Last %d:\n", mNumberFinishedBugreports,
+                        (mNumberFinishedBugreports > 1 ? "s" : ""),
+                        Math.min(mNumberFinishedBugreports, LOCAL_LOG_SIZE));
+                mFinishedBugreports.dump("  ", pw);
+            }
+        }
+
+        synchronized (mBugreportFileManager.mLock) {
+            int numberFiles = mBugreportFileManager.mBugreportFiles.size();
+            pw.printf("%d pending file%s", numberFiles, (numberFiles > 1 ? "s" : ""));
+            if (numberFiles > 0) {
+                for (int i = 0; i < numberFiles; i++) {
+                    Pair<Integer, String> caller = mBugreportFileManager.mBugreportFiles.keyAt(i);
+                    ArraySet<String> files = mBugreportFileManager.mBugreportFiles.valueAt(i);
+                    pw.printf("  %s: %s\n", callerToString(caller), files);
+                }
+            } else {
+                pw.println();
+            }
+        }
+    }
+
+    private static String callerToString(@Nullable Pair<Integer, String> caller) {
+        return (caller == null) ? "N/A" : caller.second + "/" + caller.first;
+    }
+
     private int clearBugreportFlag(int flags, @BugreportParams.BugreportFlag int flag) {
         flags &= ~flag;
         return flags;
@@ -541,19 +618,28 @@
         throw new IllegalArgumentException(message);
     }
 
-
     private final class DumpstateListener extends IDumpstateListener.Stub
             implements DeathRecipient {
+
+        private static int sNextId;
+
+        private final int mId = ++sNextId; // used for debugging purposes only
         private final IDumpstateListener mListener;
         private final IDumpstate mDs;
-        private boolean mDone = false;
         private final Pair<Integer, String> mCaller;
+        private final boolean mReportFinishedFile;
+        private int mProgress; // used for debugging purposes only
+        private boolean mDone;
 
         DumpstateListener(IDumpstateListener listener, IDumpstate ds,
-                @Nullable Pair<Integer, String> caller) {
+                Pair<Integer, String> caller, boolean reportFinishedFile) {
+            if (DEBUG) {
+                Slogf.d(TAG, "Starting DumpstateListener(id=%d) for caller %s", mId, caller);
+            }
             mListener = listener;
             mDs = ds;
             mCaller = caller;
+            mReportFinishedFile = reportFinishedFile;
             try {
                 mDs.asBinder().linkToDeath(this, 0);
             } catch (RemoteException e) {
@@ -563,35 +649,51 @@
 
         @Override
         public void onProgress(int progress) throws RemoteException {
+            if (DEBUG) {
+                Slogf.d(TAG, "onProgress: %d", progress);
+            }
+            mProgress = progress;
             mListener.onProgress(progress);
         }
 
         @Override
         public void onError(int errorCode) throws RemoteException {
+            Slogf.e(TAG, "onError(): %d", errorCode);
             synchronized (mLock) {
-                mDone = true;
+                releaseItselfLocked();
+                reportFinishedLocked("ErroCode: " + errorCode);
             }
             mListener.onError(errorCode);
         }
 
         @Override
         public void onFinished(String bugreportFile) throws RemoteException {
+            Slogf.i(TAG, "onFinished(): %s", bugreportFile);
             synchronized (mLock) {
-                mDone = true;
+                releaseItselfLocked();
+                reportFinishedLocked("File: " + bugreportFile);
             }
-            if (mCaller != null) {
+            if (mReportFinishedFile) {
                 mBugreportFileManager.addBugreportFileForCaller(mCaller, bugreportFile);
+            } else if (DEBUG) {
+                Slog.d(TAG, "Not reporting finished file");
             }
             mListener.onFinished(bugreportFile);
         }
 
         @Override
         public void onScreenshotTaken(boolean success) throws RemoteException {
+            if (DEBUG) {
+                Slogf.d(TAG, "onScreenshotTaken(): %b", success);
+            }
             mListener.onScreenshotTaken(success);
         }
 
         @Override
         public void onUiIntensiveBugreportDumpsFinished() throws RemoteException {
+            if (DEBUG) {
+                Slogf.d(TAG, "onUiIntensiveBugreportDumpsFinished()");
+            }
             mListener.onUiIntensiveBugreportDumpsFinished();
         }
 
@@ -617,5 +719,39 @@
             }
             mDs.asBinder().unlinkToDeath(this, 0);
         }
+
+        @Override
+        public String toString() {
+            return "DumpstateListener[id=" + mId + ", progress=" + mProgress + "]";
+        }
+
+        @GuardedBy("mLock")
+        private void reportFinishedLocked(String message) {
+            mNumberFinishedBugreports++;
+            mFinishedBugreports.log("Caller: " + callerToString(mCaller) + " " + message);
+        }
+
+        private void dump(PrintWriter pw) {
+            pw.println("DumpstateListener:");
+            pw.printf("  id: %d\n", mId);
+            pw.printf("  caller: %s\n", callerToString(mCaller));
+            pw.printf("  reports finished file: %b\n", mReportFinishedFile);
+            pw.printf("  progress: %d\n", mProgress);
+            pw.printf("  done: %b\n", mDone);
+        }
+
+        @GuardedBy("mLock")
+        private void releaseItselfLocked() {
+            mDone = true;
+            if (mCurrentDumpstateListener == this) {
+                if (DEBUG) {
+                    Slogf.d(TAG, "releaseItselfLocked(): releasing %s", this);
+                }
+                mCurrentDumpstateListener = null;
+            } else {
+                Slogf.w(TAG, "releaseItselfLocked(): " + this + " is finished, but current listener"
+                        + " is " + mCurrentDumpstateListener);
+            }
+        }
     }
 }
diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
index b4b8cb2..ad77ef7 100644
--- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java
+++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
@@ -28,6 +28,7 @@
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
+import android.app.BroadcastOptions;
 import android.app.IActivityManager;
 import android.app.admin.DevicePolicyManagerInternal;
 import android.content.Intent;
@@ -620,12 +621,15 @@
         extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgList);
         extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidList);
         final int flags = Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND;
+        final Bundle options = new BroadcastOptions()
+                .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
+                .toBundle();
         handler.post(() -> mBroadcastHelper.sendPackageBroadcast(intent, null /* pkg */,
                 extras, flags, null /* targetPkg */, null /* finishedReceiver */,
                 new int[]{userId}, null /* instantUserIds */, null /* broadcastAllowList */,
                 (callingUid, intentExtras) -> BroadcastHelper.filterExtrasChangedPackageList(
                         mPm.snapshotComputer(), callingUid, intentExtras),
-                null /* bOptions */));
+                options));
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index a36e9f9..927a722 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1276,7 +1276,15 @@
         getDevicePolicyManagerInternal().broadcastIntentToManifestReceivers(
                 intent, parentHandle, /* requiresPermission= */ true);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
-        mContext.sendBroadcastAsUser(intent, parentHandle);
+        final Bundle options = new BroadcastOptions()
+                .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
+                .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
+                // Both actions use single namespace because only the final state matters.
+                .setDeliveryGroupMatchingKey(
+                        Intent.ACTION_MANAGED_PROFILE_AVAILABLE /* namespace */,
+                        String.valueOf(profileHandle.getIdentifier()) /* key */)
+                .toBundle();
+        mContext.sendBroadcastAsUser(intent, parentHandle, /* receiverPermission= */ null, options);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 88d64df..35e88c1 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -33,6 +33,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.TestApi;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.ActivityThread;
@@ -178,6 +179,7 @@
     private final SessionMonitor mSessionMonitor;
     private int mCurrentUserId;
     private boolean mTracingEnabled;
+    private int mLastSystemKey = -1;
 
     private final TileRequestTracker mTileRequestTracker;
 
@@ -905,6 +907,8 @@
             return;
         }
 
+        mLastSystemKey = key;
+
         if (mBar != null) {
             try {
                 mBar.handleSystemKey(key);
@@ -914,6 +918,14 @@
     }
 
     @Override
+    @TestApi
+    public int getLastSystemKey() {
+        enforceStatusBar();
+
+        return mLastSystemKey;
+    }
+
+    @Override
     public void showPinningEnterExitToast(boolean entering) throws RemoteException {
         if (mBar != null) {
             try {
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 12be1d3..d4f151f 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -104,6 +104,7 @@
 import android.app.WaitResult;
 import android.app.WindowConfiguration;
 import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
 import android.compat.annotation.EnabledSince;
 import android.content.IIntentSender;
 import android.content.Intent;
@@ -188,7 +189,7 @@
      * Feature flag for go/activity-security rules
      */
     @ChangeId
-    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @Disabled
     static final long ASM_RESTRICTIONS = 230590090L;
 
     private final ActivityTaskManagerService mService;
@@ -2947,8 +2948,6 @@
 
         if (differentTopTask && !mAvoidMoveToFront) {
             mStartActivity.intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
-            // TODO(b/264487981): Consider using BackgroundActivityStartController to determine
-            //  whether to bring the launching activity to the front.
             if (mSourceRecord == null || inTopNonFinishingTask(mSourceRecord)) {
                 // We really do want to push this one into the user's face, right now.
                 if (mLaunchTaskBehind && mSourceRecord != null) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 555cd38..7b9cc6f 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -75,9 +75,7 @@
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IMMERSIVE;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_LOCKTASK;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
-import static com.android.server.am.ActivityManagerService.ANR_TRACE_DIR;
 import static com.android.server.am.ActivityManagerService.STOCK_PM_FLAGS;
-import static com.android.server.am.ActivityManagerService.dumpStackTraces;
 import static com.android.server.am.ActivityManagerServiceDumpActivitiesProto.ROOT_WINDOW_CONTAINER;
 import static com.android.server.am.ActivityManagerServiceDumpProcessesProto.CONFIG_WILL_CHANGE;
 import static com.android.server.am.ActivityManagerServiceDumpProcessesProto.CONTROLLER;
@@ -95,6 +93,8 @@
 import static com.android.server.am.ActivityManagerServiceDumpProcessesProto.ScreenCompatPackage.PACKAGE;
 import static com.android.server.am.EventLogTags.writeBootProgressEnableScreen;
 import static com.android.server.am.EventLogTags.writeConfigurationChanged;
+import static com.android.server.am.StackTracesDumpHelper.ANR_TRACE_DIR;
+import static com.android.server.am.StackTracesDumpHelper.dumpStackTraces;
 import static com.android.server.wm.ActivityInterceptorCallback.MAINLINE_FIRST_ORDERED_ID;
 import static com.android.server.wm.ActivityInterceptorCallback.MAINLINE_LAST_ORDERED_ID;
 import static com.android.server.wm.ActivityInterceptorCallback.SYSTEM_FIRST_ORDERED_ID;
diff --git a/services/core/java/com/android/server/wm/AnrController.java b/services/core/java/com/android/server/wm/AnrController.java
index bbe7a33..90ec964 100644
--- a/services/core/java/com/android/server/wm/AnrController.java
+++ b/services/core/java/com/android/server/wm/AnrController.java
@@ -34,7 +34,7 @@
 import android.view.InputApplicationHandle;
 
 import com.android.internal.os.TimeoutRecord;
-import com.android.server.am.ActivityManagerService;
+import com.android.server.am.StackTracesDumpHelper;
 import com.android.server.criticalevents.CriticalEventLog;
 
 import java.io.File;
@@ -336,7 +336,7 @@
 
             String criticalEvents =
                     CriticalEventLog.getInstance().logLinesForSystemServerTraceFile();
-            final File tracesFile = ActivityManagerService.dumpStackTraces(firstPids,
+            final File tracesFile = StackTracesDumpHelper.dumpStackTraces(firstPids,
                     null /* processCpuTracker */, null /* lastPids */,
                     CompletableFuture.completedFuture(nativePids),
                     null /* logExceptionCreatingFile */, "Pre-dump", criticalEvents,
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index b67bc62..fb79c067 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -262,14 +262,23 @@
                 if (!isOccluded || prevActivity.canShowWhenLocked()) {
                     // We have another Activity in the same currentTask to go to
                     final WindowContainer parent = currentActivity.getParent();
-                    final boolean isCustomize = parent != null
+                    final boolean canCustomize = parent != null
                             && (parent.asTask() != null
                             || (parent.asTaskFragment() != null
-                            && parent.canCustomizeAppTransition()))
-                            && isCustomizeExitAnimation(window);
-                    if (isCustomize) {
-                        infoBuilder.setWindowAnimations(
-                                window.mAttrs.packageName, window.mAttrs.windowAnimations);
+                            && parent.canCustomizeAppTransition()));
+                    if (canCustomize) {
+                        if (isCustomizeExitAnimation(window)) {
+                            infoBuilder.setWindowAnimations(
+                                    window.mAttrs.packageName, window.mAttrs.windowAnimations);
+                        }
+                        final ActivityRecord.CustomAppTransition customAppTransition =
+                                currentActivity.getCustomAnimation(false/* open */);
+                        if (customAppTransition != null) {
+                            infoBuilder.setCustomAnimation(currentActivity.packageName,
+                                    customAppTransition.mExitAnim,
+                                    customAppTransition.mEnterAnim,
+                                    customAppTransition.mBackgroundColor);
+                        }
                     }
                     removedWindowContainer = currentActivity;
                     prevTask = prevActivity.getTask();
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 37ee2e2..1604c2a 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1567,7 +1567,14 @@
         if (configChanged) {
             mWaitingForConfig = true;
             if (mTransitionController.isShellTransitionsEnabled()) {
-                requestChangeTransitionIfNeeded(changes, null /* displayChange */);
+                final TransitionRequestInfo.DisplayChange change =
+                        mTransitionController.isCollecting()
+                                ? null : new TransitionRequestInfo.DisplayChange(mDisplayId);
+                if (change != null) {
+                    change.setStartAbsBounds(currentDisplayConfig.windowConfiguration.getBounds());
+                    change.setEndAbsBounds(mTmpConfiguration.windowConfiguration.getBounds());
+                }
+                requestChangeTransitionIfNeeded(changes, change);
             } else if (mLastHasContent) {
                 mWmService.startFreezingDisplay(0 /* exitAnim */, 0 /* enterAnim */, this);
             }
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 868a15d..a8c9cd3 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -334,7 +334,11 @@
             // remove caption insets from floating windows.
             // TODO(b/254128050): Remove this workaround after we find a way to update window frames
             //  and caption insets frames simultaneously.
-            state.removeSource(InsetsState.ITYPE_CAPTION_BAR);
+            for (int i = state.sourceSize() - 1; i >= 0; i--) {
+                if (state.sourceAt(i).getType() == Type.captionBar()) {
+                    state.removeSourceAt(i);
+                }
+            }
         }
 
         final SparseArray<WindowContainerInsetsSourceProvider> providers =
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index f314b21..7e267e4 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -30,6 +30,7 @@
 import android.app.ActivityManager;
 import android.app.IApplicationThread;
 import android.app.WindowConfiguration;
+import android.graphics.Rect;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.IRemoteCallback;
@@ -465,6 +466,28 @@
         return type == TRANSIT_OPEN || type == TRANSIT_CLOSE;
     }
 
+    /** Whether the display change should run with blast sync. */
+    private static boolean shouldSync(@NonNull TransitionRequestInfo.DisplayChange displayChange) {
+        if ((displayChange.getStartRotation() + displayChange.getEndRotation()) % 2 == 0) {
+            // 180 degrees rotation change may not change screen size. So the clients may draw
+            // some frames before and after the display projection transaction is applied by the
+            // remote player. That may cause some buffers to show in different rotation. So use
+            // sync method to pause clients drawing until the projection transaction is applied.
+            return true;
+        }
+        final Rect startBounds = displayChange.getStartAbsBounds();
+        final Rect endBounds = displayChange.getEndAbsBounds();
+        if (startBounds == null || endBounds == null) return false;
+        final int startWidth = startBounds.width();
+        final int startHeight = startBounds.height();
+        final int endWidth = endBounds.width();
+        final int endHeight = endBounds.height();
+        // This is changing screen resolution. Because the screen decor layers are excluded from
+        // screenshot, their draw transactions need to run with the start transaction.
+        return (endWidth > startWidth) == (endHeight > startHeight)
+                && (endWidth != startWidth || endHeight != startHeight);
+    }
+
     /**
      * If a transition isn't requested yet, creates one and asks the TransitionPlayer (Shell) to
      * start it. Collection can start immediately.
@@ -494,12 +517,7 @@
         } else {
             newTransition = requestStartTransition(createTransition(type, flags),
                     trigger != null ? trigger.asTask() : null, remoteTransition, displayChange);
-            if (newTransition != null && displayChange != null && (displayChange.getStartRotation()
-                    + displayChange.getEndRotation()) % 2 == 0) {
-                // 180 degrees rotation change may not change screen size. So the clients may draw
-                // some frames before and after the display projection transaction is applied by the
-                // remote player. That may cause some buffers to show in different rotation. So use
-                // sync method to pause clients drawing until the projection transaction is applied.
+            if (newTransition != null && displayChange != null && shouldSync(displayChange)) {
                 mAtm.mWindowManager.mSyncEngine.setSyncMethod(newTransition.getSyncId(),
                         BLASTSyncEngine.METHOD_BLAST);
             }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index d1bd06f..232b817 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2343,6 +2343,9 @@
         // to be removed before the parent (so that the sync-engine tracking works). Since
         // WindowStateAnimator is a "virtual" child, we have to do it manually here.
         mWinAnimator.destroySurfaceLocked(getSyncTransaction());
+        if (!mDrawHandlers.isEmpty()) {
+            mWmService.mH.removeMessages(WINDOW_STATE_BLAST_SYNC_TIMEOUT, this);
+        }
         super.removeImmediately();
 
         final DisplayContent dc = getDisplayContent();
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index a5b1943..075dcd5 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -137,7 +137,6 @@
     jmethodID notifyDropWindow;
     jmethodID getParentSurfaceForPointers;
     jmethodID isPerDisplayTouchModeEnabled;
-    jmethodID isStylusPointerIconEnabled;
 } gServiceClassInfo;
 
 static struct {
@@ -309,6 +308,7 @@
     std::optional<std::string> getBluetoothAddress(int32_t deviceId);
     void setStylusButtonMotionEventsEnabled(bool enabled);
     FloatPoint getMouseCursorPosition();
+    void setStylusPointerIconEnabled(bool enabled);
 
     /* --- InputReaderPolicyInterface implementation --- */
 
@@ -430,6 +430,9 @@
         // True to enable a zone on the right-hand side of touchpads where clicks will be turned
         // into context (a.k.a. "right") clicks.
         bool touchpadRightClickZoneEnabled{false};
+
+        // True if a pointer icon should be shown for stylus pointers.
+        bool stylusPointerIconEnabled{false};
     } mLocked GUARDED_BY(mLock);
 
     std::atomic<bool> mInteractive;
@@ -662,12 +665,6 @@
         outConfig->pointerGestureTapSlop = hoverTapSlop;
     }
 
-    jboolean stylusPointerIconEnabled =
-            env->CallBooleanMethod(mServiceObj, gServiceClassInfo.isStylusPointerIconEnabled);
-    if (!checkAndClearExceptionFromCallback(env, "isStylusPointerIconEnabled")) {
-        outConfig->stylusPointerIconEnabled = stylusPointerIconEnabled;
-    }
-
     { // acquire lock
         std::scoped_lock _l(mLock);
 
@@ -692,6 +689,8 @@
         outConfig->disabledDevices = mLocked.disabledInputDevices;
 
         outConfig->stylusButtonMotionEventsEnabled = mLocked.stylusButtonMotionEventsEnabled;
+
+        outConfig->stylusPointerIconEnabled = mLocked.stylusPointerIconEnabled;
     } // release lock
 }
 
@@ -1664,6 +1663,21 @@
     return pc->getPosition();
 }
 
+void NativeInputManager::setStylusPointerIconEnabled(bool enabled) {
+    { // acquire lock
+        std::scoped_lock _l(mLock);
+
+        if (mLocked.stylusPointerIconEnabled == enabled) {
+            return;
+        }
+
+        mLocked.stylusPointerIconEnabled = enabled;
+    } // release lock
+
+    mInputManager->getReader().requestRefreshConfiguration(
+            InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+}
+
 // ----------------------------------------------------------------------------
 
 static NativeInputManager* getNativeInputManager(JNIEnv* env, jobject clazz) {
@@ -2565,6 +2579,12 @@
     return outArr;
 }
 
+static void nativeSetStylusPointerIconEnabled(JNIEnv* env, jobject nativeImplObj,
+                                              jboolean enabled) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+    im->setStylusPointerIconEnabled(enabled);
+}
+
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod gInputManagerMethods[] = {
@@ -2659,6 +2679,7 @@
         {"setStylusButtonMotionEventsEnabled", "(Z)V",
          (void*)nativeSetStylusButtonMotionEventsEnabled},
         {"getMouseCursorPosition", "()[F", (void*)nativeGetMouseCursorPosition},
+        {"setStylusPointerIconEnabled", "(Z)V", (void*)nativeSetStylusPointerIconEnabled},
 };
 
 #define FIND_CLASS(var, className) \
@@ -2819,9 +2840,6 @@
     GET_METHOD_ID(gServiceClassInfo.isPerDisplayTouchModeEnabled, clazz,
                   "isPerDisplayTouchModeEnabled", "()Z");
 
-    GET_METHOD_ID(gServiceClassInfo.isStylusPointerIconEnabled, clazz, "isStylusPointerIconEnabled",
-                  "()Z");
-
     // InputDevice
 
     FIND_CLASS(gInputDeviceClassInfo.clazz, "android/view/InputDevice");
diff --git a/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java b/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java
index 8ccc61b..1164516 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java
@@ -25,19 +25,16 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
-import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.locks.ReentrantLock;
-import java.util.stream.Collectors;
 
 /** Contains information on what CredentialProvider has what provisioned Credential. */
 public class CredentialDescriptionRegistry {
 
-    private static final String FLAT_STRING_SPLIT_REGEX = ";";
     private static final int MAX_ALLOWED_CREDENTIAL_DESCRIPTIONS = 128;
     private static final int MAX_ALLOWED_ENTRIES_PER_PROVIDER = 16;
     @GuardedBy("sLock")
@@ -53,15 +50,15 @@
     /** Represents the results of a given query into the registry. */
     public static final class FilterResult {
         final String mPackageName;
-        final String mFlattenedRequest;
+        final Set<String> mElementKeys;
         final List<CredentialEntry> mCredentialEntries;
 
         @VisibleForTesting
         FilterResult(String packageName,
-                String flattenedRequest,
+                Set<String> elementKeys,
                 List<CredentialEntry> credentialEntries) {
             mPackageName = packageName;
-            mFlattenedRequest = flattenedRequest;
+            mElementKeys = elementKeys;
             mCredentialEntries = credentialEntries;
         }
     }
@@ -166,18 +163,17 @@
     /** Returns package names and entries of a CredentialProviders that can satisfy a given
      * {@link CredentialDescription}. */
     public Set<FilterResult> getFilteredResultForProvider(String packageName,
-            String flatRequestString) {
+            Set<String> requestedKeyElements) {
         Set<FilterResult> result = new HashSet<>();
         if (!mCredentialDescriptions.containsKey(packageName)) {
             return result;
         }
         Set<CredentialDescription> currentSet = mCredentialDescriptions.get(packageName);
-        Set<String> unflattenedRequestString = flatStringToSet(flatRequestString);
         for (CredentialDescription containedDescription: currentSet) {
-            if (checkForMatch(flatStringToSet(containedDescription.getFlattenedRequestString()),
-                    unflattenedRequestString)) {
+            if (checkForMatch(containedDescription.getSupportedElementKeys(),
+                    requestedKeyElements)) {
                 result.add(new FilterResult(packageName,
-                        containedDescription.getFlattenedRequestString(), containedDescription
+                        containedDescription.getSupportedElementKeys(), containedDescription
                         .getCredentialEntries()));
             }
         }
@@ -186,18 +182,15 @@
 
     /** Returns package names of CredentialProviders that can satisfy a given
      * {@link CredentialDescription}. */
-    public Set<FilterResult> getMatchingProviders(Set<String> flatRequestStrings) {
+    public Set<FilterResult> getMatchingProviders(Set<Set<String>> supportedElementKeys) {
         Set<FilterResult> result = new HashSet<>();
-        Set<Set<String>> unflattenedRequestStrings = flatRequestStrings.stream().map(
-                CredentialDescriptionRegistry::flatStringToSet).collect(Collectors.toSet());
         for (String packageName: mCredentialDescriptions.keySet()) {
             Set<CredentialDescription> currentSet = mCredentialDescriptions.get(packageName);
             for (CredentialDescription containedDescription : currentSet) {
-                if (canProviderSatisfyAny(flatStringToSet(containedDescription
-                                .getFlattenedRequestString()),
-                        unflattenedRequestStrings)) {
+                if (canProviderSatisfyAny(containedDescription.getSupportedElementKeys(),
+                        supportedElementKeys)) {
                     result.add(new FilterResult(packageName,
-                            containedDescription.getFlattenedRequestString(), containedDescription
+                            containedDescription.getSupportedElementKeys(), containedDescription
                             .getCredentialEntries()));
                 }
             }
@@ -211,24 +204,19 @@
         }
     }
 
-    private static boolean canProviderSatisfyAny(Set<String> registeredUnflattenedStrings,
-            Set<Set<String>> requestedUnflattenedStrings) {
-        for (Set<String> requestedUnflattenedString : requestedUnflattenedStrings) {
-            if (registeredUnflattenedStrings.containsAll(requestedUnflattenedString)) {
+    private static boolean canProviderSatisfyAny(Set<String> registeredElementKeys,
+            Set<Set<String>> requestedElementKeys) {
+        for (Set<String> requestedUnflattenedString : requestedElementKeys) {
+            if (registeredElementKeys.containsAll(requestedUnflattenedString)) {
                 return true;
             }
         }
         return false;
     }
 
-    static boolean checkForMatch(Set<String> registeredUnflattenedStrings,
-            Set<String> requestedUnflattenedString) {
-        return registeredUnflattenedStrings.containsAll(requestedUnflattenedString);
-    }
-
-    static Set<String> flatStringToSet(String flatString) {
-        return new HashSet<>(Arrays
-                .asList(flatString.split(FLAT_STRING_SPLIT_REGEX)));
+    static boolean checkForMatch(Set<String> registeredElementKeys,
+            Set<String> requestedElementKeys) {
+        return registeredElementKeys.containsAll(requestedElementKeys);
     }
 
 }
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 90b92f4..06da76e5 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -339,13 +339,14 @@
                 CredentialDescriptionRegistry.forUser(UserHandle.getCallingUserId());
 
         // All requested credential descriptions based on the given request.
-        Set<String> requestedCredentialDescriptions =
+        Set<Set<String>> requestedCredentialDescriptions =
                 options.stream()
                         .map(
                                 getCredentialOption ->
-                                        getCredentialOption
+                                        new HashSet<>(getCredentialOption
                                                 .getCredentialRetrievalData()
-                                                .getString(CredentialOption.FLATTENED_REQUEST))
+                                                .getStringArrayList(
+                                                        CredentialOption.SUPPORTED_ELEMENT_KEYS)))
                         .collect(Collectors.toSet());
 
         // All requested credential descriptions based on the given request.
@@ -356,15 +357,13 @@
                 new HashSet<>();
 
         for (CredentialDescriptionRegistry.FilterResult filterResult : filterResults) {
-            Set<String> registeredUnflattenedStrings = CredentialDescriptionRegistry
-                    .flatStringToSet(filterResult.mFlattenedRequest);
             for (CredentialOption credentialOption : options) {
-                Set<String> requestedUnflattenedStrings = CredentialDescriptionRegistry
-                        .flatStringToSet(credentialOption
+                Set<String> requestedElementKeys = new HashSet<>(
+                        credentialOption
                                 .getCredentialRetrievalData()
-                                .getString(CredentialOption.FLATTENED_REQUEST));
-                if (CredentialDescriptionRegistry.checkForMatch(registeredUnflattenedStrings,
-                        requestedUnflattenedStrings)) {
+                                .getStringArrayList(CredentialOption.SUPPORTED_ELEMENT_KEYS));
+                if (CredentialDescriptionRegistry.checkForMatch(filterResult.mElementKeys,
+                        requestedElementKeys)) {
                     result.add(new Pair<>(credentialOption, filterResult));
                 }
             }
@@ -511,28 +510,20 @@
             if (isCredentialDescriptionApiEnabled()) {
                 List<CredentialOption> optionsThatRequireActiveCredentials =
                         request.getCredentialOptions().stream()
-                                .filter(
-                                        getCredentialOption ->
-                                                !TextUtils.isEmpty(
-                                                        getCredentialOption
-                                                                .getCredentialRetrievalData()
-                                                                .getString(
-                                                                        CredentialOption
-                                                                                .FLATTENED_REQUEST,
-                                                                        null)))
+                                .filter(credentialOption -> credentialOption
+                                        .getCredentialRetrievalData()
+                                        .getStringArrayList(
+                                                CredentialOption
+                                                        .SUPPORTED_ELEMENT_KEYS) != null)
                                 .toList();
 
                 List<CredentialOption> optionsThatDoNotRequireActiveCredentials =
                         request.getCredentialOptions().stream()
-                                .filter(
-                                        getCredentialOption ->
-                                                TextUtils.isEmpty(
-                                                        getCredentialOption
-                                                                .getCredentialRetrievalData()
-                                                                .getString(
-                                                                        CredentialOption
-                                                                                .FLATTENED_REQUEST,
-                                                                        null)))
+                                .filter(credentialOption -> credentialOption
+                                        .getCredentialRetrievalData()
+                                        .getStringArrayList(
+                                                CredentialOption
+                                                        .SUPPORTED_ELEMENT_KEYS) == null)
                                 .toList();
 
                 List<ProviderSession> sessionsWithoutRemoteService =
@@ -590,28 +581,20 @@
             if (isCredentialDescriptionApiEnabled()) {
                 List<CredentialOption> optionsThatRequireActiveCredentials =
                         request.getCredentialOptions().stream()
-                                .filter(
-                                        getCredentialOption ->
-                                                !TextUtils.isEmpty(
-                                                        getCredentialOption
-                                                                .getCredentialRetrievalData()
-                                                                .getString(
-                                                                        CredentialOption
-                                                                                .FLATTENED_REQUEST,
-                                                                        null)))
+                                .filter(credentialOption -> credentialOption
+                                                .getCredentialRetrievalData()
+                                                .getStringArrayList(
+                                                        CredentialOption
+                                                                .SUPPORTED_ELEMENT_KEYS) != null)
                                 .toList();
 
                 List<CredentialOption> optionsThatDoNotRequireActiveCredentials =
                         request.getCredentialOptions().stream()
-                                .filter(
-                                        getCredentialOption ->
-                                                TextUtils.isEmpty(
-                                                        getCredentialOption
-                                                                .getCredentialRetrievalData()
-                                                                .getString(
-                                                                        CredentialOption
-                                                                                .FLATTENED_REQUEST,
-                                                                        null)))
+                                .filter(credentialOption -> credentialOption
+                                        .getCredentialRetrievalData()
+                                        .getStringArrayList(
+                                                CredentialOption
+                                                        .SUPPORTED_ELEMENT_KEYS) == null)
                                 .toList();
 
                 List<ProviderSession> sessionsWithoutRemoteService =
diff --git a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
index 85c78445..8b14757b 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
@@ -38,6 +38,7 @@
 
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -103,7 +104,7 @@
     @NonNull
     private final String mCredentialProviderPackageName;
     @NonNull
-    private final String mFlattenedRequestOptionString;
+    private final Set<String> mElementKeys;
     @VisibleForTesting
     List<CredentialEntry> mCredentialEntries;
 
@@ -119,9 +120,9 @@
         mCredentialDescriptionRegistry = CredentialDescriptionRegistry.forUser(userId);
         mCallingAppInfo = callingAppInfo;
         mCredentialProviderPackageName = servicePackageName;
-        mFlattenedRequestOptionString = requestOption
+        mElementKeys = new HashSet<>(requestOption
                 .getCredentialRetrievalData()
-                .getString(CredentialOption.FLATTENED_REQUEST);
+                .getStringArrayList(CredentialOption.SUPPORTED_ELEMENT_KEYS));
     }
 
     protected ProviderRegistryGetSession(@NonNull Context context,
@@ -136,9 +137,9 @@
         mCredentialDescriptionRegistry = CredentialDescriptionRegistry.forUser(userId);
         mCallingAppInfo = callingAppInfo;
         mCredentialProviderPackageName = servicePackageName;
-        mFlattenedRequestOptionString = requestOption
+        mElementKeys = new HashSet<>(requestOption
                 .getCredentialRetrievalData()
-                .getString(CredentialOption.FLATTENED_REQUEST);
+                .getStringArrayList(CredentialOption.SUPPORTED_ELEMENT_KEYS));
     }
 
     private List<Entry> prepareUiCredentialEntries(
@@ -257,7 +258,7 @@
     protected void invokeSession() {
         mProviderResponse = mCredentialDescriptionRegistry
                 .getFilteredResultForProvider(mCredentialProviderPackageName,
-                        mFlattenedRequestOptionString);
+                        mElementKeys);
         mCredentialEntries = mProviderResponse.stream().flatMap(
                         (Function<CredentialDescriptionRegistry.FilterResult,
                                 Stream<CredentialEntry>>) filterResult
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
index a1b9b98..2a256f2 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
@@ -80,7 +80,7 @@
 
         final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken);
         assertThat(state).isNotNull();
-        assertThat(state.hasEdiorFocused()).isTrue();
+        assertThat(state.hasEditorFocused()).isTrue();
         assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED);
         assertThat(state.isRequestedImeVisible()).isTrue();
 
@@ -95,7 +95,7 @@
 
         final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken);
         assertThat(state).isNotNull();
-        assertThat(state.hasEdiorFocused()).isTrue();
+        assertThat(state.hasEditorFocused()).isTrue();
         assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED);
         assertThat(state.isRequestedImeVisible()).isTrue();
 
@@ -113,7 +113,7 @@
 
         final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken);
         assertThat(state).isNotNull();
-        assertThat(state.hasEdiorFocused()).isTrue();
+        assertThat(state.hasEditorFocused()).isTrue();
         assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED);
         assertThat(state.isRequestedImeVisible()).isFalse();
 
@@ -131,7 +131,7 @@
 
         final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken);
         assertThat(state).isNotNull();
-        assertThat(state.hasEdiorFocused()).isTrue();
+        assertThat(state.hasEditorFocused()).isTrue();
         assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED);
         assertThat(state.isRequestedImeVisible()).isFalse();
 
@@ -149,7 +149,7 @@
 
         final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken);
         assertThat(state).isNotNull();
-        assertThat(state.hasEdiorFocused()).isTrue();
+        assertThat(state.hasEditorFocused()).isTrue();
         assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED);
         assertThat(state.isRequestedImeVisible()).isFalse();
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
index 0ab984b..fc503b7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -66,7 +66,6 @@
 import com.android.server.LocalServices;
 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.color.ColorDisplayService;
 import com.android.server.display.layout.Layout;
 import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
@@ -565,8 +564,8 @@
                 .thenReturn(brightness);
         dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
         when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
-                any(BrightnessEvent.class))).thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness())
+                .thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
 
         mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
         advanceTime(1); // Run updatePowerState
@@ -603,8 +602,8 @@
                 .thenReturn(brightness);
         dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
         when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
-                any(BrightnessEvent.class))).thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness())
+                .thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
 
         mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
         advanceTime(1); // Run updatePowerState
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/LocationManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/location/LocationManagerServiceTest.java
index 4d11296..a1937ce 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/LocationManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/LocationManagerServiceTest.java
@@ -53,6 +53,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -69,6 +70,7 @@
     private static final int CURRENT_USER = FakeUserInfoHelper.DEFAULT_USERID;
     private static final String CALLER_PACKAGE = "caller_package";
     private static final String MISSING_PERMISSION = "missing_permission";
+    private static final String ATTRIBUTION_TAG = "test_tag";
 
     private TestInjector mInjector;
     private LocationManagerService mLocationManagerService;
@@ -136,6 +138,7 @@
     }
 
     @Test
+    @Ignore("b/274432939") // Test is flaky for as of yet unknown reasons
     public void testRequestLocationUpdates() {
         LocationRequest request = new LocationRequest.Builder(0).build();
         mLocationManagerService.registerLocationListener(
@@ -143,7 +146,7 @@
                 request,
                 mLocationListener,
                 CALLER_PACKAGE,
-                /* attributionTag= */ null,
+                ATTRIBUTION_TAG,
                 "any_listener_id");
         verify(mProviderWithPermission).onSetRequestPublic(any());
     }
@@ -159,7 +162,7 @@
                                 request,
                                 mLocationListener,
                                 CALLER_PACKAGE,
-                                /* attributionTag= */ null,
+                                ATTRIBUTION_TAG,
                                 "any_listener_id"));
     }
 
diff --git a/services/tests/servicestests/res/xml/keyboard_layouts.xml b/services/tests/servicestests/res/xml/keyboard_layouts.xml
index b5a05fc..5f3fcd6 100644
--- a/services/tests/servicestests/res/xml/keyboard_layouts.xml
+++ b/services/tests/servicestests/res/xml/keyboard_layouts.xml
@@ -73,9 +73,17 @@
         android:keyboardLocale="ru-Cyrl" />
 
     <keyboard-layout
+        android:name="keyboard_layout_english_without_script_code"
+        android:label="English(No script code)"
+        android:keyboardLayout="@raw/dummy_keyboard_layout"
+        android:keyboardLocale="en"
+        android:keyboardLayoutType="qwerty" />
+
+    <keyboard-layout
         android:name="keyboard_layout_vendorId:1,productId:1"
         android:label="vendorId:1,productId:1"
         android:keyboardLayout="@raw/dummy_keyboard_layout"
         androidprv:vendorId="1"
         androidprv:productId="1" />
+
 </keyboard-layouts>
diff --git a/services/tests/servicestests/src/com/android/server/credentials/CredentialDescriptionRegistryTest.java b/services/tests/servicestests/src/com/android/server/credentials/CredentialDescriptionRegistryTest.java
index 169210f..ab2749e 100644
--- a/services/tests/servicestests/src/com/android/server/credentials/CredentialDescriptionRegistryTest.java
+++ b/services/tests/servicestests/src/com/android/server/credentials/CredentialDescriptionRegistryTest.java
@@ -33,6 +33,7 @@
 import org.junit.runner.RunWith;
 
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.stream.Collectors;
@@ -52,10 +53,12 @@
     private static final String CALLING_PACKAGE_NAME_2 = "com.credman.app2";
     private static final String MDOC_CREDENTIAL_TYPE = "MDOC";
     private static final String PASSKEY_CREDENTIAL_TYPE = "PASSKEY";
-    private static final String FLATTENED_REGISTRY =
-            "FLATTENED_REQ;FLATTENED_REQ123;FLATTENED_REQa";
-    private static final String FLATTENED_REGISTRY_2 = "FLATTENED_REQ_2";
-    private static final String FLATTENED_REQUEST = "FLATTENED_REQ;FLATTENED_REQ123";
+    private static final HashSet<String> FLATTENED_REGISTRY = new HashSet<>(List.of(
+            "FLATTENED_REQ", "FLATTENED_REQ123", "FLATTENED_REQa"));
+    private static final HashSet<String> FLATTENED_REGISTRY_2 =
+            new HashSet<>(List.of("FLATTENED_REQ_2"));
+    private static final HashSet<String> FLATTENED_REQUEST =
+            new HashSet<>(List.of("FLATTENED_REQ;FLATTENED_REQ123"));
 
     private CredentialDescriptionRegistry mCredentialDescriptionRegistry;
     private CredentialEntry mEntry;
diff --git a/services/tests/servicestests/src/com/android/server/credentials/ProviderRegistryGetSessionTest.java b/services/tests/servicestests/src/com/android/server/credentials/ProviderRegistryGetSessionTest.java
index 4c8e70a..13d49aa 100644
--- a/services/tests/servicestests/src/com/android/server/credentials/ProviderRegistryGetSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/credentials/ProviderRegistryGetSessionTest.java
@@ -18,6 +18,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.anySet;
 import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
@@ -59,6 +60,7 @@
 import org.mockito.MockitoAnnotations;
 
 import java.security.cert.CertificateException;
+import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -74,7 +76,8 @@
 
     private static final String CALLING_PACKAGE_NAME = "com.credman.app";
     private static final int USER_ID_1 = 1;
-    private static final String FLATTENED_REQUEST = "FLATTENED_REQ";
+    private static final ArrayList<String> FLATTENED_REQUEST =
+            new ArrayList<>(List.of("FLATTENED_REQ"));
     private static final String CP_SERVICE_NAME = "CredentialProvider";
     private static final ComponentName CREDENTIAL_PROVIDER_COMPONENT =
             new ComponentName(CALLING_PACKAGE_NAME, CP_SERVICE_NAME);
@@ -102,7 +105,8 @@
         MockitoAnnotations.initMocks(this);
         final Context context = ApplicationProvider.getApplicationContext();
         mRetrievalData = new Bundle();
-        mRetrievalData.putString(CredentialOption.FLATTENED_REQUEST, FLATTENED_REQUEST);
+        mRetrievalData.putStringArrayList(CredentialOption.SUPPORTED_ELEMENT_KEYS,
+                FLATTENED_REQUEST);
         mCallingAppInfo = createCallingAppInfo();
         mGetCredentialOption = new CredentialOption(CREDENTIAL_TYPE, mRetrievalData,
                 new Bundle(), false);
@@ -114,10 +118,10 @@
         when(mEntry.getSlice()).thenReturn(mSlice);
         when(mEntry2.getSlice()).thenReturn(mSlice2);
         mResult = new CredentialDescriptionRegistry.FilterResult(CALLING_PACKAGE_NAME,
-                FLATTENED_REQUEST,
+                new HashSet<>(FLATTENED_REQUEST),
                 List.of(mEntry, mEntry2));
         mResponse.add(mResult);
-        when(mCredentialDescriptionRegistry.getFilteredResultForProvider(anyString(), anyString()))
+        when(mCredentialDescriptionRegistry.getFilteredResultForProvider(anyString(), anySet()))
                 .thenReturn(mResponse);
         mProviderRegistryGetSession = ProviderRegistryGetSession
                 .createNewSession(context, USER_ID_1, mGetRequestSession,
@@ -129,7 +133,8 @@
     @Test
     public void testInvokeSession_existingProvider_setsResults() {
         final ArgumentCaptor<String> packageNameCaptor = ArgumentCaptor.forClass(String.class);
-        final ArgumentCaptor<String> flattenedRequestCaptor = ArgumentCaptor.forClass(String.class);
+        final ArgumentCaptor<Set<String>> flattenedRequestCaptor =
+                ArgumentCaptor.forClass(Set.class);
         final ArgumentCaptor<ProviderSession.Status> statusCaptor =
                 ArgumentCaptor.forClass(ProviderSession.Status.class);
         final ArgumentCaptor<ComponentName> cpComponentNameCaptor =
@@ -141,7 +146,7 @@
                 packageNameCaptor.capture(),
                 flattenedRequestCaptor.capture());
         assertThat(packageNameCaptor.getValue()).isEqualTo(CALLING_PACKAGE_NAME);
-        assertThat(flattenedRequestCaptor.getValue()).isEqualTo(FLATTENED_REQUEST);
+        assertThat(flattenedRequestCaptor.getValue()).containsExactly(FLATTENED_REQUEST);
         verify(mGetRequestSession).onProviderStatusChanged(statusCaptor.capture(),
                 cpComponentNameCaptor.capture());
         assertThat(statusCaptor.getValue()).isEqualTo(ProviderSession.Status.CREDENTIALS_RECEIVED);
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
new file mode 100644
index 0000000..eb208d2
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
@@ -0,0 +1,329 @@
+/*
+ * 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.
+ */
+package com.android.server.display.brightness.strategy;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.hardware.display.BrightnessConfiguration;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.PowerManager;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.test.mock.MockContentResolver;
+import android.view.Display;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
+import com.android.server.display.AutomaticBrightnessController;
+import com.android.server.display.brightness.BrightnessReason;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class AutomaticBrightnessStrategyTest {
+    private static final int DISPLAY_ID = 0;
+    @Rule
+    public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
+
+    @Mock
+    private AutomaticBrightnessController mAutomaticBrightnessController;
+
+    private BrightnessConfiguration mBrightnessConfiguration;
+    private float mDefaultScreenAutoBrightnessAdjustment;
+    private Context mContext;
+    private AutomaticBrightnessStrategy mAutomaticBrightnessStrategy;
+
+    @Before
+    public void before() {
+        MockitoAnnotations.initMocks(this);
+        mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
+        final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContext);
+        when(mContext.getContentResolver()).thenReturn(resolver);
+        mDefaultScreenAutoBrightnessAdjustment = Settings.System.getFloat(
+                mContext.getContentResolver(),
+                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, Float.NaN);
+        Settings.System.putFloat(mContext.getContentResolver(),
+                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.5f);
+        mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(mContext, DISPLAY_ID);
+
+        mBrightnessConfiguration = new BrightnessConfiguration.Builder(
+                new float[]{0f, 1f}, new float[]{0, PowerManager.BRIGHTNESS_ON}).build();
+        when(mAutomaticBrightnessController.hasUserDataPoints()).thenReturn(true);
+        mAutomaticBrightnessStrategy.setAutomaticBrightnessController(
+                mAutomaticBrightnessController);
+        mAutomaticBrightnessStrategy.setBrightnessConfiguration(mBrightnessConfiguration,
+                true);
+    }
+
+    @After
+    public void after() {
+        Settings.System.putFloat(mContext.getContentResolver(),
+                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, mDefaultScreenAutoBrightnessAdjustment);
+    }
+
+    @Test
+    public void setAutoBrightnessWhenDisabled() {
+        mAutomaticBrightnessStrategy.setUseAutoBrightness(false);
+        int targetDisplayState = Display.STATE_ON;
+        boolean allowAutoBrightnessWhileDozing = false;
+        float brightnessState = Float.NaN;
+        int brightnessReason = BrightnessReason.REASON_OVERRIDE;
+        int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+        float lastUserSetBrightness = 0.2f;
+        boolean userSetBrightnessChanged = true;
+        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(true);
+        mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
+                allowAutoBrightnessWhileDozing, brightnessState, brightnessReason, policy,
+                lastUserSetBrightness, userSetBrightnessChanged);
+        verify(mAutomaticBrightnessController)
+                .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
+                        mBrightnessConfiguration,
+                        lastUserSetBrightness,
+                        userSetBrightnessChanged, 0.5f,
+                        false, policy, true);
+    }
+
+    @Test
+    public void setAutoBrightnessWhenEnabledAndDisplayIsDozing() {
+        mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
+        int targetDisplayState = Display.STATE_DOZE;
+        float brightnessState = Float.NaN;
+        boolean allowAutoBrightnessWhileDozing = true;
+        int brightnessReason = BrightnessReason.REASON_DOZE;
+        int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+        float lastUserSetBrightness = 0.2f;
+        boolean userSetBrightnessChanged = true;
+        Settings.System.putFloat(mContext.getContentResolver(),
+                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.4f);
+        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(false);
+        mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
+                allowAutoBrightnessWhileDozing, brightnessState, brightnessReason, policy,
+                lastUserSetBrightness, userSetBrightnessChanged);
+        verify(mAutomaticBrightnessController)
+                .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED,
+                        mBrightnessConfiguration,
+                        lastUserSetBrightness,
+                        userSetBrightnessChanged, 0.4f,
+                        true, policy, true);
+    }
+
+    @Test
+    public void setAutoBrightnessWhenEnabledAndDisplayIsOn() {
+        mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
+        int targetDisplayState = Display.STATE_ON;
+        float brightnessState = Float.NaN;
+        boolean allowAutoBrightnessWhileDozing = false;
+        int brightnessReason = BrightnessReason.REASON_OVERRIDE;
+        float lastUserSetBrightness = 0.2f;
+        boolean userSetBrightnessChanged = true;
+        int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+        float pendingBrightnessAdjustment = 0.1f;
+        Settings.System.putFloat(mContext.getContentResolver(),
+                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, pendingBrightnessAdjustment);
+        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(false);
+        mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
+                allowAutoBrightnessWhileDozing, brightnessState, brightnessReason, policy,
+                lastUserSetBrightness, userSetBrightnessChanged);
+        verify(mAutomaticBrightnessController)
+                .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED,
+                        mBrightnessConfiguration,
+                        lastUserSetBrightness,
+                        userSetBrightnessChanged, pendingBrightnessAdjustment,
+                        true, policy, true);
+    }
+
+    @Test
+    public void accommodateUserBrightnessChangesWorksAsExpected() {
+        // Verify the state if automaticBrightnessController is configured.
+        assertFalse(mAutomaticBrightnessStrategy.isShortTermModelActive());
+        boolean userSetBrightnessChanged = true;
+        float lastUserSetScreenBrightness = 0.2f;
+        int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+        BrightnessConfiguration brightnessConfiguration = new BrightnessConfiguration.Builder(
+                new float[]{0f, 1f}, new float[]{0, PowerManager.BRIGHTNESS_ON}).build();
+        int autoBrightnessState = AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
+        float temporaryAutoBrightnessAdjustments = 0.4f;
+        mAutomaticBrightnessStrategy.setShouldResetShortTermModel(true);
+        setTemporaryAutoBrightnessAdjustment(temporaryAutoBrightnessAdjustments);
+        mAutomaticBrightnessStrategy.accommodateUserBrightnessChanges(userSetBrightnessChanged,
+                lastUserSetScreenBrightness, policy, brightnessConfiguration,
+                autoBrightnessState);
+        verify(mAutomaticBrightnessController).configure(autoBrightnessState,
+                brightnessConfiguration,
+                lastUserSetScreenBrightness,
+                userSetBrightnessChanged, temporaryAutoBrightnessAdjustments,
+                /* userChangedAutoBrightnessAdjustment= */ false, policy,
+                /* shouldResetShortTermModel= */ true);
+        assertTrue(mAutomaticBrightnessStrategy.isTemporaryAutoBrightnessAdjustmentApplied());
+        assertFalse(mAutomaticBrightnessStrategy.shouldResetShortTermModel());
+        assertTrue(mAutomaticBrightnessStrategy.isShortTermModelActive());
+        // Verify the state when automaticBrightnessController is not configured
+        setTemporaryAutoBrightnessAdjustment(Float.NaN);
+        mAutomaticBrightnessStrategy.setAutomaticBrightnessController(null);
+        mAutomaticBrightnessStrategy.setShouldResetShortTermModel(true);
+        mAutomaticBrightnessStrategy.accommodateUserBrightnessChanges(userSetBrightnessChanged,
+                lastUserSetScreenBrightness, policy, brightnessConfiguration,
+                autoBrightnessState);
+        assertFalse(mAutomaticBrightnessStrategy.isTemporaryAutoBrightnessAdjustmentApplied());
+        assertTrue(mAutomaticBrightnessStrategy.shouldResetShortTermModel());
+        assertFalse(mAutomaticBrightnessStrategy.isShortTermModelActive());
+    }
+
+    @Test
+    public void adjustAutomaticBrightnessStateIfValid() throws Settings.SettingNotFoundException {
+        float brightnessState = 0.3f;
+        float autoBrightnessAdjustment = 0.2f;
+        when(mAutomaticBrightnessController.getAutomaticScreenBrightnessAdjustment()).thenReturn(
+                autoBrightnessAdjustment);
+        mAutomaticBrightnessStrategy.adjustAutomaticBrightnessStateIfValid(brightnessState);
+        assertTrue(mAutomaticBrightnessStrategy.hasAppliedAutoBrightness());
+        assertEquals(autoBrightnessAdjustment,
+                mAutomaticBrightnessStrategy.getAutoBrightnessAdjustment(), 0.0f);
+        assertEquals(autoBrightnessAdjustment, Settings.System.getFloatForUser(
+                mContext.getContentResolver(),
+                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ,
+                UserHandle.USER_CURRENT), 0.0f);
+        assertEquals(BrightnessReason.ADJUSTMENT_AUTO,
+                mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentReasonsFlags());
+        float invalidBrightness = -0.5f;
+        mAutomaticBrightnessStrategy
+                .adjustAutomaticBrightnessStateIfValid(invalidBrightness);
+        assertFalse(mAutomaticBrightnessStrategy.hasAppliedAutoBrightness());
+        assertEquals(autoBrightnessAdjustment,
+                mAutomaticBrightnessStrategy.getAutoBrightnessAdjustment(), 0.0f);
+        assertEquals(0,
+                mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentReasonsFlags());
+    }
+
+    @Test
+    public void updatePendingAutoBrightnessAdjustments() {
+        // Verify the state when the pendingAutoBrightnessAdjustments are not present
+        setPendingAutoBrightnessAdjustment(Float.NaN);
+        assertFalse(mAutomaticBrightnessStrategy.processPendingAutoBrightnessAdjustments());
+        assertFalse(mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged());
+        // Verify the state when the pendingAutoBrightnessAdjustments are present, but
+        // pendingAutoBrightnessAdjustments and autoBrightnessAdjustments are the same
+        float autoBrightnessAdjustment = 0.3f;
+        setPendingAutoBrightnessAdjustment(autoBrightnessAdjustment);
+        setAutoBrightnessAdjustment(autoBrightnessAdjustment);
+        assertFalse(mAutomaticBrightnessStrategy.processPendingAutoBrightnessAdjustments());
+        assertFalse(mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged());
+        assertEquals(Float.NaN, mAutomaticBrightnessStrategy.getPendingAutoBrightnessAdjustment(),
+                0.0f);
+        // Verify the state when the pendingAutoBrightnessAdjustments are present, and
+        // pendingAutoBrightnessAdjustments and autoBrightnessAdjustments are not the same
+        float pendingAutoBrightnessAdjustment = 0.2f;
+        setPendingAutoBrightnessAdjustment(pendingAutoBrightnessAdjustment);
+        setTemporaryAutoBrightnessAdjustment(0.1f);
+        assertTrue(mAutomaticBrightnessStrategy.processPendingAutoBrightnessAdjustments());
+        assertTrue(mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged());
+        assertEquals(pendingAutoBrightnessAdjustment,
+                mAutomaticBrightnessStrategy.getAutoBrightnessAdjustment(), 0.0f);
+        assertEquals(Float.NaN, mAutomaticBrightnessStrategy.getPendingAutoBrightnessAdjustment(),
+                0.0f);
+        assertEquals(Float.NaN, mAutomaticBrightnessStrategy.getTemporaryAutoBrightnessAdjustment(),
+                0.0f);
+    }
+
+    @Test
+    public void setAutomaticBrightnessWorksAsExpected() {
+        float automaticScreenBrightness = 0.3f;
+        AutomaticBrightnessController automaticBrightnessController = mock(
+                AutomaticBrightnessController.class);
+        when(automaticBrightnessController.getAutomaticScreenBrightness()).thenReturn(
+                automaticScreenBrightness);
+        mAutomaticBrightnessStrategy.setAutomaticBrightnessController(
+                automaticBrightnessController);
+        assertEquals(automaticScreenBrightness,
+                mAutomaticBrightnessStrategy.getAutomaticScreenBrightness(), 0.0f);
+    }
+
+    @Test
+    public void shouldUseAutoBrightness() {
+        mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
+        assertTrue(mAutomaticBrightnessStrategy.shouldUseAutoBrightness());
+    }
+
+    @Test
+    public void setPendingAutoBrightnessAdjustments() throws Settings.SettingNotFoundException {
+        float pendingAutoBrightnessAdjustments = 0.3f;
+        setPendingAutoBrightnessAdjustment(pendingAutoBrightnessAdjustments);
+        assertEquals(pendingAutoBrightnessAdjustments,
+                mAutomaticBrightnessStrategy.getPendingAutoBrightnessAdjustment(), 0.0f);
+        assertEquals(pendingAutoBrightnessAdjustments, Settings.System.getFloatForUser(
+                mContext.getContentResolver(),
+                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ,
+                UserHandle.USER_CURRENT), 0.0f);
+    }
+
+    @Test
+    public void setTemporaryAutoBrightnessAdjustment() {
+        float temporaryAutoBrightnessAdjustment = 0.3f;
+        mAutomaticBrightnessStrategy.setTemporaryAutoBrightnessAdjustment(
+                temporaryAutoBrightnessAdjustment);
+        assertEquals(temporaryAutoBrightnessAdjustment,
+                mAutomaticBrightnessStrategy.getTemporaryAutoBrightnessAdjustment(), 0.0f);
+    }
+
+    @Test
+    public void setAutoBrightnessApplied() {
+        mAutomaticBrightnessStrategy.setAutoBrightnessApplied(true);
+        assertTrue(mAutomaticBrightnessStrategy.hasAppliedAutoBrightness());
+    }
+
+    @Test
+    public void testVerifyNoAutoBrightnessAdjustmentsArePopulatedForNonDefaultDisplay() {
+        int newDisplayId = 1;
+        mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(mContext, newDisplayId);
+        mAutomaticBrightnessStrategy.putAutoBrightnessAdjustmentSetting(0.3f);
+        assertEquals(0.5f, mAutomaticBrightnessStrategy.getAutoBrightnessAdjustment(),
+                0.0f);
+    }
+
+    private void setPendingAutoBrightnessAdjustment(float pendingAutoBrightnessAdjustment) {
+        Settings.System.putFloat(mContext.getContentResolver(),
+                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, pendingAutoBrightnessAdjustment);
+        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(false);
+    }
+
+    private void setTemporaryAutoBrightnessAdjustment(float temporaryAutoBrightnessAdjustment) {
+        mAutomaticBrightnessStrategy.setTemporaryAutoBrightnessAdjustment(
+                temporaryAutoBrightnessAdjustment);
+    }
+
+    private void setAutoBrightnessAdjustment(float autoBrightnessAdjustment) {
+        mAutomaticBrightnessStrategy.putAutoBrightnessAdjustmentSetting(autoBrightnessAdjustment);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt b/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt
index b660926..7729fa2 100644
--- a/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt
@@ -26,7 +26,6 @@
 import android.hardware.input.IInputManager
 import android.hardware.input.InputManager
 import android.hardware.input.KeyboardLayout
-import android.icu.lang.UScript
 import android.icu.util.ULocale
 import android.os.Bundle
 import android.os.test.TestLooper
@@ -52,7 +51,6 @@
 import java.io.FileOutputStream
 import java.io.IOException
 import java.io.InputStream
-import java.util.Locale
 
 private fun createKeyboard(
     deviceId: Int,
@@ -553,24 +551,17 @@
                 0,
                 keyboardLayouts.size
             )
-
-            val englishScripts = UScript.getCode(Locale.forLanguageTag("hi-Latn"))
-            for (kl in keyboardLayouts) {
-                var isCompatible = false
-                for (i in 0 until kl.locales.size()) {
-                    val locale: Locale = kl.locales.get(i) ?: continue
-                    val scripts = UScript.getCode(locale)
-                    if (scripts != null && areScriptsCompatible(scripts, englishScripts)) {
-                        isCompatible = true
-                        break
-                    }
-                }
-                assertTrue(
-                    "New UI: getKeyboardLayoutListForInputDevice API should only return " +
-                            "compatible layouts but found " + kl.descriptor,
-                    isCompatible
+            assertTrue("New UI: getKeyboardLayoutListForInputDevice API should return a list " +
+                    "containing English(US) layout for hi-Latn",
+                containsLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
+            )
+            assertTrue("New UI: getKeyboardLayoutListForInputDevice API should return a list " +
+                    "containing English(No script code) layout for hi-Latn",
+                containsLayout(
+                    keyboardLayouts,
+                    createLayoutDescriptor("keyboard_layout_english_without_script_code")
                 )
-            }
+            )
 
             // Check Layouts for "hi" which by default uses 'Deva' script.
             keyboardLayouts =
@@ -600,6 +591,46 @@
                     1,
                 keyboardLayouts.size
             )
+
+            // Special case Japanese: UScript ignores provided script code for certain language tags
+            // Should manually match provided script codes and then rely on Uscript to derive
+            // script from language tags and match those.
+            keyboardLayouts =
+                keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
+                        keyboardDevice.identifier, USER_ID, imeInfo,
+                        createImeSubtypeForLanguageTag("ja-Latn-JP")
+                )
+            assertNotEquals(
+                "New UI: getKeyboardLayoutListForInputDevice API should return the list of " +
+                        "supported layouts with matching script code for ja-Latn-JP",
+                0,
+                keyboardLayouts.size
+            )
+            assertTrue("New UI: getKeyboardLayoutListForInputDevice API should return a list " +
+                    "containing English(US) layout for ja-Latn-JP",
+                containsLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
+            )
+            assertTrue("New UI: getKeyboardLayoutListForInputDevice API should return a list " +
+                    "containing English(No script code) layout for ja-Latn-JP",
+                containsLayout(
+                    keyboardLayouts,
+                    createLayoutDescriptor("keyboard_layout_english_without_script_code")
+                )
+            )
+
+            // If script code not explicitly provided for Japanese should rely on Uscript to find
+            // derived script code and hence no suitable layout will be found.
+            keyboardLayouts =
+                keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
+                        keyboardDevice.identifier, USER_ID, imeInfo,
+                        createImeSubtypeForLanguageTag("ja-JP")
+                )
+            assertEquals(
+                "New UI: getKeyboardLayoutListForInputDevice API should return empty list of " +
+                        "supported layouts with matching script code for ja-JP",
+                0,
+                keyboardLayouts.size
+            )
         }
     }
 
@@ -779,10 +810,10 @@
     private fun createLayoutDescriptor(keyboardName: String): String =
         "$PACKAGE_NAME/$RECEIVER_NAME/$keyboardName"
 
-    private fun areScriptsCompatible(scriptList1: IntArray, scriptList2: IntArray): Boolean {
-        for (s1 in scriptList1) {
-            for (s2 in scriptList2) {
-                if (s1 == s2) return true
+    private fun containsLayout(layoutList: Array<KeyboardLayout>, layoutDesc: String): Boolean {
+        for (kl in layoutList) {
+            if (kl.descriptor.equals(layoutDesc)) {
+                return true
             }
         }
         return false
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 be5a6d5..9ca8d84 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -74,6 +74,7 @@
 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL;
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
 
 import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.ALLOW_DISMISS_ONGOING;
@@ -82,6 +83,7 @@
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
@@ -120,6 +122,7 @@
 
 import android.Manifest;
 import android.annotation.SuppressLint;
+import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.AlarmManager;
@@ -277,12 +280,16 @@
 @RunWithLooper
 public class NotificationManagerServiceTest extends UiServiceTestCase {
     private static final String TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId";
+    private static final String TEST_PACKAGE = "The.name.is.Package.Test.Package";
     private static final String PKG_NO_CHANNELS = "com.example.no.channels";
     private static final int TEST_TASK_ID = 1;
     private static final int UID_HEADLESS = 1_000_000;
     private static final int TOAST_DURATION = 2_000;
+    private static final int SECONDARY_DISPLAY_ID = 42;
 
     private final int mUid = Binder.getCallingUid();
+    private final @UserIdInt int mUserId = UserHandle.getUserId(mUid);
+
     private TestableNotificationManagerService mService;
     private INotificationManager mBinderService;
     private NotificationManagerInternal mInternalService;
@@ -513,7 +520,7 @@
         when(mListeners.getNotificationListenerFilter(any())).thenReturn(mNlf);
         mListener = mListeners.new ManagedServiceInfo(
                 null, new ComponentName(PKG, "test_class"),
-                UserHandle.getUserId(mUid), true, null, 0, 123);
+                mUserId, true, null, 0, 123);
         ComponentName defaultComponent = ComponentName.unflattenFromString("config/device");
         ArraySet<ComponentName> components = new ArraySet<>();
         components.add(defaultComponent);
@@ -604,6 +611,7 @@
         when(mShortcutServiceInternal.isSharingShortcut(anyInt(), anyString(), anyString(),
                 anyString(), anyInt(), any())).thenReturn(true);
         when(mUserManager.isUserUnlocked(any(UserHandle.class))).thenReturn(true);
+        mockIsVisibleBackgroundUsersSupported(false);
 
         // Set the testable bubble extractor
         RankingHelper rankingHelper = mService.getRankingHelper();
@@ -1039,7 +1047,7 @@
                 new ParceledListSlice(Arrays.asList(channel)));
         verify(mWorkerHandler).post(eq(new NotificationManagerService
                 .ShowNotificationPermissionPromptRunnable(PKG_NO_CHANNELS,
-                UserHandle.getUserId(mUid), TEST_TASK_ID, mPermissionPolicyInternal)));
+                mUserId, TEST_TASK_ID, mPermissionPolicyInternal)));
     }
 
     @Test
@@ -1406,7 +1414,7 @@
         mBinderService.setNotificationsEnabledForPackage(mContext.getPackageName(), mUid, false);
 
         verify(mPermissionHelper).setNotificationPermission(
-                mContext.getPackageName(), UserHandle.getUserId(mUid), false, true);
+                mContext.getPackageName(), mUserId, false, true);
 
         verify(mAppOpsManager, never()).setMode(anyInt(), anyInt(), anyString(), anyInt());
         List<NotificationChannelLoggerFake.CallRecord> calls = mLogger.getCalls();
@@ -3123,7 +3131,7 @@
 
     @Test
     public void testCreateChannelNotifyListener() throws Exception {
-        when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+        when(mCompanionMgr.getAssociations(PKG, mUserId))
                 .thenReturn(singletonList(mock(AssociationInfo.class)));
         mService.setPreferencesHelper(mPreferencesHelper);
         when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
@@ -3150,7 +3158,7 @@
 
     @Test
     public void testCreateChannelGroupNotifyListener() throws Exception {
-        when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+        when(mCompanionMgr.getAssociations(PKG, mUserId))
                 .thenReturn(singletonList(mock(AssociationInfo.class)));
         mService.setPreferencesHelper(mPreferencesHelper);
         NotificationChannelGroup group1 = new NotificationChannelGroup("a", "b");
@@ -3169,7 +3177,7 @@
 
     @Test
     public void testUpdateChannelNotifyListener() throws Exception {
-        when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+        when(mCompanionMgr.getAssociations(PKG, mUserId))
                 .thenReturn(singletonList(mock(AssociationInfo.class)));
         mService.setPreferencesHelper(mPreferencesHelper);
         mTestNotificationChannel.setLightColor(Color.CYAN);
@@ -3186,7 +3194,7 @@
 
     @Test
     public void testDeleteChannelNotifyListener() throws Exception {
-        when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+        when(mCompanionMgr.getAssociations(PKG, mUserId))
                 .thenReturn(singletonList(mock(AssociationInfo.class)));
         mService.setPreferencesHelper(mPreferencesHelper);
         when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
@@ -3203,7 +3211,7 @@
 
     @Test
     public void testDeleteChannelOnlyDoExtraWorkIfExisted() throws Exception {
-        when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+        when(mCompanionMgr.getAssociations(PKG, mUserId))
                 .thenReturn(singletonList(mock(AssociationInfo.class)));
         mService.setPreferencesHelper(mPreferencesHelper);
         when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
@@ -3217,7 +3225,7 @@
 
     @Test
     public void testDeleteChannelGroupNotifyListener() throws Exception {
-        when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+        when(mCompanionMgr.getAssociations(PKG, mUserId))
                 .thenReturn(singletonList(mock(AssociationInfo.class)));
         NotificationChannelGroup ncg = new NotificationChannelGroup("a", "b/c");
         mService.setPreferencesHelper(mPreferencesHelper);
@@ -3233,7 +3241,7 @@
 
     @Test
     public void testDeleteChannelGroupChecksForFgses() throws Exception {
-        when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+        when(mCompanionMgr.getAssociations(PKG, mUserId))
                 .thenReturn(singletonList(mock(AssociationInfo.class)));
         CountDownLatch latch = new CountDownLatch(2);
         mService.createNotificationChannelGroup(
@@ -3282,7 +3290,7 @@
     @Test
     public void testUpdateNotificationChannelFromPrivilegedListener_success() throws Exception {
         mService.setPreferencesHelper(mPreferencesHelper);
-        when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+        when(mCompanionMgr.getAssociations(PKG, mUserId))
                 .thenReturn(singletonList(mock(AssociationInfo.class)));
         when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
                 eq(mTestNotificationChannel.getId()), anyBoolean()))
@@ -3302,7 +3310,7 @@
     @Test
     public void testUpdateNotificationChannelFromPrivilegedListener_noAccess() throws Exception {
         mService.setPreferencesHelper(mPreferencesHelper);
-        when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+        when(mCompanionMgr.getAssociations(PKG, mUserId))
                 .thenReturn(emptyList());
 
         try {
@@ -3324,7 +3332,7 @@
     @Test
     public void testUpdateNotificationChannelFromPrivilegedListener_badUser() throws Exception {
         mService.setPreferencesHelper(mPreferencesHelper);
-        when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+        when(mCompanionMgr.getAssociations(PKG, mUserId))
                 .thenReturn(singletonList(mock(AssociationInfo.class)));
         mListener = mock(ManagedServices.ManagedServiceInfo.class);
         mListener.component = new ComponentName(PKG, PKG);
@@ -3350,7 +3358,7 @@
     @Test
     public void testGetNotificationChannelFromPrivilegedListener_cdm_success() throws Exception {
         mService.setPreferencesHelper(mPreferencesHelper);
-        when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+        when(mCompanionMgr.getAssociations(PKG, mUserId))
                 .thenReturn(singletonList(mock(AssociationInfo.class)));
 
         mBinderService.getNotificationChannelsFromPrivilegedListener(
@@ -3363,7 +3371,7 @@
     @Test
     public void testGetNotificationChannelFromPrivilegedListener_cdm_noAccess() throws Exception {
         mService.setPreferencesHelper(mPreferencesHelper);
-        when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+        when(mCompanionMgr.getAssociations(PKG, mUserId))
                 .thenReturn(emptyList());
 
         try {
@@ -3382,7 +3390,7 @@
     public void testGetNotificationChannelFromPrivilegedListener_assistant_success()
             throws Exception {
         mService.setPreferencesHelper(mPreferencesHelper);
-        when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+        when(mCompanionMgr.getAssociations(PKG, mUserId))
                 .thenReturn(emptyList());
         when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true);
 
@@ -3397,7 +3405,7 @@
     public void testGetNotificationChannelFromPrivilegedListener_assistant_noAccess()
             throws Exception {
         mService.setPreferencesHelper(mPreferencesHelper);
-        when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+        when(mCompanionMgr.getAssociations(PKG, mUserId))
                 .thenReturn(emptyList());
         when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(false);
 
@@ -3416,7 +3424,7 @@
     @Test
     public void testGetNotificationChannelFromPrivilegedListener_badUser() throws Exception {
         mService.setPreferencesHelper(mPreferencesHelper);
-        when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+        when(mCompanionMgr.getAssociations(PKG, mUserId))
                 .thenReturn(singletonList(mock(AssociationInfo.class)));
         mListener = mock(ManagedServices.ManagedServiceInfo.class);
         when(mListener.enabledAndUserMatches(anyInt())).thenReturn(false);
@@ -3437,7 +3445,7 @@
     @Test
     public void testGetNotificationChannelGroupsFromPrivilegedListener_success() throws Exception {
         mService.setPreferencesHelper(mPreferencesHelper);
-        when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+        when(mCompanionMgr.getAssociations(PKG, mUserId))
                 .thenReturn(singletonList(mock(AssociationInfo.class)));
 
         mBinderService.getNotificationChannelGroupsFromPrivilegedListener(
@@ -3449,7 +3457,7 @@
     @Test
     public void testGetNotificationChannelGroupsFromPrivilegedListener_noAccess() throws Exception {
         mService.setPreferencesHelper(mPreferencesHelper);
-        when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+        when(mCompanionMgr.getAssociations(PKG, mUserId))
                 .thenReturn(emptyList());
 
         try {
@@ -3466,7 +3474,7 @@
     @Test
     public void testGetNotificationChannelGroupsFromPrivilegedListener_badUser() throws Exception {
         mService.setPreferencesHelper(mPreferencesHelper);
-        when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+        when(mCompanionMgr.getAssociations(PKG, mUserId))
                 .thenReturn(emptyList());
         mListener = mock(ManagedServices.ManagedServiceInfo.class);
         when(mListener.enabledAndUserMatches(anyInt())).thenReturn(false);
@@ -4538,7 +4546,7 @@
 
         // Same notifications are enqueued as posted, everything counts b/c id and tag don't match
         // anything that's currently enqueued or posted
-        int userId = UserHandle.getUserId(mUid);
+        int userId = mUserId;
         assertEquals(40,
                 mService.getNotificationCount(PKG, userId, 0, null));
         assertEquals(40,
@@ -6532,7 +6540,7 @@
         setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
 
         // package is not suspended
-        when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+        when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
                 .thenReturn(false);
 
         // notifications from this package are blocked by the user
@@ -6554,7 +6562,7 @@
         setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
 
         // package is not suspended
-        when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+        when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
                 .thenReturn(false);
 
         setAppInForegroundForToasts(mUid, false);
@@ -6573,7 +6581,7 @@
         setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
 
         // package is not suspended
-        when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+        when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
                 .thenReturn(false);
 
         setAppInForegroundForToasts(mUid, true);
@@ -6602,7 +6610,7 @@
         setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
 
         // package is not suspended
-        when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+        when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
                 .thenReturn(false);
 
         setAppInForegroundForToasts(mUid, true);
@@ -6625,7 +6633,7 @@
         setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
 
         // package is not suspended
-        when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+        when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
                 .thenReturn(false);
 
         setAppInForegroundForToasts(mUid, true);
@@ -6659,7 +6667,7 @@
         setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
 
         // package is not suspended
-        when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+        when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
                 .thenReturn(false);
 
         setAppInForegroundForToasts(mUid, true);
@@ -6678,7 +6686,7 @@
         setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
 
         // package is not suspended
-        when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+        when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
                 .thenReturn(false);
 
         setAppInForegroundForToasts(mUid, false);
@@ -6697,7 +6705,7 @@
         setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
 
         // package is not suspended
-        when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+        when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
                 .thenReturn(false);
 
         setAppInForegroundForToasts(mUid, true);
@@ -6728,7 +6736,7 @@
         setAppInForegroundForToasts(mUid, false);
 
         // package is not suspended
-        when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+        when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
                 .thenReturn(false);
 
         Binder token = new Binder();
@@ -6750,7 +6758,7 @@
         setAppInForegroundForToasts(mUid, true);
 
         // package is not suspended
-        when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+        when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
                 .thenReturn(false);
 
         Binder token = new Binder();
@@ -6771,7 +6779,7 @@
         setAppInForegroundForToasts(mUid, false);
 
         // package is not suspended
-        when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+        when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
                 .thenReturn(false);
 
         Binder token = new Binder();
@@ -6792,7 +6800,7 @@
         setAppInForegroundForToasts(mUid, false);
 
         // package is not suspended
-        when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+        when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
                 .thenReturn(false);
 
         Binder token = new Binder();
@@ -6825,7 +6833,7 @@
         setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
 
         // package is not suspended
-        when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+        when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
                 .thenReturn(false);
 
         // notifications from this package are blocked by the user
@@ -6849,7 +6857,7 @@
         setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
 
         // package is not suspended
-        when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+        when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
                 .thenReturn(false);
 
         setAppInForegroundForToasts(mUid, true);
@@ -6870,7 +6878,7 @@
         setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
 
         // package is not suspended
-        when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+        when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
                 .thenReturn(false);
 
         setAppInForegroundForToasts(mUid, false);
@@ -6883,20 +6891,81 @@
 
     @Test
     public void testTextToastsCallStatusBar() throws Exception {
-        final String testPackage = "testPackageName";
-        assertEquals(0, mService.mToastQueue.size());
-        mService.isSystemUid = false;
-        setToastRateIsWithinQuota(true);
-        setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
-
-        // package is not suspended
-        when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
-                .thenReturn(false);
+        allowTestPackageToToast();
 
         // enqueue toast -> no toasts enqueued
-        enqueueTextToast(testPackage, "Text");
-        verify(mStatusBar).showToast(anyInt(), any(), any(), any(), any(), anyInt(), any(),
-                anyInt());
+        enqueueTextToast(TEST_PACKAGE, "Text");
+
+        verifyToastShownForTestPackage("Text", DEFAULT_DISPLAY);
+    }
+
+    @Test
+    public void testTextToastsCallStatusBar_nonUiContext_defaultDisplay()
+            throws Exception {
+        allowTestPackageToToast();
+
+        enqueueTextToast(TEST_PACKAGE, "Text", /* isUiContext= */ false, DEFAULT_DISPLAY);
+
+        verifyToastShownForTestPackage("Text", DEFAULT_DISPLAY);
+    }
+
+    @Test
+    public void testTextToastsCallStatusBar_nonUiContext_secondaryDisplay()
+            throws Exception {
+        allowTestPackageToToast();
+
+        enqueueTextToast(TEST_PACKAGE, "Text", /* isUiContext= */ false, SECONDARY_DISPLAY_ID);
+
+        verifyToastShownForTestPackage("Text", SECONDARY_DISPLAY_ID);
+    }
+
+    @Test
+    public void testTextToastsCallStatusBar_visibleBgUsers_uiContext_defaultDisplay()
+            throws Exception {
+        mockIsVisibleBackgroundUsersSupported(true);
+        mockDisplayAssignedToUser(SECONDARY_DISPLAY_ID);
+        allowTestPackageToToast();
+
+        enqueueTextToast(TEST_PACKAGE, "Text", /* isUiContext= */ true, DEFAULT_DISPLAY);
+
+        verifyToastShownForTestPackage("Text", DEFAULT_DISPLAY);
+
+    }
+
+    @Test
+    public void testTextToastsCallStatusBar_visibleBgUsers_uiContext_secondaryDisplay()
+            throws Exception {
+        mockIsVisibleBackgroundUsersSupported(true);
+        mockDisplayAssignedToUser(INVALID_DISPLAY); // make sure it's not used
+        allowTestPackageToToast();
+
+        enqueueTextToast(TEST_PACKAGE, "Text", /* isUiContext= */ true, SECONDARY_DISPLAY_ID);
+
+        verifyToastShownForTestPackage("Text", SECONDARY_DISPLAY_ID);
+    }
+
+    @Test
+    public void testTextToastsCallStatusBar_visibleBgUsers_nonUiContext_defaultDisplay()
+            throws Exception {
+        mockIsVisibleBackgroundUsersSupported(true);
+        mockDisplayAssignedToUser(SECONDARY_DISPLAY_ID);
+        allowTestPackageToToast();
+
+        enqueueTextToast(TEST_PACKAGE, "Text", /* isUiContext= */ false, DEFAULT_DISPLAY);
+
+        verifyToastShownForTestPackage("Text", SECONDARY_DISPLAY_ID);
+    }
+
+    @Test
+    public void testTextToastsCallStatusBar_visibleBgUsers_nonUiContext_secondaryDisplay()
+            throws Exception {
+        mockIsVisibleBackgroundUsersSupported(true);
+        mockDisplayAssignedToUser(INVALID_DISPLAY); // make sure it's not used
+        allowTestPackageToToast();
+
+        enqueueTextToast(TEST_PACKAGE, "Text", /* isUiContext= */ false, SECONDARY_DISPLAY_ID);
+
+        verifyToastShownForTestPackage("Text", SECONDARY_DISPLAY_ID);
     }
 
     @Test
@@ -6908,7 +6977,7 @@
         setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
 
         // package is suspended
-        when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+        when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
                 .thenReturn(true);
 
         // notifications from this package are NOT blocked by the user
@@ -6928,7 +6997,7 @@
         setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
 
         // package is not suspended
-        when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+        when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
                 .thenReturn(false);
 
         // notifications from this package are blocked by the user
@@ -6950,7 +7019,7 @@
         setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
 
         // package is suspended
-        when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+        when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
                 .thenReturn(true);
 
         // notifications from this package ARE blocked by the user
@@ -6972,7 +7041,7 @@
         setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
 
         // package is not suspended
-        when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+        when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
                 .thenReturn(false);
 
         INotificationManager nmService = (INotificationManager) mService.mService;
@@ -7002,7 +7071,7 @@
     private void setIfPackageHasPermissionToAvoidToastRateLimiting(
             String pkg, boolean hasPermission) throws Exception {
         when(mPackageManager.checkPermission(android.Manifest.permission.UNLIMITED_TOASTS,
-                pkg, UserHandle.getUserId(mUid)))
+                pkg, mUserId))
                 .thenReturn(hasPermission ? PERMISSION_GRANTED : PERMISSION_DENIED);
     }
 
@@ -9611,7 +9680,7 @@
     @Test
     public void testMigrateNotificationFilter_migrationAllAllowed() throws Exception {
         int uid = 9000;
-        int[] userIds = new int[] {UserHandle.getUserId(mUid), 1000};
+        int[] userIds = new int[] {mUserId, 1000};
         when(mUm.getProfileIds(anyInt(), anyBoolean())).thenReturn(userIds);
         List<String> disallowedApps = ImmutableList.of("apples", "bananas", "cherries");
         for (int userId : userIds) {
@@ -9643,10 +9712,10 @@
 
     @Test
     public void testMigrateNotificationFilter_noPreexistingFilter() throws Exception {
-        int[] userIds = new int[] {UserHandle.getUserId(mUid)};
+        int[] userIds = new int[] {mUserId};
         when(mUm.getProfileIds(anyInt(), anyBoolean())).thenReturn(userIds);
         List<String> disallowedApps = ImmutableList.of("apples");
-        when(mPackageManager.getPackageUid("apples", 0, UserHandle.getUserId(mUid)))
+        when(mPackageManager.getPackageUid("apples", 0, mUserId))
                 .thenReturn(1001);
 
         when(mListeners.getNotificationListenerFilter(any())).thenReturn(null);
@@ -9664,10 +9733,10 @@
 
     @Test
     public void testMigrateNotificationFilter_existingTypeFilter() throws Exception {
-        int[] userIds = new int[] {UserHandle.getUserId(mUid)};
+        int[] userIds = new int[] {mUserId};
         when(mUm.getProfileIds(anyInt(), anyBoolean())).thenReturn(userIds);
         List<String> disallowedApps = ImmutableList.of("apples");
-        when(mPackageManager.getPackageUid("apples", 0, UserHandle.getUserId(mUid)))
+        when(mPackageManager.getPackageUid("apples", 0, mUserId))
                 .thenReturn(1001);
 
         when(mListeners.getNotificationListenerFilter(any())).thenReturn(
@@ -9687,10 +9756,10 @@
 
     @Test
     public void testMigrateNotificationFilter_existingPkgFilter() throws Exception {
-        int[] userIds = new int[] {UserHandle.getUserId(mUid)};
+        int[] userIds = new int[] {mUserId};
         when(mUm.getProfileIds(anyInt(), anyBoolean())).thenReturn(userIds);
         List<String> disallowedApps = ImmutableList.of("apples");
-        when(mPackageManager.getPackageUid("apples", 0, UserHandle.getUserId(mUid)))
+        when(mPackageManager.getPackageUid("apples", 0, mUserId))
                 .thenReturn(1001);
 
         NotificationListenerFilter preexisting = new NotificationListenerFilter();
@@ -10704,6 +10773,16 @@
                 /* makeDefault= */ false);
     }
 
+    private void allowTestPackageToToast() throws Exception {
+        assertWithMessage("toast queue").that(mService.mToastQueue).isEmpty();
+        mService.isSystemUid = false;
+        setToastRateIsWithinQuota(true);
+        setIfPackageHasPermissionToAvoidToastRateLimiting(TEST_PACKAGE, false);
+        // package is not suspended
+        when(mPackageManager.isPackageSuspendedForUser(TEST_PACKAGE, mUserId))
+                .thenReturn(false);
+    }
+
     private void enqueueToast(String testPackage, ITransientNotification callback)
             throws RemoteException {
         enqueueToast((INotificationManager) mService.mService, testPackage, new Binder(), callback);
@@ -10716,7 +10795,25 @@
     }
 
     private void enqueueTextToast(String testPackage, CharSequence text) throws RemoteException {
+        enqueueTextToast(testPackage, text, /* isUiContext= */ true, DEFAULT_DISPLAY);
+    }
+
+    private void enqueueTextToast(String testPackage, CharSequence text, boolean isUiContext,
+            int displayId) throws RemoteException {
         ((INotificationManager) mService.mService).enqueueTextToast(testPackage, new Binder(), text,
-                TOAST_DURATION, /* isUiContext= */ true, DEFAULT_DISPLAY, /* textCallback= */ null);
+                TOAST_DURATION, isUiContext, displayId, /* textCallback= */ null);
+    }
+
+    private void mockIsVisibleBackgroundUsersSupported(boolean supported) {
+        when(mUm.isVisibleBackgroundUsersSupported()).thenReturn(supported);
+    }
+
+    private void mockDisplayAssignedToUser(int displayId) {
+        when(mUmInternal.getMainDisplayAssignedToUser(mUserId)).thenReturn(displayId);
+    }
+
+    private void verifyToastShownForTestPackage(String text, int displayId) {
+        verify(mStatusBar).showToast(eq(mUid), eq(TEST_PACKAGE), any(), eq(text), any(),
+                eq(TOAST_DURATION), any(), eq(displayId));
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index 695a72e..7a0961d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -202,7 +202,6 @@
         // Exclude comparing IME insets because currently the simulated layout only focuses on the
         // insets from status bar and navigation bar.
         realInsetsState.removeSource(InsetsSource.ID_IME);
-        realInsetsState.removeSource(InsetsState.ITYPE_CAPTION_BAR);
 
         assertEquals(new ToStringComparatorWrapper<>(realInsetsState),
                 new ToStringComparatorWrapper<>(simulatedInsetsState));
diff --git a/telecomm/java/android/telecom/Log.java b/telecomm/java/android/telecom/Log.java
index 884dcf2..a34094c 100644
--- a/telecomm/java/android/telecom/Log.java
+++ b/telecomm/java/android/telecom/Log.java
@@ -69,6 +69,7 @@
     private static final Object sSingletonSync = new Object();
     private static EventManager sEventManager;
     private static SessionManager sSessionManager;
+    private static Object sLock = null;
 
     /**
      * Tracks whether user-activated extended logging is enabled.
@@ -388,6 +389,19 @@
     }
 
     /**
+     * Sets the main telecom sync lock used within Telecom.  This is used when building log messages
+     * so that we can identify places in the code where we are doing something outside of the
+     * Telecom lock.
+     * @param lock The lock.
+     */
+    public static void setLock(Object lock) {
+        // Don't do lock monitoring on user builds.
+        if (!Build.IS_USER) {
+            sLock = lock;
+        }
+    }
+
+    /**
      * If user enabled extended logging is enabled and the time limit has passed, disables the
      * extended logging.
      */
@@ -512,7 +526,10 @@
                     args.length);
             msg = format + " (An error occurred while formatting the message.)";
         }
-        return String.format(Locale.US, "%s: %s%s", prefix, msg, sessionPostfix);
+        // If a lock was set, check if this thread holds that lock and output an emoji that lets
+        // the developer know whether a log message came from within the Telecom lock or not.
+        String isLocked = sLock != null ? (Thread.holdsLock(sLock) ? "\uD83D\uDD12" : "❗") : "";
+        return String.format(Locale.US, "%s: %s%s%s", prefix, msg, sessionPostfix, isLocked);
     }
 
     /**
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 9b7f244..c4a501d 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -17914,6 +17914,97 @@
     }
 
     /**
+     * Captures parameters for collection of emergency
+     * call diagnostic data
+     * @hide
+     */
+    public static class EmergencyCallDiagnosticParams {
+
+       private boolean mCollectTelecomDumpSys;
+       private boolean mCollectTelephonyDumpsys;
+       private boolean mCollectLogcat;
+
+        //logcat lines with this time or greater are collected
+        //how much is collected is dependent on internal implementation.
+        //Time represented as milliseconds since January 1, 1970 UTC
+        private long mLogcatStartTimeMillis;
+
+
+        public boolean isTelecomDumpSysCollectionEnabled() {
+            return mCollectTelecomDumpSys;
+        }
+
+        public void setTelecomDumpSysCollection(boolean collectTelecomDumpSys) {
+            mCollectTelecomDumpSys = collectTelecomDumpSys;
+        }
+
+        public boolean isTelephonyDumpSysCollectionEnabled() {
+            return mCollectTelephonyDumpsys;
+        }
+
+        public void setTelephonyDumpSysCollection(boolean collectTelephonyDumpsys) {
+            mCollectTelephonyDumpsys = collectTelephonyDumpsys;
+        }
+
+        public boolean isLogcatCollectionEnabled() {
+            return mCollectLogcat;
+        }
+
+        public long getLogcatStartTime()
+        {
+            return mLogcatStartTimeMillis;
+        }
+
+        public void setLogcatCollection(boolean collectLogcat, long startTimeMillis) {
+            mCollectLogcat = collectLogcat;
+            if(mCollectLogcat)
+            {
+                mLogcatStartTimeMillis = startTimeMillis;
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "EmergencyCallDiagnosticParams{" +
+                    "mCollectTelecomDumpSys=" + mCollectTelecomDumpSys +
+                    ", mCollectTelephonyDumpsys=" + mCollectTelephonyDumpsys +
+                    ", mCollectLogcat=" + mCollectLogcat +
+                    ", mLogcatStartTimeMillis=" + mLogcatStartTimeMillis +
+                    '}';
+        }
+    }
+
+    /**
+     * Request telephony to persist state for debugging emergency call failures.
+     *
+     * @param dropboxTag Tag to use when persisting data to dropbox service.
+     *
+     * @see params Parameters controlling what is collected
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.DUMP)
+    public void persistEmergencyCallDiagnosticData(@NonNull String dropboxTag,
+            @NonNull EmergencyCallDiagnosticParams params) {
+        try {
+            ITelephony telephony = ITelephony.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getTelephonyServiceRegisterer()
+                            .get());
+            if (telephony != null) {
+                telephony.persistEmergencyCallDiagnosticData(dropboxTag,
+                        params.isLogcatCollectionEnabled(),
+                        params.getLogcatStartTime(),
+                        params.isTelecomDumpSysCollectionEnabled(),
+                        params.isTelephonyDumpSysCollectionEnabled());
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error while persistEmergencyCallDiagnosticData: " + e);
+        }
+    }
+
+    /**
      * Set the UE's ability to accept/reject null ciphered and null integrity-protected connections.
      *
      * The modem is required to ignore this in case of an emergency call.
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index d0de3ac..bab08b5 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2677,6 +2677,21 @@
     int getSimStateForSlotIndex(int slotIndex);
 
     /**
+     * Request telephony to persist state for debugging emergency call failures.
+     *
+     * @param dropBoxTag Tag to use when persisting data to dropbox service.
+     * @param enableLogcat whether to collect logcat output
+     * @param logcatStartTimestampMillis timestamp from when logcat buffers would be persisted
+     * @param enableTelecomDump whether to collect telecom dumpsys
+     * @param enableTelephonyDump whether to collect telephony dumpsys
+     *
+     * @hide
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+            + "android.Manifest.permission.DUMP)")
+    void persistEmergencyCallDiagnosticData(String dropboxTag, boolean enableLogcat,
+        long logcatStartTimestampMillis, boolean enableTelecomDump, boolean enableTelephonyDump);
+    /**
      * Set whether the radio is able to connect with null ciphering or integrity
      * algorithms. This is a global setting and will apply to all active subscriptions
      * and all new subscriptions after this.
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
index dd9e4cf..3fccd12 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
@@ -219,9 +219,13 @@
             val resourceId =
                 Resources.getSystem()
                     .getIdentifier("image_wallpaper_component", "string", "android")
-            return ComponentNameMatcher.unflattenFromString(
+            // frameworks/base/core/res/res/values/config.xml returns package plus class name,
+            // but wallpaper layer has only class name
+            val rawComponentMatcher = ComponentNameMatcher.unflattenFromString(
                 instrumentation.targetContext.resources.getString(resourceId)
             )
+
+            return ComponentNameMatcher(rawComponentMatcher.className)
         }
 
         @Parameterized.Parameters(name = "{0}")
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index 075bc5e..4123f80 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -17,7 +17,9 @@
 package com.android.server;
 
 import static android.net.ConnectivityManager.NetworkCallback;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_ENTERPRISE;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
@@ -67,7 +69,6 @@
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
-import android.net.TelephonyNetworkSpecifier;
 import android.net.Uri;
 import android.net.vcn.IVcnStatusCallback;
 import android.net.vcn.IVcnUnderlyingNetworkPolicyListener;
@@ -128,6 +129,15 @@
     private static final VcnConfig TEST_VCN_CONFIG;
     private static final VcnConfig TEST_VCN_CONFIG_PKG_2;
     private static final int TEST_UID = Process.FIRST_APPLICATION_UID;
+    private static final String TEST_IFACE_NAME = "TEST_IFACE";
+    private static final String TEST_IFACE_NAME_2 = "TEST_IFACE2";
+    private static final LinkProperties TEST_LP_1 = new LinkProperties();
+    private static final LinkProperties TEST_LP_2 = new LinkProperties();
+
+    static {
+        TEST_LP_1.setInterfaceName(TEST_IFACE_NAME);
+        TEST_LP_2.setInterfaceName(TEST_IFACE_NAME_2);
+    }
 
     static {
         final Context mockConfigContext = mock(Context.class);
@@ -1034,8 +1044,7 @@
         setupSubscriptionAndStartVcn(subId, subGrp, isVcnActive);
 
         return mVcnMgmtSvc.getUnderlyingNetworkPolicy(
-                getNetworkCapabilitiesBuilderForTransport(subId, transport).build(),
-                new LinkProperties());
+                getNetworkCapabilitiesBuilderForTransport(subId, transport).build(), TEST_LP_1);
     }
 
     private void checkGetRestrictedTransportsFromCarrierConfig(
@@ -1260,7 +1269,7 @@
                 false /* expectRestricted */);
     }
 
-    private void setupTrackedCarrierWifiNetwork(NetworkCapabilities caps) {
+    private void setupTrackedNetwork(NetworkCapabilities caps, LinkProperties lp) {
         mVcnMgmtSvc.systemReady();
 
         final ArgumentCaptor<NetworkCallback> captor =
@@ -1269,7 +1278,10 @@
                 .registerNetworkCallback(
                         eq(new NetworkRequest.Builder().clearCapabilities().build()),
                         captor.capture());
-        captor.getValue().onCapabilitiesChanged(mock(Network.class, CALLS_REAL_METHODS), caps);
+
+        Network mockNetwork = mock(Network.class, CALLS_REAL_METHODS);
+        captor.getValue().onCapabilitiesChanged(mockNetwork, caps);
+        captor.getValue().onLinkPropertiesChanged(mockNetwork, lp);
     }
 
     @Test
@@ -1279,7 +1291,7 @@
                 getNetworkCapabilitiesBuilderForTransport(TEST_SUBSCRIPTION_ID, TRANSPORT_WIFI)
                         .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
                         .build();
-        setupTrackedCarrierWifiNetwork(existingNetworkCaps);
+        setupTrackedNetwork(existingNetworkCaps, TEST_LP_1);
 
         // Trigger test without VCN instance alive; expect restart due to change of NOT_RESTRICTED
         // immutable capability
@@ -1288,7 +1300,7 @@
                         getNetworkCapabilitiesBuilderForTransport(
                                         TEST_SUBSCRIPTION_ID, TRANSPORT_WIFI)
                                 .build(),
-                        new LinkProperties());
+                        TEST_LP_1);
         assertTrue(policy.isTeardownRequested());
     }
 
@@ -1298,7 +1310,7 @@
         final NetworkCapabilities existingNetworkCaps =
                 getNetworkCapabilitiesBuilderForTransport(TEST_SUBSCRIPTION_ID, TRANSPORT_WIFI)
                         .build();
-        setupTrackedCarrierWifiNetwork(existingNetworkCaps);
+        setupTrackedNetwork(existingNetworkCaps, TEST_LP_1);
 
         final VcnUnderlyingNetworkPolicy policy =
                 startVcnAndGetPolicyForTransport(
@@ -1315,7 +1327,7 @@
                         .addCapability(NET_CAPABILITY_NOT_RESTRICTED)
                         .removeCapability(NET_CAPABILITY_IMS)
                         .build();
-        setupTrackedCarrierWifiNetwork(existingNetworkCaps);
+        setupTrackedNetwork(existingNetworkCaps, TEST_LP_1);
 
         final VcnUnderlyingNetworkPolicy policy =
                 mVcnMgmtSvc.getUnderlyingNetworkPolicy(
@@ -1336,7 +1348,7 @@
                 new NetworkCapabilities.Builder()
                         .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
                         .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
-                        .setNetworkSpecifier(new TelephonyNetworkSpecifier(TEST_SUBSCRIPTION_ID_2))
+                        .setSubscriptionIds(Collections.singleton(TEST_SUBSCRIPTION_ID_2))
                         .build();
 
         VcnUnderlyingNetworkPolicy policy =
@@ -1346,6 +1358,38 @@
         assertEquals(nc, policy.getMergedNetworkCapabilities());
     }
 
+    /**
+     * Checks that networks with similar capabilities do not clobber each other.
+     *
+     * <p>In previous iterations, the VcnMgmtSvc used capability-matching to check if a network
+     * undergoing policy checks were the same as an existing networks. However, this meant that if
+     * there were newly added capabilities that the VCN did not check, two networks differing only
+     * by that capability would restart each other constantly.
+     */
+    @Test
+    public void testGetUnderlyingNetworkPolicySimilarNetworks() throws Exception {
+        NetworkCapabilities nc1 =
+                new NetworkCapabilities.Builder()
+                        .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                        .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+                        .addCapability(NET_CAPABILITY_INTERNET)
+                        .setSubscriptionIds(Collections.singleton(TEST_SUBSCRIPTION_ID_2))
+                        .build();
+
+        NetworkCapabilities nc2 =
+                new NetworkCapabilities.Builder(nc1)
+                        .addCapability(NET_CAPABILITY_ENTERPRISE)
+                        .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+                        .build();
+
+        setupTrackedNetwork(nc1, TEST_LP_1);
+
+        VcnUnderlyingNetworkPolicy policy = mVcnMgmtSvc.getUnderlyingNetworkPolicy(nc2, TEST_LP_2);
+
+        assertFalse(policy.isTeardownRequested());
+        assertEquals(nc2, policy.getMergedNetworkCapabilities());
+    }
+
     @Test(expected = SecurityException.class)
     public void testGetUnderlyingNetworkPolicyInvalidPermission() {
         doReturn(PackageManager.PERMISSION_DENIED)
diff --git a/wifi/java/src/android/net/wifi/nl80211/OWNERS b/wifi/java/src/android/net/wifi/nl80211/OWNERS
new file mode 100644
index 0000000..8a75e25
--- /dev/null
+++ b/wifi/java/src/android/net/wifi/nl80211/OWNERS
@@ -0,0 +1 @@
+kumachang@google.com