Merge "Don't add the "Events" mode anymore, and delete it if disabled" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index dd919ca..a0f38d9 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -680,6 +680,11 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+cc_aconfig_library {
+    name: "com.android.media.flags.editing-aconfig-cc",
+    aconfig_declarations: "com.android.media.flags.editing-aconfig",
+}
+
 // MediaProjection
 aconfig_declarations {
     name: "com.android.media.flags.projection-aconfig",
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 384add5..2ab16e9 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -2397,7 +2397,11 @@
                 // it hasn't changed and there is no need to update.
                 ret = mBlastBufferQueue.createSurface();
             } else {
-                mBlastBufferQueue.update(mBbqSurfaceControl, width, height, format);
+                if (mBbqSurfaceControl != null && mBbqSurfaceControl.isValid()) {
+                    mBlastBufferQueue.update(mBbqSurfaceControl, width, height, format);
+                } else {
+                    Log.w(TAG, "Skipping BlastBufferQueue update - invalid surface control");
+                }
             }
 
             return ret;
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index e1402f8..b6aad11 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -266,3 +266,14 @@
     purpose: PURPOSE_BUGFIX
   }
 }
+
+flag {
+  name: "remove_starting_window_wait_for_multi_transitions"
+  namespace: "windowing_frontend"
+  description: "Avoid remove starting window too early when playing multiple transitions"
+  bug: "362347290"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index fbc058c..b0e38e2 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -122,18 +122,20 @@
     private final Lock mBackgroundServiceLock = new ReentrantLock();
     private ExecutorService mBackgroundLoggingService = Executors.newSingleThreadExecutor();
 
-    public PerfettoProtoLogImpl(@NonNull IProtoLogGroup[] groups) {
+    public PerfettoProtoLogImpl(@NonNull IProtoLogGroup[] groups)
+            throws ServiceManager.ServiceNotFoundException {
         this(null, null, null, () -> {}, groups);
     }
 
-    public PerfettoProtoLogImpl(@NonNull Runnable cacheUpdater, @NonNull IProtoLogGroup[] groups) {
+    public PerfettoProtoLogImpl(@NonNull Runnable cacheUpdater, @NonNull IProtoLogGroup[] groups)
+            throws ServiceManager.ServiceNotFoundException {
         this(null, null, null, cacheUpdater, groups);
     }
 
     public PerfettoProtoLogImpl(
             @NonNull String viewerConfigFilePath,
             @NonNull Runnable cacheUpdater,
-            @NonNull IProtoLogGroup[] groups) {
+            @NonNull IProtoLogGroup[] groups) throws ServiceManager.ServiceNotFoundException {
         this(viewerConfigFilePath,
                 null,
                 new ProtoLogViewerConfigReader(() -> {
@@ -177,12 +179,14 @@
             @Nullable ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
             @Nullable ProtoLogViewerConfigReader viewerConfigReader,
             @NonNull Runnable cacheUpdater,
-            @NonNull IProtoLogGroup[] groups) {
+            @NonNull IProtoLogGroup[] groups) throws ServiceManager.ServiceNotFoundException {
         this(viewerConfigFilePath, viewerConfigInputStreamProvider, viewerConfigReader,
                 cacheUpdater, groups,
                 ProtoLogDataSource::new,
-                IProtoLogConfigurationService.Stub
-                        .asInterface(ServiceManager.getService(PROTOLOG_CONFIGURATION_SERVICE))
+                android.tracing.Flags.clientSideProtoLogging() ?
+                    IProtoLogConfigurationService.Stub.asInterface(
+                        ServiceManager.getServiceOrThrow(PROTOLOG_CONFIGURATION_SERVICE)
+                    ) : null
         );
     }
 
@@ -222,7 +226,7 @@
         if (android.tracing.Flags.clientSideProtoLogging()) {
             mProtoLogConfigurationService = configurationService;
             Objects.requireNonNull(mProtoLogConfigurationService,
-                    "ServiceManager returned a null ProtoLog Configuration Service");
+                    "A null ProtoLog Configuration Service was provided!");
 
             try {
                 var args = new ProtoLogConfigurationServiceImpl.RegisterClientArgs();
diff --git a/core/java/com/android/internal/protolog/ProtoLog.java b/core/java/com/android/internal/protolog/ProtoLog.java
index bf77db7..adf03fe 100644
--- a/core/java/com/android/internal/protolog/ProtoLog.java
+++ b/core/java/com/android/internal/protolog/ProtoLog.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.protolog;
 
+import android.os.ServiceManager;
+
 import com.android.internal.protolog.common.IProtoLog;
 import com.android.internal.protolog.common.IProtoLogGroup;
 import com.android.internal.protolog.common.LogLevel;
@@ -76,7 +78,11 @@
                     groups = allGroups.toArray(new IProtoLogGroup[0]);
                 }
 
-                sProtoLogInstance = new PerfettoProtoLogImpl(groups);
+                try {
+                    sProtoLogInstance = new PerfettoProtoLogImpl(groups);
+                } catch (ServiceManager.ServiceNotFoundException e) {
+                    throw new RuntimeException(e);
+                }
             }
         } else {
             sProtoLogInstance = new LogcatOnlyProtoLogImpl();
diff --git a/core/java/com/android/internal/protolog/ProtoLogImpl.java b/core/java/com/android/internal/protolog/ProtoLogImpl.java
index 7bdcf2d..5d67534 100644
--- a/core/java/com/android/internal/protolog/ProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/ProtoLogImpl.java
@@ -23,6 +23,7 @@
 import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.VIEWER_CONFIG_PATH;
 
 import android.annotation.Nullable;
+import android.os.ServiceManager;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -106,18 +107,23 @@
             final var groups = sLogGroups.values().toArray(new IProtoLogGroup[0]);
 
             if (android.tracing.Flags.perfettoProtologTracing()) {
-                File f = new File(sViewerConfigPath);
-                if (!ProtoLog.REQUIRE_PROTOLOGTOOL && !f.exists()) {
-                    // TODO(b/353530422): Remove - temporary fix to unblock b/352290057
-                    // In some tests the viewer config file might not exist in which we don't
-                    // want to provide config path to the user
-                    Log.w(LOG_TAG, "Failed to find viewerConfigFile when setting up "
-                            + ProtoLogImpl.class.getSimpleName() + ". "
-                            + "Setting up without a viewer config instead...");
-                    sServiceInstance = new PerfettoProtoLogImpl(sCacheUpdater, groups);
-                } else {
-                    sServiceInstance =
-                            new PerfettoProtoLogImpl(sViewerConfigPath, sCacheUpdater, groups);
+                try {
+                    File f = new File(sViewerConfigPath);
+                    if (!ProtoLog.REQUIRE_PROTOLOGTOOL && !f.exists()) {
+                        // TODO(b/353530422): Remove - temporary fix to unblock b/352290057
+                        // In some tests the viewer config file might not exist in which we don't
+                        // want to provide config path to the user
+                        Log.w(LOG_TAG, "Failed to find viewerConfigFile when setting up "
+                                + ProtoLogImpl.class.getSimpleName() + ". "
+                                + "Setting up without a viewer config instead...");
+
+                        sServiceInstance = new PerfettoProtoLogImpl(sCacheUpdater, groups);
+                    } else {
+                        sServiceInstance =
+                                new PerfettoProtoLogImpl(sViewerConfigPath, sCacheUpdater, groups);
+                    }
+                } catch (ServiceManager.ServiceNotFoundException e) {
+                    throw new RuntimeException(e);
                 }
             } else {
                 var protologImpl = new LegacyProtoLogImpl(
diff --git a/core/proto/android/service/graphicsstats.proto b/core/proto/android/service/graphicsstats.proto
index bb654f0..d836563 100644
--- a/core/proto/android/service/graphicsstats.proto
+++ b/core/proto/android/service/graphicsstats.proto
@@ -62,6 +62,9 @@
 
     // HWUI renders pipeline type: GL or Vulkan
     optional PipelineType pipeline = 8;
+
+    // The UID of the app
+    optional int32 uid = 9;
 }
 
 message GraphicsStatsJankSummaryProto {
diff --git a/graphics/java/android/graphics/GraphicsStatsService.java b/graphics/java/android/graphics/GraphicsStatsService.java
index dc785c5..7a012bc 100644
--- a/graphics/java/android/graphics/GraphicsStatsService.java
+++ b/graphics/java/android/graphics/GraphicsStatsService.java
@@ -311,7 +311,7 @@
                 Log.w(TAG, "Unable to create path: '" + parent.getAbsolutePath() + "'");
                 return;
             }
-            nSaveBuffer(path.getAbsolutePath(), buffer.mInfo.mPackageName,
+            nSaveBuffer(path.getAbsolutePath(), buffer.mInfo.mUid, buffer.mInfo.mPackageName,
                     buffer.mInfo.mVersionCode, buffer.mInfo.mStartTime, buffer.mInfo.mEndTime,
                     buffer.mData);
         }
@@ -405,7 +405,7 @@
             HistoricalBuffer buffer = buffers.get(i);
             File path = pathForApp(buffer.mInfo);
             skipFiles.add(path);
-            nAddToDump(dump, path.getAbsolutePath(), buffer.mInfo.mPackageName,
+            nAddToDump(dump, path.getAbsolutePath(), buffer.mInfo.mUid, buffer.mInfo.mPackageName,
                     buffer.mInfo.mVersionCode,  buffer.mInfo.mStartTime, buffer.mInfo.mEndTime,
                     buffer.mData);
         }
@@ -469,21 +469,23 @@
 
     private static native int nGetAshmemSize();
     private static native long nCreateDump(int outFd, boolean isProto);
-    private static native void nAddToDump(long dump, String path, String packageName,
+    private static native void nAddToDump(long dump, String path, int uid, String packageName,
             long versionCode, long startTime, long endTime, byte[] data);
     private static native void nAddToDump(long dump, String path);
     private static native void nFinishDump(long dump);
     private static native void nFinishDumpInMemory(long dump, long pulledData, boolean lastFullDay);
-    private static native void nSaveBuffer(String path, String packageName, long versionCode,
-            long startTime, long endTime, byte[] data);
+    private static native void nSaveBuffer(String path, int uid, String packageName,
+            long versionCode, long startTime, long endTime, byte[] data);
 
     private final class BufferInfo {
+        final int mUid;
         final String mPackageName;
         final long mVersionCode;
         long mStartTime;
         long mEndTime;
 
-        BufferInfo(String packageName, long versionCode, long startTime) {
+        BufferInfo(int uid, String packageName, long versionCode, long startTime) {
+            this.mUid = uid;
             this.mPackageName = packageName;
             this.mVersionCode = versionCode;
             this.mStartTime = startTime;
@@ -502,7 +504,7 @@
         ActiveBuffer(IGraphicsStatsCallback token, int uid, int pid, String packageName,
                 long versionCode)
                 throws RemoteException, IOException {
-            mInfo = new BufferInfo(packageName, versionCode, System.currentTimeMillis());
+            mInfo = new BufferInfo(uid, packageName, versionCode, System.currentTimeMillis());
             mUid = uid;
             mPid = pid;
             mCallback = token;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index 7e6f434..4607a8e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -584,7 +584,8 @@
             final boolean windowModeChanged =
                     data.getTaskInfo().getWindowingMode() != taskInfo.getWindowingMode();
             final boolean visibilityChanged = data.getTaskInfo().isVisible != taskInfo.isVisible;
-            if (windowModeChanged || visibilityChanged) {
+            if (windowModeChanged || (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
+                    && visibilityChanged)) {
                 mRecentTasks.ifPresent(recentTasks ->
                         recentTasks.onTaskRunningInfoChanged(taskInfo));
             }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index e514dc3..f01ed84 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -39,6 +39,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.never;
 
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.TaskInfo;
@@ -599,6 +600,18 @@
     }
 
     @Test
+    public void testRecentTasks_visibilityChanges_notFreeForm_shouldNotNotifyTaskController() {
+        RunningTaskInfo task1_visible = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_FULLSCREEN);
+        mOrganizer.onTaskAppeared(task1_visible, /* leash= */ null);
+        RunningTaskInfo task1_hidden = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_FULLSCREEN);
+        task1_hidden.isVisible = false;
+
+        mOrganizer.onTaskInfoChanged(task1_hidden);
+
+        verify(mRecentTasksController, never()).onTaskRunningInfoChanged(task1_hidden);
+    }
+
+    @Test
     public void testRecentTasks_windowingModeChanges_shouldNotifyTaskController() {
         RunningTaskInfo task1 = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_FULLSCREEN);
         mOrganizer.onTaskAppeared(task1, /* leash= */ null);
diff --git a/libs/hwui/jni/GraphicsStatsService.cpp b/libs/hwui/jni/GraphicsStatsService.cpp
index 54369b9..80a8ae1 100644
--- a/libs/hwui/jni/GraphicsStatsService.cpp
+++ b/libs/hwui/jni/GraphicsStatsService.cpp
@@ -42,8 +42,9 @@
     return reinterpret_cast<jlong>(dump);
 }
 
-static void addToDump(JNIEnv* env, jobject, jlong dumpPtr, jstring jpath, jstring jpackage,
-                      jlong versionCode, jlong startTime, jlong endTime, jbyteArray jdata) {
+static void addToDump(JNIEnv* env, jobject, jlong dumpPtr, jstring jpath, jint uid,
+                      jstring jpackage, jlong versionCode, jlong startTime, jlong endTime,
+                      jbyteArray jdata) {
     std::string path;
     const ProfileData* data = nullptr;
     LOG_ALWAYS_FATAL_IF(jdata == nullptr && jpath == nullptr, "Path and data can't both be null");
@@ -68,7 +69,8 @@
     LOG_ALWAYS_FATAL_IF(!dump, "null passed for dump pointer");
 
     const std::string package(packageChars.c_str(), packageChars.size());
-    GraphicsStatsService::addToDump(dump, path, package, versionCode, startTime, endTime, data);
+    GraphicsStatsService::addToDump(dump, path, static_cast<uid_t>(uid), package, versionCode,
+                                    startTime, endTime, data);
 }
 
 static void addFileToDump(JNIEnv* env, jobject, jlong dumpPtr, jstring jpath) {
@@ -91,7 +93,7 @@
     GraphicsStatsService::finishDumpInMemory(dump, data, lastFullDay == JNI_TRUE);
 }
 
-static void saveBuffer(JNIEnv* env, jobject clazz, jstring jpath, jstring jpackage,
+static void saveBuffer(JNIEnv* env, jobject clazz, jstring jpath, jint uid, jstring jpackage,
                        jlong versionCode, jlong startTime, jlong endTime, jbyteArray jdata) {
     ScopedByteArrayRO buffer(env, jdata);
     LOG_ALWAYS_FATAL_IF(buffer.size() != sizeof(ProfileData),
@@ -106,7 +108,8 @@
     const std::string path(pathChars.c_str(), pathChars.size());
     const std::string package(packageChars.c_str(), packageChars.size());
     const ProfileData* data = reinterpret_cast<const ProfileData*>(buffer.get());
-    GraphicsStatsService::saveBuffer(path, package, versionCode, startTime, endTime, data);
+    GraphicsStatsService::saveBuffer(path, static_cast<uid_t>(uid), package, versionCode, startTime,
+                                     endTime, data);
 }
 
 static jobject gGraphicsStatsServiceObject = nullptr;
@@ -173,16 +176,16 @@
 } // namespace android
 using namespace android;
 
-static const JNINativeMethod sMethods[] =
-        {{"nGetAshmemSize", "()I", (void*)getAshmemSize},
-         {"nCreateDump", "(IZ)J", (void*)createDump},
-         {"nAddToDump", "(JLjava/lang/String;Ljava/lang/String;JJJ[B)V", (void*)addToDump},
-         {"nAddToDump", "(JLjava/lang/String;)V", (void*)addFileToDump},
-         {"nFinishDump", "(J)V", (void*)finishDump},
-         {"nFinishDumpInMemory", "(JJZ)V", (void*)finishDumpInMemory},
-         {"nSaveBuffer", "(Ljava/lang/String;Ljava/lang/String;JJJ[B)V", (void*)saveBuffer},
-         {"nativeInit", "()V", (void*)nativeInit},
-         {"nativeDestructor", "()V", (void*)nativeDestructor}};
+static const JNINativeMethod sMethods[] = {
+        {"nGetAshmemSize", "()I", (void*)getAshmemSize},
+        {"nCreateDump", "(IZ)J", (void*)createDump},
+        {"nAddToDump", "(JLjava/lang/String;ILjava/lang/String;JJJ[B)V", (void*)addToDump},
+        {"nAddToDump", "(JLjava/lang/String;)V", (void*)addFileToDump},
+        {"nFinishDump", "(J)V", (void*)finishDump},
+        {"nFinishDumpInMemory", "(JJZ)V", (void*)finishDumpInMemory},
+        {"nSaveBuffer", "(Ljava/lang/String;ILjava/lang/String;JJJ[B)V", (void*)saveBuffer},
+        {"nativeInit", "()V", (void*)nativeInit},
+        {"nativeDestructor", "()V", (void*)nativeDestructor}};
 
 int register_android_graphics_GraphicsStatsService(JNIEnv* env) {
     jclass graphicsStatsService_class =
diff --git a/libs/hwui/protos/graphicsstats.proto b/libs/hwui/protos/graphicsstats.proto
index 745393c..a6e786c 100644
--- a/libs/hwui/protos/graphicsstats.proto
+++ b/libs/hwui/protos/graphicsstats.proto
@@ -58,6 +58,9 @@
 
     // HWUI renders pipeline type: GL or Vulkan
     optional PipelineType pipeline = 8;
+
+    // The UID of the app
+    optional int32 uid = 9;
 }
 
 message GraphicsStatsJankSummaryProto {
diff --git a/libs/hwui/service/GraphicsStatsService.cpp b/libs/hwui/service/GraphicsStatsService.cpp
index ece5905..702f2a5 100644
--- a/libs/hwui/service/GraphicsStatsService.cpp
+++ b/libs/hwui/service/GraphicsStatsService.cpp
@@ -22,6 +22,7 @@
 #include <google/protobuf/io/zero_copy_stream_impl_lite.h>
 #include <inttypes.h>
 #include <log/log.h>
+#include <stats_annotations.h>
 #include <stats_event.h>
 #include <statslog_hwui.h>
 #include <sys/mman.h>
@@ -45,9 +46,9 @@
 constexpr int sHistogramSize = ProfileData::HistogramSize();
 constexpr int sGPUHistogramSize = ProfileData::GPUHistogramSize();
 
-static bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto, const std::string& package,
-                                      int64_t versionCode, int64_t startTime, int64_t endTime,
-                                      const ProfileData* data);
+static bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto, uid_t uid,
+                                      const std::string& package, int64_t versionCode,
+                                      int64_t startTime, int64_t endTime, const ProfileData* data);
 static void dumpAsTextToFd(protos::GraphicsStatsProto* proto, int outFd);
 
 class FileDescriptor {
@@ -159,15 +160,16 @@
     return success;
 }
 
-bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto, const std::string& package,
-                               int64_t versionCode, int64_t startTime, int64_t endTime,
-                               const ProfileData* data) {
+bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto, uid_t uid,
+                               const std::string& package, int64_t versionCode, int64_t startTime,
+                               int64_t endTime, const ProfileData* data) {
     if (proto->stats_start() == 0 || proto->stats_start() > startTime) {
         proto->set_stats_start(startTime);
     }
     if (proto->stats_end() == 0 || proto->stats_end() < endTime) {
         proto->set_stats_end(endTime);
     }
+    proto->set_uid(static_cast<int32_t>(uid));
     proto->set_package_name(package);
     proto->set_version_code(versionCode);
     proto->set_pipeline(data->pipelineType() == RenderPipelineType::SkiaGL ?
@@ -286,6 +288,7 @@
               proto->package_name().c_str(), proto->has_summary());
         return;
     }
+    dprintf(fd, "\nUID: %d", proto->uid());
     dprintf(fd, "\nPackage: %s", proto->package_name().c_str());
     dprintf(fd, "\nVersion: %" PRId64, proto->version_code());
     dprintf(fd, "\nStats since: %" PRId64 "ns", proto->stats_start());
@@ -319,14 +322,15 @@
     dprintf(fd, "\n");
 }
 
-void GraphicsStatsService::saveBuffer(const std::string& path, const std::string& package,
-                                      int64_t versionCode, int64_t startTime, int64_t endTime,
-                                      const ProfileData* data) {
+void GraphicsStatsService::saveBuffer(const std::string& path, uid_t uid,
+                                      const std::string& package, int64_t versionCode,
+                                      int64_t startTime, int64_t endTime, const ProfileData* data) {
     protos::GraphicsStatsProto statsProto;
     if (!parseFromFile(path, &statsProto)) {
         statsProto.Clear();
     }
-    if (!mergeProfileDataIntoProto(&statsProto, package, versionCode, startTime, endTime, data)) {
+    if (!mergeProfileDataIntoProto(&statsProto, uid, package, versionCode, startTime, endTime,
+                                   data)) {
         return;
     }
     // Although we might not have read any data from the file, merging the existing data
@@ -383,7 +387,7 @@
 
 private:
     // use package name and app version for a key
-    typedef std::pair<std::string, int64_t> DumpKey;
+    typedef std::tuple<uid_t, std::string, int64_t> DumpKey;
 
     std::map<DumpKey, protos::GraphicsStatsProto> mStats;
     int mFd;
@@ -392,7 +396,8 @@
 };
 
 void GraphicsStatsService::Dump::mergeStat(const protos::GraphicsStatsProto& stat) {
-    auto dumpKey = std::make_pair(stat.package_name(), stat.version_code());
+    auto dumpKey = std::make_tuple(static_cast<uid_t>(stat.uid()), stat.package_name(),
+                                   stat.version_code());
     auto findIt = mStats.find(dumpKey);
     if (findIt == mStats.end()) {
         mStats[dumpKey] = stat;
@@ -437,15 +442,15 @@
     return new Dump(outFd, type);
 }
 
-void GraphicsStatsService::addToDump(Dump* dump, const std::string& path,
+void GraphicsStatsService::addToDump(Dump* dump, const std::string& path, uid_t uid,
                                      const std::string& package, int64_t versionCode,
                                      int64_t startTime, int64_t endTime, const ProfileData* data) {
     protos::GraphicsStatsProto statsProto;
     if (!path.empty() && !parseFromFile(path, &statsProto)) {
         statsProto.Clear();
     }
-    if (data &&
-        !mergeProfileDataIntoProto(&statsProto, package, versionCode, startTime, endTime, data)) {
+    if (data && !mergeProfileDataIntoProto(&statsProto, uid, package, versionCode, startTime,
+                                           endTime, data)) {
         return;
     }
     if (!statsProto.IsInitialized()) {
@@ -556,6 +561,8 @@
         // TODO: fill in UI mainline module version, when the feature is available.
         AStatsEvent_writeInt64(event, (int64_t)0);
         AStatsEvent_writeBool(event, !lastFullDay);
+        AStatsEvent_writeInt32(event, stat.uid());
+        AStatsEvent_addBoolAnnotation(event, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
         AStatsEvent_build(event);
     }
     delete dump;
diff --git a/libs/hwui/service/GraphicsStatsService.h b/libs/hwui/service/GraphicsStatsService.h
index 4063f74..68c7355 100644
--- a/libs/hwui/service/GraphicsStatsService.h
+++ b/libs/hwui/service/GraphicsStatsService.h
@@ -44,13 +44,14 @@
         ProtobufStatsd,
     };
 
-    static void saveBuffer(const std::string& path, const std::string& package, int64_t versionCode,
-                           int64_t startTime, int64_t endTime, const ProfileData* data);
+    static void saveBuffer(const std::string& path, uid_t uid, const std::string& package,
+                           int64_t versionCode, int64_t startTime, int64_t endTime,
+                           const ProfileData* data);
 
     static Dump* createDump(int outFd, DumpType type);
-    static void addToDump(Dump* dump, const std::string& path, const std::string& package,
-                          int64_t versionCode, int64_t startTime, int64_t endTime,
-                          const ProfileData* data);
+    static void addToDump(Dump* dump, const std::string& path, uid_t uid,
+                          const std::string& package, int64_t versionCode, int64_t startTime,
+                          int64_t endTime, const ProfileData* data);
     static void addToDump(Dump* dump, const std::string& path);
     static void finishDump(Dump* dump);
     static void finishDumpInMemory(Dump* dump, AStatsEventList* data, bool lastFullDay);
diff --git a/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp b/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp
index c2d23e6..eb164f9 100644
--- a/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp
+++ b/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp
@@ -62,6 +62,7 @@
 
 TEST(GraphicsStats, saveLoad) {
     std::string path = findRootPath() + "/test_saveLoad";
+    uid_t uid = 123;
     std::string packageName = "com.test.saveLoad";
     MockProfileData mockData;
     mockData.editJankFrameCount() = 20;
@@ -75,12 +76,13 @@
     for (size_t i = 0; i < mockData.editSlowFrameCounts().size(); i++) {
         mockData.editSlowFrameCounts()[i] = (i % 5) + 1;
     }
-    GraphicsStatsService::saveBuffer(path, packageName, 5, 3000, 7000, &mockData);
+    GraphicsStatsService::saveBuffer(path, uid, packageName, 5, 3000, 7000, &mockData);
     protos::GraphicsStatsProto loadedProto;
     EXPECT_TRUE(GraphicsStatsService::parseFromFile(path, &loadedProto));
     // Clean up the file
     unlink(path.c_str());
 
+    EXPECT_EQ(uid, loadedProto.uid());
     EXPECT_EQ(packageName, loadedProto.package_name());
     EXPECT_EQ(5, loadedProto.version_code());
     EXPECT_EQ(3000, loadedProto.stats_start());
@@ -109,6 +111,7 @@
 TEST(GraphicsStats, merge) {
     std::string path = findRootPath() + "/test_merge";
     std::string packageName = "com.test.merge";
+    uid_t uid = 123;
     MockProfileData mockData;
     mockData.editJankFrameCount() = 20;
     mockData.editTotalFrameCount() = 100;
@@ -121,7 +124,7 @@
     for (size_t i = 0; i < mockData.editSlowFrameCounts().size(); i++) {
         mockData.editSlowFrameCounts()[i] = (i % 5) + 1;
     }
-    GraphicsStatsService::saveBuffer(path, packageName, 5, 3000, 7000, &mockData);
+    GraphicsStatsService::saveBuffer(path, uid, packageName, 5, 3000, 7000, &mockData);
     mockData.editJankFrameCount() = 50;
     mockData.editTotalFrameCount() = 500;
     for (size_t i = 0; i < mockData.editFrameCounts().size(); i++) {
@@ -130,13 +133,15 @@
     for (size_t i = 0; i < mockData.editSlowFrameCounts().size(); i++) {
         mockData.editSlowFrameCounts()[i] = ((i % 10) + 1) * 2;
     }
-    GraphicsStatsService::saveBuffer(path, packageName, 5, 7050, 10000, &mockData);
+
+    GraphicsStatsService::saveBuffer(path, uid, packageName, 5, 7050, 10000, &mockData);
 
     protos::GraphicsStatsProto loadedProto;
     EXPECT_TRUE(GraphicsStatsService::parseFromFile(path, &loadedProto));
     // Clean up the file
     unlink(path.c_str());
 
+    EXPECT_EQ(uid, loadedProto.uid());
     EXPECT_EQ(packageName, loadedProto.package_name());
     EXPECT_EQ(5, loadedProto.version_code());
     EXPECT_EQ(3000, loadedProto.stats_start());
diff --git a/media/java/android/media/flags/editing.aconfig b/media/java/android/media/flags/editing.aconfig
index bf6ec96..185f579 100644
--- a/media/java/android/media/flags/editing.aconfig
+++ b/media/java/android/media/flags/editing.aconfig
@@ -8,3 +8,10 @@
   description: "Add media metrics for transcoding/editing events."
   bug: "297487694"
 }
+
+flag {
+  name: "stagefrightrecorder_enable_b_frames"
+  namespace: "media_solutions"
+  description: "Enable B frames for Stagefright recorder."
+  bug: "341121900"
+}
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_chevron.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_chevron.xml
new file mode 100644
index 0000000..16ca18a
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_chevron.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="18dp"
+    android:height="18dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="@color/settingslib_materialColorOnSurfaceVariant"
+    android:autoMirrored="true">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M321,880L250,809L579,480L250,151L321,80L721,480L321,880Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_two_target_divider.xml b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_two_target_divider.xml
new file mode 100644
index 0000000..3f75181
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_two_target_divider.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/two_target_divider"
+    android:layout_width="wrap_content"
+    android:layout_height="match_parent"
+    android:gravity="start|center_vertical"
+    android:orientation="horizontal"
+    android:paddingStart="?android:attr/listPreferredItemPaddingEnd"
+    android:paddingLeft="?android:attr/listPreferredItemPaddingEnd"
+    android:paddingVertical="@dimen/settingslib_expressive_space_extrasmall7">
+
+    <ImageView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:paddingEnd="@dimen/settingslib_expressive_space_extrasmall6"
+        android:src="@drawable/settingslib_expressive_icon_chevron"/>
+
+    <View
+        android:layout_width="1dp"
+        android:layout_height="40dp"
+        android:background="?android:attr/listDivider" />
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts
index 3011ce0..b69912a 100644
--- a/packages/SettingsLib/Spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/build.gradle.kts
@@ -29,7 +29,7 @@
 
 allprojects {
     extra["androidTop"] = androidTop
-    extra["jetpackComposeVersion"] = "1.7.0-rc01"
+    extra["jetpackComposeVersion"] = "1.7.0"
 }
 
 subprojects {
diff --git a/packages/SettingsLib/Spa/gallery/res/values/strings.xml b/packages/SettingsLib/Spa/gallery/res/values/strings.xml
index 18a6db0..f942fd0 100644
--- a/packages/SettingsLib/Spa/gallery/res/values/strings.xml
+++ b/packages/SettingsLib/Spa/gallery/res/values/strings.xml
@@ -26,6 +26,8 @@
     <string name="single_line_summary_preference_summary" translatable="false">A very long summary to show case a preference which only shows a single line summary.</string>
     <!-- Footer text with two links. [DO NOT TRANSLATE] -->
     <string name="footer_with_two_links" translatable="false">Annotated string with <a href="https://www.android.com/">link 1</a> and <a href="https://source.android.com/">link 2</a>.</string>
+    <!-- TopIntroPreference preview text. [DO NOT TRANSLATE] -->
+    <string name="label_with_two_links" translatable="false"><a href="https://www.android.com/">Label</a></string>
 
     <!-- Sample title -->
     <string name="sample_title" translatable="false">Lorem ipsum</string>
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index 83d657e..7139f5b4 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -42,12 +42,15 @@
 import com.android.settingslib.spa.gallery.page.ProgressBarPageProvider
 import com.android.settingslib.spa.gallery.scaffold.NonScrollablePagerPageProvider
 import com.android.settingslib.spa.gallery.page.SliderPageProvider
+import com.android.settingslib.spa.gallery.preference.IntroPreferencePageProvider
 import com.android.settingslib.spa.gallery.preference.ListPreferencePageProvider
 import com.android.settingslib.spa.gallery.preference.MainSwitchPreferencePageProvider
 import com.android.settingslib.spa.gallery.preference.PreferenceMainPageProvider
 import com.android.settingslib.spa.gallery.preference.PreferencePageProvider
 import com.android.settingslib.spa.gallery.preference.SwitchPreferencePageProvider
+import com.android.settingslib.spa.gallery.preference.TopIntroPreferencePageProvider
 import com.android.settingslib.spa.gallery.preference.TwoTargetSwitchPreferencePageProvider
+import com.android.settingslib.spa.gallery.preference.ZeroStatePreferencePageProvider
 import com.android.settingslib.spa.gallery.scaffold.PagerMainPageProvider
 import com.android.settingslib.spa.gallery.scaffold.SearchScaffoldPageProvider
 import com.android.settingslib.spa.gallery.scaffold.SuwScaffoldPageProvider
@@ -82,6 +85,7 @@
                 MainSwitchPreferencePageProvider,
                 ListPreferencePageProvider,
                 TwoTargetSwitchPreferencePageProvider,
+                ZeroStatePreferencePageProvider,
                 ArgumentPageProvider,
                 SliderPageProvider,
                 SpinnerPageProvider,
@@ -109,6 +113,8 @@
                 SuwScaffoldPageProvider,
                 BannerPageProvider,
                 CopyablePageProvider,
+                IntroPreferencePageProvider,
+                TopIntroPreferencePageProvider,
             ),
             rootPages = listOf(
                 HomePageProvider.createSettingsPage(),
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/IntroPreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/IntroPreferencePageProvider.kt
new file mode 100644
index 0000000..603fcee
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/IntroPreferencePageProvider.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.gallery.preference
+
+import android.os.Bundle
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.AirplanemodeActive
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import com.android.settingslib.spa.framework.common.SettingsEntry
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.widget.preference.IntroPreference
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+
+private const val TITLE = "Sample IntroPreference"
+
+object IntroPreferencePageProvider : SettingsPageProvider {
+    override val name = "IntroPreference"
+    private val owner = createSettingsPage()
+
+    override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+        val entryList = mutableListOf<SettingsEntry>()
+        entryList.add(
+            SettingsEntryBuilder.create("IntroPreference", owner)
+                .setUiLayoutFn { SampleIntroPreference() }
+                .build()
+        )
+
+        return entryList
+    }
+
+    fun buildInjectEntry(): SettingsEntryBuilder {
+        return SettingsEntryBuilder.createInject(owner).setUiLayoutFn {
+            Preference(
+                object : PreferenceModel {
+                    override val title = TITLE
+                    override val onClick = navigator(name)
+                }
+            )
+        }
+    }
+
+    override fun getTitle(arguments: Bundle?): String {
+        return TITLE
+    }
+}
+
+@Composable
+private fun SampleIntroPreference() {
+    Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+        IntroPreference(
+            title = "Preferred network type",
+            descriptions = listOf("Description"),
+            imageVector = Icons.Outlined.AirplanemodeActive,
+        )
+    }
+}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt
index ce9678b..1626b02 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt
@@ -39,6 +39,9 @@
             ListPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
             TwoTargetSwitchPreferencePageProvider.buildInjectEntry()
                 .setLink(fromPage = owner).build(),
+            ZeroStatePreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+            IntroPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+            TopIntroPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
         )
     }
 
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TopIntroPreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TopIntroPreferencePageProvider.kt
new file mode 100644
index 0000000..b251266
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TopIntroPreferencePageProvider.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.gallery.preference
+
+import android.os.Bundle
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import com.android.settingslib.spa.gallery.R
+import com.android.settingslib.spa.framework.common.SettingsEntry
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.preference.TopIntroPreference
+import com.android.settingslib.spa.widget.preference.TopIntroPreferenceModel
+
+private const val TITLE = "Sample TopIntroPreference"
+
+object TopIntroPreferencePageProvider : SettingsPageProvider {
+    override val name = "TopIntroPreference"
+    private val owner = createSettingsPage()
+
+    override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+        val entryList = mutableListOf<SettingsEntry>()
+        entryList.add(
+            SettingsEntryBuilder.create("TopIntroPreference", owner)
+                .setUiLayoutFn { SampleTopIntroPreference() }
+                .build()
+        )
+
+        return entryList
+    }
+
+    fun buildInjectEntry(): SettingsEntryBuilder {
+        return SettingsEntryBuilder.createInject(owner).setUiLayoutFn {
+            Preference(
+                object : PreferenceModel {
+                    override val title = TITLE
+                    override val onClick = navigator(name)
+                }
+            )
+        }
+    }
+
+    override fun getTitle(arguments: Bundle?): String {
+        return TITLE
+    }
+}
+
+@Composable
+private fun SampleTopIntroPreference() {
+    Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+        TopIntroPreference(
+            object : TopIntroPreferenceModel {
+                override val text =
+                    "Additional text needed for the page. This can sit on the right side of the screen in 2 column.\n" +
+                        "Example collapsed text area that you will not see until you expand this block."
+                override val expandText = "Expand"
+                override val collapseText = "Collapse"
+                override val labelText = R.string.label_with_two_links
+            }
+        )
+    }
+}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ZeroStatePreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ZeroStatePreferencePageProvider.kt
new file mode 100644
index 0000000..4a9c5c8
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ZeroStatePreferencePageProvider.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.gallery.preference
+
+import android.os.Bundle
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.History
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.common.SettingsEntry
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.preference.ZeroStatePreference
+
+private const val TITLE = "Sample ZeroStatePreference"
+
+object ZeroStatePreferencePageProvider : SettingsPageProvider {
+    override val name = "ZeroStatePreference"
+    private val owner = createSettingsPage()
+
+    override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+        val entryList = mutableListOf<SettingsEntry>()
+        entryList.add(
+            SettingsEntryBuilder.create("ZeroStatePreference", owner)
+                .setUiLayoutFn {
+                    SampleZeroStatePreference()
+                }.build()
+        )
+
+        return entryList
+    }
+
+    fun buildInjectEntry(): SettingsEntryBuilder {
+        return SettingsEntryBuilder.createInject(owner)
+            .setUiLayoutFn {
+                Preference(object : PreferenceModel {
+                    override val title = TITLE
+                    override val onClick = navigator(name)
+                })
+            }
+    }
+
+    override fun getTitle(arguments: Bundle?): String {
+        return TITLE
+    }
+}
+
+@Composable
+private fun SampleZeroStatePreference() {
+    Box(
+        modifier = Modifier.fillMaxSize(),
+        contentAlignment = Alignment.Center
+    ) {
+        ZeroStatePreference(
+            Icons.Filled.History,
+            "No recent search history",
+            "Description"
+        )
+    }
+}
+
+
+@Preview(showBackground = true)
+@Composable
+private fun SwitchPreferencePagePreview() {
+    SettingsTheme {
+        ZeroStatePreferencePageProvider.Page(null)
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index f0c2ea6..790aa9f 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -54,15 +54,16 @@
 dependencies {
     api(project(":SettingsLibColor"))
     api("androidx.appcompat:appcompat:1.7.0")
-    api("androidx.compose.material3:material3:1.3.0-rc01")
+    api("androidx.compose.material3:material3:1.3.0")
     api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion")
     api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion")
     api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion")
     api("androidx.lifecycle:lifecycle-livedata-ktx")
     api("androidx.lifecycle:lifecycle-runtime-compose")
-    api("androidx.navigation:navigation-compose:2.8.0-rc01")
+    api("androidx.navigation:navigation-compose:2.8.1")
     api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha")
     api("com.google.android.material:material:1.11.0")
+    api("androidx.graphics:graphics-shapes-android:1.0.1")
     debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion")
     implementation("com.airbnb.android:lottie-compose:6.4.0")
 
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
index 1f3e2425..f8c791a 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
@@ -21,7 +21,9 @@
 
 object SettingsDimension {
     val paddingTiny = 2.dp
-    val paddingSmall = 4.dp
+    val paddingExtraSmall = 4.dp
+    val paddingSmall = if (isSpaExpressiveEnabled) 8.dp else 4.dp
+    val paddingExtraSmall5 = 10.dp
     val paddingLarge = 16.dp
     val paddingExtraLarge = 24.dp
 
@@ -56,6 +58,7 @@
     val itemDividerHeight = 32.dp
 
     val iconLarge = 48.dp
+    val introIconSize = 40.dp
 
     /** The size when app icon is displayed in list. */
     val appIconItemSize = 32.dp
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt
index 15def72..f948d51 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt
@@ -21,6 +21,7 @@
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
+import com.android.settingslib.spa.framework.util.SystemProperties
 
 /**
  * The Material 3 Theme for Settings.
@@ -41,4 +42,5 @@
     }
 }
 
-const val isSpaExpressiveEnabled = false
\ No newline at end of file
+val isSpaExpressiveEnabled
+    by lazy { SystemProperties.getBoolean("is_expressive_design_enabled", false) }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SystemProperties.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SystemProperties.kt
new file mode 100644
index 0000000..ed4936b
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SystemProperties.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.util
+
+import android.annotation.SuppressLint
+import android.util.Log
+
+@SuppressLint("PrivateApi")
+object SystemProperties {
+    private const val TAG = "SystemProperties"
+
+    fun getBoolean(key: String, default: Boolean): Boolean = try {
+        val systemProperties = Class.forName("android.os.SystemProperties")
+        systemProperties
+            .getMethod("getBoolean", String::class.java, Boolean::class.java)
+            .invoke(systemProperties, key, default) as Boolean
+    } catch (e: Exception) {
+        Log.e(TAG, "getBoolean: $key", e)
+        default
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/IntroPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/IntroPreference.kt
new file mode 100644
index 0000000..22a5755
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/IntroPreference.kt
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.preference
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.AirplanemodeActive
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+
+@Composable
+fun IntroPreference(
+    title: String,
+    descriptions: List<String>? = null,
+    imageVector: ImageVector? = null,
+) {
+    IntroPreference(title = title, descriptions = descriptions, icon = { IntroIcon(imageVector) })
+}
+
+@Composable
+fun IntroAppPreference(
+    title: String,
+    descriptions: List<String>? = null,
+    appIcon: @Composable (() -> Unit),
+) {
+    IntroPreference(title = title, descriptions = descriptions, icon = { IntroAppIcon(appIcon) })
+}
+
+@Composable
+internal fun IntroPreference(
+    title: String,
+    descriptions: List<String>?,
+    icon: @Composable (() -> Unit),
+) {
+    Column(
+        modifier =
+            Modifier.fillMaxWidth()
+                .padding(
+                    horizontal = SettingsDimension.paddingExtraLarge,
+                    vertical = SettingsDimension.paddingLarge,
+                ),
+        horizontalAlignment = Alignment.CenterHorizontally,
+    ) {
+        icon()
+        IntroTitle(title)
+        IntroDescription(descriptions)
+    }
+}
+
+@Composable
+private fun IntroIcon(imageVector: ImageVector?) {
+    if (imageVector != null) {
+        Box(
+            modifier =
+                Modifier.size(SettingsDimension.itemIconContainerSize)
+                    .clip(CircleShape)
+                    .background(MaterialTheme.colorScheme.secondaryContainer),
+            contentAlignment = Alignment.Center,
+        ) {
+            Icon(
+                imageVector = imageVector,
+                contentDescription = null,
+                modifier = Modifier.size(SettingsDimension.introIconSize),
+                tint = MaterialTheme.colorScheme.onSecondary,
+            )
+        }
+    }
+}
+
+@Composable
+private fun IntroAppIcon(appIcon: @Composable () -> Unit) {
+    Box(
+        modifier = Modifier.size(SettingsDimension.itemIconContainerSize).clip(CircleShape),
+        contentAlignment = Alignment.Center,
+    ) {
+        appIcon()
+    }
+}
+
+@Composable
+private fun IntroTitle(title: String) {
+    Box(modifier = Modifier.padding(top = SettingsDimension.paddingLarge)) {
+        Text(
+            text = title,
+            textAlign = TextAlign.Center,
+            style = MaterialTheme.typography.titleLarge,
+            color = MaterialTheme.colorScheme.onSurface,
+        )
+    }
+}
+
+@Composable
+private fun IntroDescription(descriptions: List<String>?) {
+    if (descriptions != null) {
+        for (description in descriptions) {
+            if (description.isEmpty()) continue
+            Text(
+                text = description,
+                textAlign = TextAlign.Center,
+                style = MaterialTheme.typography.titleMedium,
+                color = MaterialTheme.colorScheme.onSurfaceVariant,
+                modifier = Modifier.padding(top = SettingsDimension.paddingExtraSmall),
+            )
+        }
+    }
+}
+
+@Preview
+@Composable
+private fun IntroPreferencePreview() {
+    IntroPreference(
+        title = "Preferred network type",
+        descriptions = listOf("Description", "Version"),
+        imageVector = Icons.Outlined.AirplanemodeActive,
+    )
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TopIntroPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TopIntroPreference.kt
new file mode 100644
index 0000000..7e61959
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TopIntroPreference.kt
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.preference
+
+import androidx.annotation.StringRes
+import androidx.compose.animation.animateContentSize
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.KeyboardArrowDown
+import androidx.compose.material.icons.filled.KeyboardArrowUp
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.toMediumWeight
+import com.android.settingslib.spa.framework.util.annotatedStringResource
+
+/** The widget model for [TopIntroPreference] widget. */
+interface TopIntroPreferenceModel {
+    /** The content of this [TopIntroPreference]. */
+    val text: String
+
+    /** The text clicked to expand this [TopIntroPreference]. */
+    val expandText: String
+
+    /** The text clicked to collapse this [TopIntroPreference]. */
+    val collapseText: String
+
+    /** The text clicked to open other resources. Should be a resource Id. */
+    val labelText: Int?
+}
+
+@Composable
+fun TopIntroPreference(model: TopIntroPreferenceModel) {
+    var expanded by remember { mutableStateOf(false) }
+    Column(Modifier.background(MaterialTheme.colorScheme.surfaceContainer)) {
+        // TopIntroPreference content.
+        Column(
+            modifier =
+                Modifier.padding(
+                        horizontal = SettingsDimension.paddingExtraLarge,
+                        vertical = SettingsDimension.paddingSmall,
+                    )
+                    .animateContentSize()
+        ) {
+            Text(
+                text = model.text,
+                style = MaterialTheme.typography.bodyLarge,
+                maxLines = if (expanded) MAX_LINE else MIN_LINE,
+            )
+            if (expanded) TopIntroAnnotatedText(model.labelText)
+        }
+
+        // TopIntroPreference collapse bar.
+        Row(
+            verticalAlignment = Alignment.CenterVertically,
+            modifier =
+                Modifier.fillMaxWidth()
+                    .clickable(onClick = { expanded = !expanded })
+                    .padding(
+                        top = SettingsDimension.paddingSmall,
+                        bottom = SettingsDimension.paddingLarge,
+                        start = SettingsDimension.paddingExtraLarge,
+                        end = SettingsDimension.paddingExtraLarge,
+                    ),
+        ) {
+            Icon(
+                imageVector =
+                    if (expanded) Icons.Filled.KeyboardArrowUp else Icons.Filled.KeyboardArrowDown,
+                contentDescription = null,
+                modifier =
+                    Modifier.size(SettingsDimension.itemIconSize)
+                        .clip(CircleShape)
+                        .background(MaterialTheme.colorScheme.surfaceContainerHighest),
+            )
+            Text(
+                text = if (expanded) model.collapseText else model.expandText,
+                modifier = Modifier.padding(start = SettingsDimension.paddingSmall),
+                style = MaterialTheme.typography.bodyLarge.toMediumWeight(),
+                color = MaterialTheme.colorScheme.onSurface,
+            )
+        }
+    }
+}
+
+@Composable
+private fun TopIntroAnnotatedText(@StringRes id: Int?) {
+    if (id != null) {
+        Box(
+            Modifier.padding(
+                top = SettingsDimension.paddingExtraSmall5,
+                bottom = SettingsDimension.paddingExtraSmall5,
+                end = SettingsDimension.paddingLarge,
+            )
+        ) {
+            Text(
+                text = annotatedStringResource(id),
+                style = MaterialTheme.typography.bodyLarge.toMediumWeight(),
+                color = MaterialTheme.colorScheme.primary,
+            )
+        }
+    }
+}
+
+@Preview
+@Composable
+private fun TopIntroPreferencePreview() {
+    TopIntroPreference(
+        object : TopIntroPreferenceModel {
+            override val text =
+                "Additional text needed for the page. This can sit on the right side of the screen in 2 column.\n" +
+                    "Example collapsed text area that you will not see until you expand this block."
+            override val expandText = "Expand"
+            override val collapseText = "Collapse"
+            override val labelText = androidx.appcompat.R.string.abc_prepend_shortcut_label
+        }
+    )
+}
+
+const val MIN_LINE = 2
+const val MAX_LINE = 10
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ZeroStatePreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ZeroStatePreference.kt
new file mode 100644
index 0000000..3f2e772
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ZeroStatePreference.kt
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.preference
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.History
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Matrix
+import androidx.compose.ui.graphics.Outline
+import androidx.compose.ui.graphics.Path
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.graphics.asComposePath
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import androidx.graphics.shapes.CornerRounding
+import androidx.graphics.shapes.RoundedPolygon
+import androidx.graphics.shapes.star
+import androidx.graphics.shapes.toPath
+
+@Composable
+fun ZeroStatePreference(icon: ImageVector, text: String? = null, description: String? = null) {
+    val zeroStateShape = remember {
+        RoundedPolygon.star(
+            numVerticesPerRadius = 6,
+            innerRadius = 0.75f,
+            rounding = CornerRounding(0.3f)
+        )
+    }
+    val clip = remember(zeroStateShape) {
+        RoundedPolygonShape(polygon = zeroStateShape)
+    }
+    Column(horizontalAlignment = Alignment.CenterHorizontally) {
+        Box(
+            modifier = Modifier
+                .clip(clip)
+                .background(MaterialTheme.colorScheme.primary)
+                .size(160.dp)
+        ) {
+            Icon(
+                imageVector = icon,
+                modifier = Modifier
+                    .align(Alignment.Center)
+                    .size(72.dp),
+                tint = MaterialTheme.colorScheme.onPrimary,
+                contentDescription = null,
+            )
+        }
+        if (text != null) {
+            Text(
+                text = text,
+                textAlign = TextAlign.Center,
+                style = MaterialTheme.typography.titleMedium,
+                color = MaterialTheme.colorScheme.onSurfaceVariant,
+                modifier = Modifier.padding(top = 24.dp),
+            )
+        }
+        if (description != null) {
+            Box {
+                Text(
+                    text = description,
+                    textAlign = TextAlign.Center,
+                    style = MaterialTheme.typography.bodyMedium,
+                    color = MaterialTheme.colorScheme.onSurfaceVariant,
+                )
+            }
+        }
+    }
+}
+
+@Preview
+@Composable
+private fun ZeroStatePreferencePreview() {
+    ZeroStatePreference(
+        Icons.Filled.History,
+        "No recent search history",
+        "Description"
+    )
+}
+
+class RoundedPolygonShape(
+    private val polygon: RoundedPolygon,
+    private var matrix: Matrix = Matrix()
+) : Shape {
+    private var path = Path()
+    override fun createOutline(
+        size: Size,
+        layoutDirection: LayoutDirection,
+        density: Density
+    ): Outline {
+        path.rewind()
+        path = polygon.toPath().asComposePath()
+
+        matrix.reset()
+        matrix.scale(size.width / 2f, size.height / 2f)
+        matrix.translate(1f, 1f)
+        matrix.rotateZ(30.0f)
+
+        path.transform(matrix)
+        return Outline.Generic(path)
+    }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/tests/res/values/strings.xml b/packages/SettingsLib/Spa/tests/res/values/strings.xml
index fb8f878..346f69b 100644
--- a/packages/SettingsLib/Spa/tests/res/values/strings.xml
+++ b/packages/SettingsLib/Spa/tests/res/values/strings.xml
@@ -28,5 +28,7 @@
 
     <string name="test_annotated_string_resource">Annotated string with <b>bold</b> and <a href="https://www.android.com/">link</a>.</string>
 
+    <string name="test_top_intro_preference_label"><a href="https://www.android.com/">Label</a></string>
+
     <string name="test_link"><a href="https://www.android.com/">link</a></string>
 </resources>
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/SystemPropertiesTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/SystemPropertiesTest.kt
new file mode 100644
index 0000000..0827fa9
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/SystemPropertiesTest.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.util
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SystemPropertiesTest {
+
+    @Test
+    fun getBoolean_noCrash() {
+        SystemProperties.getBoolean("is_expressive_design_enabled", false)
+    }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/IntroPreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/IntroPreferenceTest.kt
new file mode 100644
index 0000000..5d80145
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/IntroPreferenceTest.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.preference
+
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class IntroPreferenceTest {
+    @get:Rule val composeTestRule = createComposeRule()
+
+    @Test
+    fun title_displayed() {
+        composeTestRule.setContent { IntroPreference(title = TITLE) }
+
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
+    }
+
+    @Test
+    fun description_displayed() {
+        composeTestRule.setContent { IntroPreference(title = TITLE, descriptions = DESCRIPTION) }
+
+        composeTestRule.onNodeWithText(DESCRIPTION.component1()).assertIsDisplayed()
+        composeTestRule.onNodeWithText(DESCRIPTION.component2()).assertIsNotDisplayed()
+    }
+
+    private companion object {
+        const val TITLE = "Title"
+        val DESCRIPTION = listOf("Description", "")
+    }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TopIntroPreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TopIntroPreferenceTest.kt
new file mode 100644
index 0000000..62a71d4
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TopIntroPreferenceTest.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.preference
+
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.test.R
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class TopIntroPreferenceTest {
+    @get:Rule val composeTestRule = createComposeRule()
+
+    @Test
+    fun content_collapsed_displayed() {
+        composeTestRule.setContent {
+            TopIntroPreference(
+                object : TopIntroPreferenceModel {
+                    override val text = TEXT
+                    override val expandText = EXPAND_TEXT
+                    override val collapseText = COLLAPSE_TEXT
+                    override val labelText = R.string.test_top_intro_preference_label
+                }
+            )
+        }
+
+        composeTestRule.onNodeWithText(TEXT).assertIsDisplayed()
+        composeTestRule.onNodeWithText(EXPAND_TEXT).assertIsDisplayed()
+    }
+
+    @Test
+    fun content_expended_displayed() {
+        composeTestRule.setContent {
+            TopIntroPreference(
+                object : TopIntroPreferenceModel {
+                    override val text = TEXT
+                    override val expandText = EXPAND_TEXT
+                    override val collapseText = COLLAPSE_TEXT
+                    override val labelText = R.string.test_top_intro_preference_label
+                }
+            )
+        }
+
+        composeTestRule.onNodeWithText(TEXT).assertIsDisplayed()
+        composeTestRule.onNodeWithText(EXPAND_TEXT).assertIsDisplayed().performClick()
+        composeTestRule.onNodeWithText(COLLAPSE_TEXT).assertIsDisplayed()
+        composeTestRule.onNodeWithText(LABEL_TEXT).assertIsDisplayed()
+    }
+
+    private companion object {
+        const val TEXT = "Text"
+        const val EXPAND_TEXT = "Expand"
+        const val COLLAPSE_TEXT = "Collapse"
+        const val LABEL_TEXT = "Label"
+    }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ZeroStatePreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ZeroStatePreferenceTest.kt
new file mode 100644
index 0000000..99ac27c
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ZeroStatePreferenceTest.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.preference
+
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.History
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class ZeroStatePreferenceTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @Test
+    fun title_displayed() {
+        composeTestRule.setContent {
+            ZeroStatePreference(Icons.Filled.History, TITLE)
+        }
+
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
+    }
+
+    @Test
+    fun description_displayed() {
+        composeTestRule.setContent {
+            ZeroStatePreference(Icons.Filled.History, TITLE, DESCRIPTION)
+        }
+
+        composeTestRule.onNodeWithText(DESCRIPTION).assertIsDisplayed()
+    }
+
+    private companion object {
+        const val TITLE = "Title"
+        const val DESCRIPTION = "Description"
+    }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/TwoTargetPreference/res/layout-v35/settingslib_expressive_preference_two_target.xml b/packages/SettingsLib/TwoTargetPreference/res/layout-v35/settingslib_expressive_preference_two_target.xml
new file mode 100644
index 0000000..4347ef2
--- /dev/null
+++ b/packages/SettingsLib/TwoTargetPreference/res/layout-v35/settingslib_expressive_preference_two_target.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?android:attr/listPreferredItemHeightSmall"
+    android:gravity="center_vertical"
+    android:background="?android:attr/selectableItemBackground"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:clipToPadding="false">
+
+    <include layout="@layout/settingslib_expressive_preference_icon_frame"/>
+
+    <include layout="@layout/settingslib_expressive_preference_text_frame" />
+
+    <include layout="@layout/settingslib_expressive_two_target_divider" />
+
+    <!-- Preference should place its actual preference widget here. -->
+    <LinearLayout
+        android:id="@android:id/widget_frame"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:minWidth="@dimen/two_target_min_width"
+        android:gravity="center"
+        android:orientation="vertical" />
+
+</LinearLayout>
diff --git a/packages/SettingsLib/TwoTargetPreference/src/com/android/settingslib/widget/TwoTargetPreference.java b/packages/SettingsLib/TwoTargetPreference/src/com/android/settingslib/widget/TwoTargetPreference.java
index b125f71..58ff0ce 100644
--- a/packages/SettingsLib/TwoTargetPreference/src/com/android/settingslib/widget/TwoTargetPreference.java
+++ b/packages/SettingsLib/TwoTargetPreference/src/com/android/settingslib/widget/TwoTargetPreference.java
@@ -72,7 +72,10 @@
     }
 
     private void init(Context context) {
-        setLayoutResource(R.layout.preference_two_target);
+        int resID = SettingsThemeHelper.isExpressiveTheme(context)
+                ? R.layout.settingslib_expressive_preference_two_target
+                : R.layout.preference_two_target;
+        setLayoutResource(resID);
         mSmallIconSize = context.getResources().getDimensionPixelSize(
                 R.dimen.two_target_pref_small_icon_size);
         mMediumIconSize = context.getResources().getDimensionPixelSize(
diff --git a/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java
index e41126f..2475c8e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java
@@ -31,6 +31,8 @@
 
 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
 import com.android.settingslib.core.instrumentation.SettingsJankMonitor;
+import com.android.settingslib.widget.SettingsThemeHelper;
+import com.android.settingslib.widget.theme.R;
 
 /**
  * A custom preference that provides inline switch toggle. It has a mandatory field for title, and
@@ -62,7 +64,9 @@
 
     @Override
     protected int getSecondTargetResId() {
-        return androidx.preference.R.layout.preference_widget_switch_compat;
+        return SettingsThemeHelper.isExpressiveTheme(getContext())
+                ? R.layout.settingslib_expressive_preference_switch
+                : androidx.preference.R.layout.preference_widget_switch_compat;
     }
 
     @Override
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index 492543f..af3ddfc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -310,6 +310,41 @@
                 .isEqualTo(displayId)
         }
 
+    @Test
+    fun afterSuccessfulAuthentication_focusIsNotRequested() =
+        testScope.runTest {
+            val authResult by collectLastValue(authenticationInteractor.onAuthenticationResult)
+            val textInputFocusRequested by collectLastValue(underTest.isTextFieldFocusRequested)
+            lockDeviceAndOpenPasswordBouncer()
+
+            // remove focus from text field
+            underTest.onTextFieldFocusChanged(false)
+            runCurrent()
+
+            // focus should be requested
+            assertThat(textInputFocusRequested).isTrue()
+
+            // simulate text field getting focus
+            underTest.onTextFieldFocusChanged(true)
+            runCurrent()
+
+            // focus should not be requested anymore
+            assertThat(textInputFocusRequested).isFalse()
+
+            // authenticate successfully.
+            underTest.onPasswordInputChanged("password")
+            underTest.onAuthenticateKeyPressed()
+            runCurrent()
+
+            assertThat(authResult).isTrue()
+
+            // remove focus from text field
+            underTest.onTextFieldFocusChanged(false)
+            runCurrent()
+            // focus should not be requested again
+            assertThat(textInputFocusRequested).isFalse()
+        }
+
     private fun TestScope.switchToScene(toScene: SceneKey) {
         val currentScene by collectLastValue(sceneInteractor.currentScene)
         val bouncerHidden = currentScene == Scenes.Bouncer && toScene != Scenes.Bouncer
@@ -327,10 +362,7 @@
         switchToScene(Scenes.Bouncer)
     }
 
-    private suspend fun TestScope.setLockout(
-        isLockedOut: Boolean,
-        failedAttemptCount: Int = 5,
-    ) {
+    private suspend fun TestScope.setLockout(isLockedOut: Boolean, failedAttemptCount: Int = 5) {
         if (isLockedOut) {
             repeat(failedAttemptCount) {
                 kosmos.fakeAuthenticationRepository.reportAuthenticationAttempt(false)
@@ -350,7 +382,7 @@
         kosmos.fakeUserRepository.selectedUser.value =
             SelectedUserModel(
                 userInfo = userInfo,
-                selectionStatus = SelectionStatus.SELECTION_COMPLETE
+                selectionStatus = SelectionStatus.SELECTION_COMPLETE,
             )
         advanceTimeBy(PasswordBouncerViewModel.DELAY_TO_FETCH_IMES)
     }
@@ -374,7 +406,7 @@
             subtypes =
                 List(auxiliarySubtypes + nonAuxiliarySubtypes) {
                     InputMethodModel.Subtype(subtypeId = it, isAuxiliary = it < auxiliarySubtypes)
-                }
+                },
         )
     }
 
@@ -383,9 +415,6 @@
         private const val WRONG_PASSWORD = "Wrong password"
 
         private val USER_INFOS =
-            listOf(
-                UserInfo(100, "First user", 0),
-                UserInfo(101, "Second user", 0),
-            )
+            listOf(UserInfo(100, "First user", 0), UserInfo(101, "Second user", 0))
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorTest.kt
index b3ffc71..d6734e8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorTest.kt
@@ -19,6 +19,7 @@
 import android.app.ActivityManager.RunningTaskInfo
 import android.app.usage.UsageEvents
 import android.content.pm.UserInfo
+import android.service.dream.dreamManager
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -28,6 +29,7 @@
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.ActivityStarter.OnDismissAction
 import com.android.systemui.plugins.activityStarter
 import com.android.systemui.settings.fakeUserTracker
 import com.android.systemui.shared.system.taskStackChangeListeners
@@ -48,6 +50,7 @@
 import org.junit.runner.RunWith
 import org.mockito.kotlin.any
 import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.argumentCaptor
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.never
 import org.mockito.kotlin.times
@@ -90,6 +93,27 @@
         }
 
     @Test
+    fun testNewTaskStartsWhileOnHub_stopsDream() =
+        testScope.runTest {
+            transition(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GLANCEABLE_HUB)
+            backgroundScope.launch { underTest.waitForActivityStartAndDismissKeyguard() }
+            runCurrent()
+
+            verify(activityStarter, never()).dismissKeyguardThenExecute(any(), anyOrNull(), any())
+            moveTaskToFront()
+
+            argumentCaptor<OnDismissAction>().apply {
+                verify(activityStarter).dismissKeyguardThenExecute(capture(), anyOrNull(), any())
+
+                firstValue.onDismiss()
+                runCurrent()
+
+                // Dream is stopped once keyguard is dismissed.
+                verify(kosmos.dreamManager).stopDream()
+            }
+        }
+
+    @Test
     fun testNewTaskStartsAfterExitingHub_doesNotTriggerUnlock() =
         testScope.runTest {
             transition(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GLANCEABLE_HUB)
@@ -209,7 +233,7 @@
                     ownerName = "test",
                 ),
             ),
-            testScope
+            testScope,
         )
         runCurrent()
     }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
index 873d1b3..4185aed 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
@@ -86,6 +86,9 @@
 
             _animateFailure.value = authenticationResult != AuthenticationResult.SUCCEEDED
             clearInput()
+            if (authenticationResult == AuthenticationResult.SUCCEEDED) {
+                onSuccessfulAuthentication()
+            }
         }
         awaitCancellation()
     }
@@ -116,6 +119,9 @@
     /** Returns the input entered so far. */
     protected abstract fun getInput(): List<Any>
 
+    /** Invoked after a successful authentication. */
+    protected open fun onSuccessfulAuthentication() = Unit
+
     /** Perform authentication result haptics */
     private fun performAuthenticationHapticFeedback(result: AuthenticationResult) {
         if (result == AuthenticationResult.SKIPPED) return
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
index 2493cf1..1427d78 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
@@ -81,50 +81,59 @@
     val selectedUserId: StateFlow<Int> = _selectedUserId.asStateFlow()
 
     private val requests = Channel<Request>(Channel.BUFFERED)
+    private var wasSuccessfullyAuthenticated = false
 
     override suspend fun onActivated(): Nothing {
-        coroutineScope {
-            launch { super.onActivated() }
-            launch {
-                requests.receiveAsFlow().collect { request ->
-                    when (request) {
-                        is OnImeSwitcherButtonClicked -> {
-                            inputMethodInteractor.showInputMethodPicker(
-                                displayId = request.displayId,
-                                showAuxiliarySubtypes = false,
-                            )
-                        }
-                        is OnImeDismissed -> {
-                            interactor.onImeHiddenByUser()
+        try {
+            coroutineScope {
+                launch { super.onActivated() }
+                launch {
+                    requests.receiveAsFlow().collect { request ->
+                        when (request) {
+                            is OnImeSwitcherButtonClicked -> {
+                                inputMethodInteractor.showInputMethodPicker(
+                                    displayId = request.displayId,
+                                    showAuxiliarySubtypes = false,
+                                )
+                            }
+                            is OnImeDismissed -> {
+                                interactor.onImeHiddenByUser()
+                            }
                         }
                     }
                 }
+                launch {
+                    combine(isInputEnabled, isTextFieldFocused) { hasInput, hasFocus ->
+                            hasInput && !hasFocus && !wasSuccessfullyAuthenticated
+                        }
+                        .collect { _isTextFieldFocusRequested.value = it }
+                }
+                launch {
+                    selectedUserInteractor.selectedUser.collect { _selectedUserId.value = it }
+                }
+                launch {
+                    // Re-fetch the currently-enabled IMEs whenever the selected user changes, and
+                    // whenever
+                    // the UI subscribes to the `isImeSwitcherButtonVisible` flow.
+                    combine(
+                            // InputMethodManagerService sometimes takes
+                            // some time to update its internal state when the
+                            // selected user changes.
+                            // As a workaround, delay fetching the IME info.
+                            selectedUserInteractor.selectedUser.onEach {
+                                delay(DELAY_TO_FETCH_IMES)
+                            },
+                            _isImeSwitcherButtonVisible.onSubscriberAdded(),
+                        ) { selectedUserId, _ ->
+                            inputMethodInteractor.hasMultipleEnabledImesOrSubtypes(selectedUserId)
+                        }
+                        .collect { _isImeSwitcherButtonVisible.value = it }
+                }
+                awaitCancellation()
             }
-            launch {
-                combine(isInputEnabled, isTextFieldFocused) { hasInput, hasFocus ->
-                        hasInput && !hasFocus
-                    }
-                    .collect { _isTextFieldFocusRequested.value = it }
-            }
-            launch { selectedUserInteractor.selectedUser.collect { _selectedUserId.value = it } }
-            launch {
-                // Re-fetch the currently-enabled IMEs whenever the selected user changes, and
-                // whenever
-                // the UI subscribes to the `isImeSwitcherButtonVisible` flow.
-                combine(
-                        // InputMethodManagerService sometimes takes some time to update its
-                        // internal
-                        // state when the selected user changes. As a workaround, delay fetching the
-                        // IME
-                        // info.
-                        selectedUserInteractor.selectedUser.onEach { delay(DELAY_TO_FETCH_IMES) },
-                        _isImeSwitcherButtonVisible.onSubscriberAdded()
-                    ) { selectedUserId, _ ->
-                        inputMethodInteractor.hasMultipleEnabledImesOrSubtypes(selectedUserId)
-                    }
-                    .collect { _isImeSwitcherButtonVisible.value = it }
-            }
-            awaitCancellation()
+        } finally {
+            // reset whenever the view model is "deactivated"
+            wasSuccessfullyAuthenticated = false
         }
     }
 
@@ -141,6 +150,10 @@
         return _password.value.toCharArray().toList()
     }
 
+    override fun onSuccessfulAuthentication() {
+        wasSuccessfullyAuthenticated = true
+    }
+
     /** Notifies that the user has changed the password input. */
     fun onPasswordInputChanged(newPassword: String) {
         if (newPassword.isNotEmpty()) {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepository.kt
index f77dd58..f0f7ca5 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepository.kt
@@ -82,19 +82,26 @@
             }
 
         _timers.value =
-            timerTargets.map { (stableId, target) ->
-                CommunalSmartspaceTimer(
-                    // The view layer should have the instance based smartspaceTargetId instead of
-                    // stable id, so that when a new instance of the timer is created, for example,
-                    // when it is paused, the view should re-render its remote views.
-                    smartspaceTargetId =
-                        if (communalTimerFlickerFix()) stableId else target.smartspaceTargetId,
-                    createdTimestampMillis = targetCreationTimes[stableId]!!,
-                    remoteViews = target.remoteViews!!,
-                )
-            }
-
-        logger.d({ "Smartspace timers updated: $str1" }) { str1 = _timers.value.toString() }
+            timerTargets
+                .map { (stableId, target) ->
+                    CommunalSmartspaceTimer(
+                        // The view layer should have the instance based smartspaceTargetId instead
+                        // of stable id, so that when a new instance of the timer is created, for
+                        // example, when it is paused, the view should re-render its remote views.
+                        smartspaceTargetId =
+                            if (communalTimerFlickerFix()) stableId else target.smartspaceTargetId,
+                        createdTimestampMillis = targetCreationTimes[stableId]!!,
+                        remoteViews = target.remoteViews!!,
+                    )
+                }
+                .also { newVal ->
+                    // Only log when value changes to avoid filling up the buffer.
+                    if (newVal != _timers.value) {
+                        logger.d({ "Smartspace timers updated: $str1" }) {
+                            str1 = newVal.toString()
+                        }
+                    }
+                }
     }
 
     override fun startListening() {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractor.kt
index 7453368..f7cd2ab 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractor.kt
@@ -16,10 +16,13 @@
 
 package com.android.systemui.communal.domain.interactor
 
+import android.annotation.SuppressLint
 import android.app.ActivityManager
+import android.app.DreamManager
 import com.android.systemui.common.usagestats.domain.UsageStatsInteractor
 import com.android.systemui.common.usagestats.shared.model.ActivityEventModel
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.log.LogBuffer
@@ -34,10 +37,12 @@
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.TimeoutCancellationException
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.takeWhile
+import kotlinx.coroutines.launch
 import kotlinx.coroutines.suspendCancellableCoroutine
 import kotlinx.coroutines.withTimeout
 
@@ -56,6 +61,8 @@
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val taskStackChangeListeners: TaskStackChangeListeners,
     private val usageStatsInteractor: UsageStatsInteractor,
+    private val dreamManager: DreamManager,
+    @Background private val bgScope: CoroutineScope,
     @CommunalLog logBuffer: LogBuffer,
 ) {
     private companion object {
@@ -127,13 +134,21 @@
      * Checks if an activity starts while on the glanceable hub and dismisses the keyguard if it
      * does. This can detect activities started due to broadcast trampolines from widgets.
      */
+    @SuppressLint("MissingPermission")
     suspend fun waitForActivityStartAndDismissKeyguard() {
         if (waitForActivityStartWhileOnHub()) {
             logger.d("Detected trampoline, requesting unlock")
             activityStarter.dismissKeyguardThenExecute(
-                /* action= */ { false },
+                /* action= */ {
+                    // Kill the dream when launching the trampoline activity. Right now the exit
+                    // animation stalls when tapping the battery widget, and the dream remains
+                    // visible until the transition hits some timeouts and gets cancelled.
+                    // TODO(b/362841648): remove once exit animation is fixed.
+                    bgScope.launch { dreamManager.stopDream() }
+                    false
+                },
                 /* cancel= */ null,
-                /* afterKeyguardGone= */ false
+                /* afterKeyguardGone= */ false,
             )
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt
index 28db3b8..f90f02a 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt
@@ -70,7 +70,14 @@
 ) {
     private val keyguardOccludedByApp: Flow<Boolean> =
         if (KeyguardWmStateRefactor.isEnabled) {
-            keyguardTransitionInteractor.currentKeyguardState.map { it == KeyguardState.OCCLUDED }
+            combine(
+                    keyguardTransitionInteractor.currentKeyguardState,
+                    communalSceneInteractor.isIdleOnCommunal,
+                    ::Pair,
+                )
+                .map { (currentState, isIdleOnCommunal) ->
+                    currentState == KeyguardState.OCCLUDED && !isIdleOnCommunal
+                }
         } else {
             combine(
                     keyguardInteractor.isKeyguardOccluded,
@@ -120,7 +127,7 @@
             // On fingerprint success when the screen is on and not dreaming, go to the home screen
             fingerprintUnlockSuccessEvents
                 .sample(
-                    combine(powerInteractor.isInteractive, keyguardInteractor.isDreaming, ::Pair),
+                    combine(powerInteractor.isInteractive, keyguardInteractor.isDreaming, ::Pair)
                 )
                 .collect { (interactive, dreaming) ->
                     if (interactive && !dreaming) {
@@ -148,7 +155,7 @@
                         }
                     },
                     /* cancel= */ null,
-                    /* afterKeyguardGone */ false
+                    /* afterKeyguardGone */ false,
                 )
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt
index 77c54ec..3992c3f 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt
@@ -39,6 +39,7 @@
 import kotlinx.coroutines.cancel
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
+import com.android.app.tracing.coroutines.createCoroutineTracingContext
 
 class HomeControlsDreamService
 @Inject
@@ -53,7 +54,7 @@
 ) : DreamService() {
 
     private val serviceJob = SupervisorJob()
-    private val serviceScope = CoroutineScope(bgDispatcher + serviceJob)
+    private val serviceScope = CoroutineScope(bgDispatcher + serviceJob + createCoroutineTracingContext("HomeControlsDreamService"))
     private val logger = DreamLogger(logBuffer, TAG)
     private lateinit var taskFragmentComponent: TaskFragmentComponent
     private val wakeLock: WakeLock by lazy {
diff --git a/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt b/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
index 7e2c9f8..4caf95b 100644
--- a/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.education.dagger
 
+import com.android.app.tracing.coroutines.createCoroutineTracingContext
 import com.android.systemui.CoreStartable
 import com.android.systemui.Flags
 import com.android.systemui.contextualeducation.GestureType
@@ -56,7 +57,7 @@
         fun provideEduDataStoreScope(
             @Background bgDispatcher: CoroutineDispatcher
         ): CoroutineScope {
-            return CoroutineScope(bgDispatcher + SupervisorJob())
+            return CoroutineScope(bgDispatcher + SupervisorJob() + createCoroutineTracingContext("EduDataStoreScope"))
         }
 
         @EduClock
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 0b8f741..cef9a4e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -17,6 +17,7 @@
 
 package com.android.systemui.keyguard.ui.preview
 
+import com.android.app.tracing.coroutines.createCoroutineTracingContext
 import android.app.WallpaperColors
 import android.content.BroadcastReceiver
 import android.content.Context
@@ -187,7 +188,7 @@
     private var themeStyle: Style? = null
 
     init {
-        coroutineScope = CoroutineScope(applicationScope.coroutineContext + Job())
+        coroutineScope = CoroutineScope(applicationScope.coroutineContext + Job() + createCoroutineTracingContext("KeyguardPreviewRenderer"))
         disposables += DisposableHandle { coroutineScope.cancel() }
         clockController.setFallbackWeatherData(WeatherData.getPlaceholderWeatherData())
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
index 4b62eab..0d55709 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
@@ -179,14 +179,6 @@
         } else {
             button(KeyguardQuickAffordancePosition.BOTTOM_START)
         }
-        .stateIn(
-            scope = applicationScope,
-            started = SharingStarted.Eagerly,
-            initialValue =
-                KeyguardQuickAffordanceViewModel(
-                    slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId()
-                ),
-        )
 
     /** An observable for the view-model of the "end button" quick affordance. */
     val endButton: Flow<KeyguardQuickAffordanceViewModel> =
@@ -200,14 +192,6 @@
         } else {
             button(KeyguardQuickAffordancePosition.BOTTOM_END)
         }
-        .stateIn(
-            scope = applicationScope,
-            started = SharingStarted.Eagerly,
-            initialValue =
-                KeyguardQuickAffordanceViewModel(
-                    slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId()
-                ),
-        )
 
     /**
      * Notifies that a slot with the given ID has been selected in the preview experience that is
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
index c2b5d98..5559698 100644
--- a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
@@ -26,7 +26,7 @@
 import androidx.lifecycle.LifecycleRegistry
 import androidx.lifecycle.lifecycleScope
 import com.android.app.tracing.coroutines.createCoroutineTracingContext
-import com.android.app.tracing.coroutines.launch
+import com.android.app.tracing.coroutines.traceCoroutine
 import com.android.systemui.Flags.coroutineTracing
 import com.android.systemui.util.Assert
 import com.android.systemui.util.Compile
@@ -45,6 +45,7 @@
 import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.launch
 
 /**
  * Runs the given [block] every time the [View] becomes attached (or immediately after calling this
@@ -137,7 +138,7 @@
 ): ViewLifecycleOwner {
     return ViewLifecycleOwner(view).apply {
         onCreate()
-        lifecycleScope.launch(nameForTrace, coroutineContext) { block(view) }
+        lifecycleScope.launch(coroutineContext) { traceCoroutine(nameForTrace) { block(view) } }
     }
 }
 
@@ -367,7 +368,8 @@
  * an extension function, and plumbing dagger-injected instances for static usage has little
  * benefit.
  */
-private val MAIN_DISPATCHER_SINGLETON = Dispatchers.Main + createCoroutineTracingContext()
+private val MAIN_DISPATCHER_SINGLETON =
+    Dispatchers.Main + createCoroutineTracingContext("RepeatWhenAttached")
 private const val DEFAULT_TRACE_NAME = "repeatWhenAttached"
 private const val CURRENT_CLASS_NAME = "com.android.systemui.lifecycle.RepeatWhenAttachedKt"
 private const val JAVA_ADAPTER_CLASS_NAME = "com.android.systemui.util.kotlin.JavaAdapterKt"
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
index 8bec46a..70ca824 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
@@ -545,6 +545,7 @@
 
     /** Bind this player view based on the data given. */
     public void bindPlayer(@NonNull MediaData data, String key) {
+        SceneContainerFlag.assertInLegacyMode();
         if (mMediaViewHolder == null) {
             return;
         }
@@ -638,10 +639,7 @@
         // to something which might impact the measurement
         // State refresh interferes with the translation animation, only run it if it's not running.
         if (!mMetadataAnimationHandler.isRunning()) {
-            // Don't refresh in scene framework, because it will calculate with invalid layout sizes
-            if (!SceneContainerFlag.isEnabled()) {
-                mMediaViewController.refreshState();
-            }
+            mMediaViewController.refreshState();
         }
 
         if (shouldPlayTurbulenceNoise()) {
@@ -907,11 +905,6 @@
         // Capture width & height from views in foreground for artwork scaling in background
         int width = mMediaViewHolder.getAlbumView().getMeasuredWidth();
         int height = mMediaViewHolder.getAlbumView().getMeasuredHeight();
-        if (SceneContainerFlag.isEnabled() && (width <= 0 || height <= 0)) {
-            // TODO(b/312714128): ensure we have a valid size before setting background
-            width = mMediaViewController.getWidthInSceneContainerPx();
-            height = mMediaViewController.getHeightInSceneContainerPx();
-        }
 
         final int finalWidth = width;
         final int finalHeight = height;
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
index 6440205..544dbdd 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.mediaprojection.appselector
 
+import com.android.app.tracing.coroutines.createCoroutineTracingContext
 import android.app.Activity
 import android.content.ComponentName
 import android.content.Context
@@ -133,7 +134,7 @@
         @MediaProjectionAppSelector
         @MediaProjectionAppSelectorScope
         fun provideCoroutineScope(@Application applicationScope: CoroutineScope): CoroutineScope =
-            CoroutineScope(applicationScope.coroutineContext + SupervisorJob())
+            CoroutineScope(applicationScope.coroutineContext + SupervisorJob() + createCoroutineTracingContext("MediaProjectionAppSelectorScope"))
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index b3c697e..1216a88 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -551,6 +551,12 @@
     }
 
     @Override
+    public void appTransitionStarting(int displayId, long startTime, long duration,
+            boolean forced) {
+        appTransitionPending(false);
+    }
+
+    @Override
     public void appTransitionCancelled(int displayId) {
         appTransitionPending(false);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileCoroutineScopeFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileCoroutineScopeFactory.kt
index d0437a7..b8f4ab4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileCoroutineScopeFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileCoroutineScopeFactory.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.tiles.base.viewmodel
 
+import com.android.app.tracing.coroutines.createCoroutineTracingContext
 import com.android.systemui.dagger.qualifiers.Application
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -27,5 +28,5 @@
 constructor(@Application private val applicationScope: CoroutineScope) {
 
     fun create(): CoroutineScope =
-        CoroutineScope(applicationScope.coroutineContext + SupervisorJob())
+        CoroutineScope(applicationScope.coroutineContext + SupervisorJob() + createCoroutineTracingContext("QSTileScope"))
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogManager.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogManager.kt
index 246fe38..ae56c2a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogManager.kt
@@ -15,6 +15,7 @@
  */
 package com.android.systemui.qs.tiles.dialog
 
+import com.android.app.tracing.coroutines.createCoroutineTracingContext
 import android.util.Log
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.systemui.animation.DialogCuj
@@ -62,7 +63,7 @@
             }
             return
         } else {
-            coroutineScope = CoroutineScope(bgDispatcher)
+            coroutineScope = CoroutineScope(bgDispatcher + createCoroutineTracingContext("InternetDialogScope"))
             dialog =
                 dialogFactory
                     .create(aboveStatusBar, canConfigMobileData, canConfigWifi, coroutineScope)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileUserActionInteractor.kt
index cca947f..ac75932 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileUserActionInteractor.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.tiles.impl.location.domain.interactor
 
+import com.android.app.tracing.coroutines.createCoroutineTracingContext
 import android.content.Intent
 import android.provider.Settings
 import com.android.systemui.dagger.qualifiers.Application
@@ -52,7 +53,7 @@
                     val wasEnabled: Boolean = input.data.isEnabled
                     if (keyguardController.isMethodSecure() && keyguardController.isShowing()) {
                         activityStarter.postQSRunnableDismissingKeyguard {
-                            CoroutineScope(applicationScope.coroutineContext).launch {
+                            CoroutineScope(applicationScope.coroutineContext + createCoroutineTracingContext("LocationTileScope")).launch {
                                 locationController.setLocationEnabled(!wasEnabled)
                             }
                         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegate.kt
index b25c61c..468e180 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegate.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.tiles.impl.saver.domain
 
+import com.android.app.tracing.coroutines.createCoroutineTracingContext
 import android.content.Context
 import android.content.DialogInterface
 import android.content.SharedPreferences
@@ -44,7 +45,7 @@
             setTitle(R.string.data_saver_enable_title)
             setMessage(R.string.data_saver_description)
             setPositiveButton(R.string.data_saver_enable_button) { _: DialogInterface?, _ ->
-                CoroutineScope(backgroundContext).launch {
+                CoroutineScope(backgroundContext + createCoroutineTracingContext("DataSaverDialogScope")).launch {
                     dataSaverController.setDataSaverEnabled(true)
                 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 560028c..7b6a2cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -444,11 +444,9 @@
                 if (onFinishedRunnable != null) {
                     onFinishedRunnable.run();
                 }
-                if (mRunWithoutInterruptions) {
-                    enableAppearDrawing(false);
-                }
 
                 // We need to reset the View state, even if the animation was cancelled
+                enableAppearDrawing(false);
                 onAppearAnimationFinished(isAppearing);
 
                 if (mRunWithoutInterruptions) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
index deae576..bad6f80 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
 
+import com.android.app.tracing.coroutines.createCoroutineTracingContext
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
@@ -29,6 +30,7 @@
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
+import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.cancel
@@ -114,7 +116,7 @@
 
     private fun createViewModel(subId: Int): Pair<MobileIconViewModel, CoroutineScope> {
         // Create a child scope so we can cancel it
-        val vmScope = scope.createChildScope()
+        val vmScope = scope.createChildScope(createCoroutineTracingContext("MobileIconViewModel"))
         val vm =
             MobileIconViewModel(
                 subId,
@@ -128,8 +130,8 @@
         return Pair(vm, vmScope)
     }
 
-    private fun CoroutineScope.createChildScope() =
-        CoroutineScope(coroutineContext + Job(coroutineContext[Job]))
+    private fun CoroutineScope.createChildScope(extraContext: CoroutineContext) =
+        CoroutineScope(coroutineContext + Job(coroutineContext[Job]) + extraContext)
 
     private fun invalidateCaches(subIds: List<Int>) {
         reuseCache.keys
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/GlobalCoroutinesModule.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/GlobalCoroutinesModule.kt
index 8ecf250..2af84c7 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/GlobalCoroutinesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/GlobalCoroutinesModule.kt
@@ -37,7 +37,7 @@
     @Application
     fun applicationScope(
         @Main dispatcherContext: CoroutineContext,
-    ): CoroutineScope = CoroutineScope(dispatcherContext)
+    ): CoroutineScope = CoroutineScope(dispatcherContext + createCoroutineTracingContext("ApplicationScope"))
 
     @Provides
     @Singleton
@@ -51,15 +51,7 @@
     @Provides
     @Singleton
     @Main
-    fun mainCoroutineContext(@Tracing tracingCoroutineContext: CoroutineContext): CoroutineContext {
-        return Dispatchers.Main.immediate + tracingCoroutineContext
-    }
-
-    @OptIn(ExperimentalCoroutinesApi::class)
-    @Provides
-    @Tracing
-    @Singleton
-    fun tracingCoroutineContext(): CoroutineContext {
-        return createCoroutineTracingContext()
+    fun mainCoroutineContext(): CoroutineContext {
+        return Dispatchers.Main.immediate
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
index a03221e..3c06828 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
@@ -91,10 +91,9 @@
     @Background
     @SysUISingleton
     fun bgCoroutineContext(
-        @Tracing tracingCoroutineContext: CoroutineContext,
         @Background bgCoroutineDispatcher: CoroutineDispatcher,
     ): CoroutineContext {
-        return bgCoroutineDispatcher + tracingCoroutineContext
+        return bgCoroutineDispatcher
     }
 
     /** Coroutine dispatcher for background operations on for UI. */
@@ -112,9 +111,8 @@
     @UiBackground
     @SysUISingleton
     fun uiBgCoroutineContext(
-        @Tracing tracingCoroutineContext: CoroutineContext,
         @UiBackground uiBgCoroutineDispatcher: CoroutineDispatcher,
     ): CoroutineContext {
-        return uiBgCoroutineDispatcher + tracingCoroutineContext
+        return uiBgCoroutineDispatcher
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
index 82f41a7..4d9aaa6 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
@@ -15,6 +15,7 @@
  */
 package com.android.systemui.util.settings
 
+import com.android.app.tracing.coroutines.createCoroutineTracingContext
 import android.annotation.UserIdInt
 import android.content.ContentResolver
 import android.database.ContentObserver
@@ -93,7 +94,7 @@
      */
     @AnyThread
     fun registerContentObserverAsync(name: String, settingsObserver: ContentObserver) =
-        CoroutineScope(backgroundDispatcher).launch {
+        CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-A")).launch {
             registerContentObserverSync(getUriFor(name), settingsObserver)
         }
 
@@ -110,7 +111,7 @@
         settingsObserver: ContentObserver,
         @WorkerThread registered: Runnable
     ) =
-        CoroutineScope(backgroundDispatcher).launch {
+        CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-B")).launch {
             registerContentObserverSync(getUriFor(name), settingsObserver)
             registered.run()
         }
@@ -143,7 +144,7 @@
      */
     @AnyThread
     fun registerContentObserverAsync(uri: Uri, settingsObserver: ContentObserver) =
-        CoroutineScope(backgroundDispatcher).launch {
+        CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-C")).launch {
             registerContentObserverSync(uri, settingsObserver)
         }
 
@@ -160,7 +161,7 @@
         settingsObserver: ContentObserver,
         @WorkerThread registered: Runnable
     ) =
-        CoroutineScope(backgroundDispatcher).launch {
+        CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-D")).launch {
             registerContentObserverSync(uri, settingsObserver)
             registered.run()
         }
@@ -205,7 +206,7 @@
         notifyForDescendants: Boolean,
         settingsObserver: ContentObserver
     ) =
-        CoroutineScope(backgroundDispatcher).launch {
+        CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-E")).launch {
             registerContentObserverSync(getUriFor(name), notifyForDescendants, settingsObserver)
         }
 
@@ -223,7 +224,7 @@
         settingsObserver: ContentObserver,
         @WorkerThread registered: Runnable
     ) =
-        CoroutineScope(backgroundDispatcher).launch {
+        CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-F")).launch {
             registerContentObserverSync(getUriFor(name), notifyForDescendants, settingsObserver)
             registered.run()
         }
@@ -274,7 +275,7 @@
         notifyForDescendants: Boolean,
         settingsObserver: ContentObserver
     ) =
-        CoroutineScope(backgroundDispatcher).launch {
+        CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-G")).launch {
             registerContentObserverSync(uri, notifyForDescendants, settingsObserver)
         }
 
@@ -292,7 +293,7 @@
         settingsObserver: ContentObserver,
         @WorkerThread registered: Runnable
     ) =
-        CoroutineScope(backgroundDispatcher).launch {
+        CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-H")).launch {
             registerContentObserverSync(uri, notifyForDescendants, settingsObserver)
             registered.run()
         }
@@ -329,7 +330,7 @@
      */
     @AnyThread
     fun unregisterContentObserverAsync(settingsObserver: ContentObserver) =
-        CoroutineScope(backgroundDispatcher).launch { unregisterContentObserver(settingsObserver) }
+        CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-I")).launch { unregisterContentObserver(settingsObserver) }
 
     /**
      * Look up a name in the database.
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
index 8e3b813..c820c07 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
@@ -15,6 +15,7 @@
  */
 package com.android.systemui.util.settings
 
+import com.android.app.tracing.coroutines.createCoroutineTracingContext
 import android.annotation.UserIdInt
 import android.annotation.WorkerThread
 import android.content.ContentResolver
@@ -78,7 +79,7 @@
     }
 
     override fun registerContentObserverAsync(uri: Uri, settingsObserver: ContentObserver) =
-        CoroutineScope(backgroundDispatcher).launch {
+        CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-A")).launch {
             registerContentObserverForUserSync(uri, settingsObserver, userId)
         }
 
@@ -112,7 +113,7 @@
         notifyForDescendants: Boolean,
         settingsObserver: ContentObserver
     ) =
-        CoroutineScope(backgroundDispatcher).launch {
+        CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-B")).launch {
             registerContentObserverForUserSync(uri, notifyForDescendants, settingsObserver, userId)
         }
 
@@ -157,7 +158,7 @@
         settingsObserver: ContentObserver,
         userHandle: Int
     ) =
-        CoroutineScope(backgroundDispatcher).launch {
+        CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-C")).launch {
             registerContentObserverForUserSync(getUriFor(name), settingsObserver, userHandle)
         }
 
@@ -198,7 +199,7 @@
         settingsObserver: ContentObserver,
         userHandle: Int
     ) =
-        CoroutineScope(backgroundDispatcher).launch {
+        CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-D")).launch {
             registerContentObserverForUserSync(uri, settingsObserver, userHandle)
         }
 
@@ -215,7 +216,7 @@
         userHandle: Int,
         @WorkerThread registered: Runnable
     ) =
-        CoroutineScope(backgroundDispatcher).launch {
+        CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-E")).launch {
             registerContentObserverForUserSync(uri, settingsObserver, userHandle)
             registered.run()
         }
@@ -274,7 +275,7 @@
         settingsObserver: ContentObserver,
         userHandle: Int
     ) {
-        CoroutineScope(backgroundDispatcher).launch {
+        CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-F")).launch {
             registerContentObserverForUserSync(
                 getUriFor(name),
                 notifyForDescendants,
@@ -338,7 +339,7 @@
         settingsObserver: ContentObserver,
         userHandle: Int
     ) =
-        CoroutineScope(backgroundDispatcher).launch {
+        CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-G")).launch {
             registerContentObserverForUserSync(
                 uri,
                 notifyForDescendants,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorKosmos.kt
index 8124224..3d41362 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorKosmos.kt
@@ -16,9 +16,11 @@
 
 package com.android.systemui.communal.domain.interactor
 
+import android.service.dream.dreamManager
 import com.android.systemui.common.usagestats.domain.interactor.usageStatsInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.plugins.activityStarter
 import com.android.systemui.shared.system.taskStackChangeListeners
@@ -32,6 +34,8 @@
             keyguardTransitionInteractor = keyguardTransitionInteractor,
             taskStackChangeListeners = taskStackChangeListeners,
             usageStatsInteractor = usageStatsInteractor,
+            dreamManager = dreamManager,
+            bgScope = applicationCoroutineScope,
             logBuffer = logcatLogBuffer("WidgetTrampolineInteractor"),
         )
     }
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java
index 1f98334..c3b7087 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java
@@ -16,15 +16,7 @@
 
 package com.android.server.appfunctions;
 
-import android.annotation.NonNull;
-import android.os.UserHandle;
-import android.util.SparseArray;
-
-import com.android.internal.annotations.GuardedBy;
-
 import java.util.concurrent.Executor;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
@@ -41,50 +33,5 @@
                     /* unit= */ TimeUnit.SECONDS,
                     /* workQueue= */ new LinkedBlockingQueue<>());
 
-    /** A map of per-user executors for queued work. */
-    @GuardedBy("sLock")
-    private static final SparseArray<ExecutorService> mPerUserExecutorsLocked = new SparseArray<>();
-
-    private static final Object sLock = new Object();
-
-    /**
-     * Returns a per-user executor for queued metadata sync request.
-     *
-     * <p>The work submitted to these executor (Sync request) needs to be synchronous per user hence
-     * the use of a single thread.
-     *
-     * <p>Note: Use a different executor if not calling {@code submitSyncRequest} on a {@code
-     * MetadataSyncAdapter}.
-     */
-    // TODO(b/357551503): Restrict the scope of this executor to the MetadataSyncAdapter itself.
-    public static ExecutorService getPerUserSyncExecutor(@NonNull UserHandle user) {
-        synchronized (sLock) {
-            ExecutorService executor = mPerUserExecutorsLocked.get(user.getIdentifier(), null);
-            if (executor == null) {
-                executor = Executors.newSingleThreadExecutor();
-                mPerUserExecutorsLocked.put(user.getIdentifier(), executor);
-            }
-            return executor;
-        }
-    }
-
-    /**
-     * Shuts down and removes the per-user executor for queued work.
-     *
-     * <p>This should be called when the user is removed.
-     */
-    public static void shutDownAndRemoveUserExecutor(@NonNull UserHandle user)
-            throws InterruptedException {
-        ExecutorService executor;
-        synchronized (sLock) {
-            executor = mPerUserExecutorsLocked.get(user.getIdentifier());
-            mPerUserExecutorsLocked.remove(user.getIdentifier());
-        }
-        if (executor != null) {
-            executor.shutdown();
-            var unused = executor.awaitTermination(30, TimeUnit.SECONDS);
-        }
-    }
-
     private AppFunctionExecutors() {}
 }
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
index b4713d9..1e723b5 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
@@ -95,12 +95,7 @@
     public void onUserStopping(@NonNull TargetUser user) {
         Objects.requireNonNull(user);
 
-        try {
-            AppFunctionExecutors.shutDownAndRemoveUserExecutor(user.getUserHandle());
-            MetadataSyncPerUser.removeUserSyncAdapter(user.getUserHandle());
-        } catch (InterruptedException e) {
-            Slog.e(TAG, "Unable to remove data for: " + user.getUserHandle(), e);
-        }
+        MetadataSyncPerUser.removeUserSyncAdapter(user.getUserHandle());
     }
 
     @Override
diff --git a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
index e29b6e4..d84b205 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
@@ -42,6 +42,7 @@
 import android.util.ArraySet;
 import android.util.Slog;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.infra.AndroidFuture;
 import com.android.server.appfunctions.FutureAppSearchSession.FutureSearchResults;
@@ -53,7 +54,9 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
 
 /**
  * This class implements helper methods for synchronously interacting with AppSearch while
@@ -63,9 +66,15 @@
  */
 public class MetadataSyncAdapter {
     private static final String TAG = MetadataSyncAdapter.class.getSimpleName();
-    private final Executor mSyncExecutor;
+
+    private final ExecutorService mExecutor;
+
     private final AppSearchManager mAppSearchManager;
     private final PackageManager mPackageManager;
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private Future<?> mCurrentSyncTask;
 
     // Hidden constants in {@link SetSchemaRequest} that restricts runtime metadata visibility
     // by permissions.
@@ -73,12 +82,10 @@
     public static final int EXECUTE_APP_FUNCTIONS_TRUSTED = 10;
 
     public MetadataSyncAdapter(
-            @NonNull Executor syncExecutor,
-            @NonNull PackageManager packageManager,
-            @NonNull AppSearchManager appSearchManager) {
-        mSyncExecutor = Objects.requireNonNull(syncExecutor);
+            @NonNull PackageManager packageManager, @NonNull AppSearchManager appSearchManager) {
         mPackageManager = Objects.requireNonNull(packageManager);
         mAppSearchManager = Objects.requireNonNull(appSearchManager);
+        mExecutor = Executors.newSingleThreadExecutor();
     }
 
     /**
@@ -97,7 +104,7 @@
                                 AppFunctionRuntimeMetadata.APP_FUNCTION_RUNTIME_METADATA_DB)
                         .build();
         AndroidFuture<Boolean> settableSyncStatus = new AndroidFuture<>();
-        mSyncExecutor.execute(
+        Runnable runnable =
                 () -> {
                     try (FutureAppSearchSession staticMetadataSearchSession =
                                     new FutureAppSearchSessionImpl(
@@ -117,10 +124,23 @@
                     } catch (Exception ex) {
                         settableSyncStatus.completeExceptionally(ex);
                     }
-                });
+                };
+
+        synchronized (mLock) {
+            if (mCurrentSyncTask != null && !mCurrentSyncTask.isDone()) {
+                var unused = mCurrentSyncTask.cancel(false);
+            }
+            mCurrentSyncTask = mExecutor.submit(runnable);
+        }
+
         return settableSyncStatus;
     }
 
+    /** This method shuts down the {@link MetadataSyncAdapter} scheduler. */
+    public void shutDown() {
+        mExecutor.shutdown();
+    }
+
     @WorkerThread
     @VisibleForTesting
     void trySyncAppFunctionMetadataBlocking(
diff --git a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java
index f421527..e933ec1 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java
@@ -55,10 +55,7 @@
                 PackageManager perUserPackageManager = userContext.getPackageManager();
                 if (perUserAppSearchManager != null) {
                     metadataSyncAdapter =
-                            new MetadataSyncAdapter(
-                                    AppFunctionExecutors.getPerUserSyncExecutor(user),
-                                    perUserPackageManager,
-                                    perUserAppSearchManager);
+                            new MetadataSyncAdapter(perUserPackageManager, perUserAppSearchManager);
                     sPerUserMetadataSyncAdapter.put(user.getIdentifier(), metadataSyncAdapter);
                     return metadataSyncAdapter;
                 }
@@ -74,7 +71,12 @@
      */
     public static void removeUserSyncAdapter(UserHandle user) {
         synchronized (sLock) {
-            sPerUserMetadataSyncAdapter.remove(user.getIdentifier());
+            MetadataSyncAdapter metadataSyncAdapter =
+                    sPerUserMetadataSyncAdapter.get(user.getIdentifier(), null);
+            if (metadataSyncAdapter != null) {
+                metadataSyncAdapter.shutDown();
+                sPerUserMetadataSyncAdapter.remove(user.getIdentifier());
+            }
         }
     }
 }
diff --git a/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
index 46d60f9..0c54720 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
@@ -454,10 +454,10 @@
             @NonNull Associations associations)
             throws IOException {
         final XmlSerializer serializer = parent.startTag(null, XML_TAG_ASSOCIATIONS);
+        writeIntAttribute(serializer, XML_ATTR_MAX_ID, associations.getMaxId());
         for (AssociationInfo association : associations.getAssociations()) {
             writeAssociation(serializer, association);
         }
-        writeIntAttribute(serializer, XML_ATTR_MAX_ID, associations.getMaxId());
         serializer.endTag(null, XML_TAG_ASSOCIATIONS);
     }
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 871c320..414a4e6 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -15304,10 +15304,8 @@
             }
 
             psr.setReportedForegroundServiceTypes(fgServiceTypes);
-            ProcessChangeItem item = mProcessList.enqueueProcessChangeItemLocked(
-                    proc.getPid(), proc.info.uid);
-            item.changes |= ProcessChangeItem.CHANGE_FOREGROUND_SERVICES;
-            item.foregroundServiceTypes = fgServiceTypes;
+            mProcessList.enqueueProcessChangeItemLocked(proc.getPid(), proc.info.uid,
+                    ProcessChangeItem.CHANGE_FOREGROUND_SERVICES, fgServiceTypes);
         }
         if (oomAdj) {
             updateOomAdjLocked(proc, OOM_ADJ_REASON_UI_VISIBILITY);
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 22ec790..78a0a11 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -3617,14 +3617,12 @@
         if (changes != 0) {
             if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
                     "Changes in " + app + ": " + changes);
-            ActivityManagerService.ProcessChangeItem item =
-                    mProcessList.enqueueProcessChangeItemLocked(app.getPid(), app.info.uid);
-            item.changes |= changes;
-            item.foregroundActivities = state.hasRepForegroundActivities();
+            mProcessList.enqueueProcessChangeItemLocked(app.getPid(), app.info.uid,
+                    changes, state.hasRepForegroundActivities());
             if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
-                    "Item " + Integer.toHexString(System.identityHashCode(item))
-                            + " " + app.toShortString() + ": changes=" + item.changes
-                            + " foreground=" + item.foregroundActivities
+                    "Enqueued process change item for "
+                            + app.toShortString() + ": changes=" + changes
+                            + " foreground=" + state.hasRepForegroundActivities()
                             + " type=" + state.getAdjType() + " source=" + state.getAdjSource()
                             + " target=" + state.getAdjTarget());
         }
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 00250b4..a93ae72 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -4998,53 +4998,70 @@
     }
 
     @GuardedBy("mService")
-    ProcessChangeItem enqueueProcessChangeItemLocked(int pid, int uid) {
+    void enqueueProcessChangeItemLocked(int pid, int uid, int changes, int foregroundServicetypes) {
         synchronized (mProcessChangeLock) {
-            int i = mPendingProcessChanges.size() - 1;
-            ActivityManagerService.ProcessChangeItem item = null;
-            while (i >= 0) {
-                item = mPendingProcessChanges.get(i);
-                if (item.pid == pid) {
-                    if (DEBUG_PROCESS_OBSERVERS) {
-                        Slog.i(TAG_PROCESS_OBSERVERS, "Re-using existing item: " + item);
-                    }
-                    break;
-                }
-                i--;
-            }
-
-            if (i < 0) {
-                // No existing item in pending changes; need a new one.
-                final int num = mAvailProcessChanges.size();
-                if (num > 0) {
-                    item = mAvailProcessChanges.remove(num - 1);
-                    if (DEBUG_PROCESS_OBSERVERS) {
-                        Slog.i(TAG_PROCESS_OBSERVERS, "Retrieving available item: " + item);
-                    }
-                } else {
-                    item = new ActivityManagerService.ProcessChangeItem();
-                    if (DEBUG_PROCESS_OBSERVERS) {
-                        Slog.i(TAG_PROCESS_OBSERVERS, "Allocating new item: " + item);
-                    }
-                }
-                item.changes = 0;
-                item.pid = pid;
-                item.uid = uid;
-                if (mPendingProcessChanges.size() == 0) {
-                    if (DEBUG_PROCESS_OBSERVERS) {
-                        Slog.i(TAG_PROCESS_OBSERVERS, "*** Enqueueing dispatch processes changed!");
-                    }
-                    mService.mUiHandler.obtainMessage(DISPATCH_PROCESSES_CHANGED_UI_MSG)
-                            .sendToTarget();
-                }
-                mPendingProcessChanges.add(item);
-            }
-
-            return item;
+            final ProcessChangeItem item = enqueueProcessChangeItemLocked(pid, uid);
+            item.changes |= changes;
+            item.foregroundServiceTypes = foregroundServicetypes;
         }
     }
 
     @GuardedBy("mService")
+    void enqueueProcessChangeItemLocked(int pid, int uid, int changes,
+            boolean hasForegroundActivities) {
+        synchronized (mProcessChangeLock) {
+            final ProcessChangeItem item = enqueueProcessChangeItemLocked(pid, uid);
+            item.changes |= changes;
+            item.foregroundActivities = hasForegroundActivities;
+        }
+    }
+
+    @GuardedBy({"mService", "mProcessChangeLock"})
+    private ProcessChangeItem enqueueProcessChangeItemLocked(int pid, int uid) {
+        int i = mPendingProcessChanges.size() - 1;
+        ActivityManagerService.ProcessChangeItem item = null;
+        while (i >= 0) {
+            item = mPendingProcessChanges.get(i);
+            if (item.pid == pid) {
+                if (DEBUG_PROCESS_OBSERVERS) {
+                    Slog.i(TAG_PROCESS_OBSERVERS, "Re-using existing item: " + item);
+                }
+                break;
+            }
+            i--;
+        }
+
+        if (i < 0) {
+            // No existing item in pending changes; need a new one.
+            final int num = mAvailProcessChanges.size();
+            if (num > 0) {
+                item = mAvailProcessChanges.remove(num - 1);
+                if (DEBUG_PROCESS_OBSERVERS) {
+                    Slog.i(TAG_PROCESS_OBSERVERS, "Retrieving available item: " + item);
+                }
+            } else {
+                item = new ActivityManagerService.ProcessChangeItem();
+                if (DEBUG_PROCESS_OBSERVERS) {
+                    Slog.i(TAG_PROCESS_OBSERVERS, "Allocating new item: " + item);
+                }
+            }
+            item.changes = 0;
+            item.pid = pid;
+            item.uid = uid;
+            if (mPendingProcessChanges.size() == 0) {
+                if (DEBUG_PROCESS_OBSERVERS) {
+                    Slog.i(TAG_PROCESS_OBSERVERS, "*** Enqueueing dispatch processes changed!");
+                }
+                mService.mUiHandler.obtainMessage(DISPATCH_PROCESSES_CHANGED_UI_MSG)
+                        .sendToTarget();
+            }
+            mPendingProcessChanges.add(item);
+        }
+
+        return item;
+    }
+
+    @GuardedBy("mService")
     void scheduleDispatchProcessDiedLocked(int pid, int uid) {
         synchronized (mProcessChangeLock) {
             for (int i = mPendingProcessChanges.size() - 1; i >= 0; i--) {
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 6ae6f3d..6af4be5 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -619,7 +619,7 @@
             this.op = op;
             this.uid = uid;
             this.uidState = uidState;
-            this.packageName = packageName;
+            this.packageName = packageName.intern();
             // We keep an invariant that the persistent device will always have an entry in
             // mDeviceAttributedOps.
             mDeviceAttributedOps.put(PERSISTENT_DEVICE_ID_DEFAULT,
@@ -1031,7 +1031,7 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
-            String pkgName = intent.getData().getEncodedSchemeSpecificPart();
+            String pkgName = intent.getData().getEncodedSchemeSpecificPart().intern();
             int uid = intent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID);
 
             if (action.equals(ACTION_PACKAGE_ADDED)
@@ -1235,7 +1235,7 @@
         Ops ops = uidState.pkgOps.get(packageName);
         if (ops == null) {
             ops = new Ops(packageName, uidState);
-            uidState.pkgOps.put(packageName, ops);
+            uidState.pkgOps.put(packageName.intern(), ops);
         }
 
         SparseIntArray packageModes =
@@ -4739,7 +4739,7 @@
                 return null;
             }
             ops = new Ops(packageName, uidState);
-            uidState.pkgOps.put(packageName, ops);
+            uidState.pkgOps.put(packageName.intern(), ops);
         }
 
         if (edit) {
@@ -5076,7 +5076,7 @@
         Ops ops = uidState.pkgOps.get(pkgName);
         if (ops == null) {
             ops = new Ops(pkgName, uidState);
-            uidState.pkgOps.put(pkgName, ops);
+            uidState.pkgOps.put(pkgName.intern(), ops);
         }
         ops.put(op.op, op);
     }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 9e11081..df69afe 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -10809,7 +10809,8 @@
             //TODO move inside HardeningEnforcer after refactor that moves permission checks
             //     in the blockFocusMethod
             if (permissionOverridesCheck) {
-                mHardeningEnforcer.metricsLogFocusReq(/*blocked*/false, focusReqType, uid);
+                mHardeningEnforcer.metricsLogFocusReq(/*blocked*/ false, focusReqType, uid,
+                        /*unblockedBySdk*/ false);
             }
             if (!permissionOverridesCheck && mHardeningEnforcer.blockFocusMethod(uid,
                     HardeningEnforcer.METHOD_AUDIO_MANAGER_REQUEST_AUDIO_FOCUS,
diff --git a/services/core/java/com/android/server/audio/HardeningEnforcer.java b/services/core/java/com/android/server/audio/HardeningEnforcer.java
index faeba5d..6611110 100644
--- a/services/core/java/com/android/server/audio/HardeningEnforcer.java
+++ b/services/core/java/com/android/server/audio/HardeningEnforcer.java
@@ -168,6 +168,8 @@
         }
 
         boolean blocked = true;
+        // indicates the focus request was not blocked because of the SDK version
+        boolean unblockedBySdk = false;
         if (noteOp(AppOpsManager.OP_TAKE_AUDIO_FOCUS, callingUid, packageName, attributionTag)) {
             if (DEBUG) {
                 Slog.i(TAG, "blockFocusMethod pack:" + packageName + " NOT blocking");
@@ -179,9 +181,10 @@
                         + targetSdk);
             }
             blocked = false;
+            unblockedBySdk = true;
         }
 
-        metricsLogFocusReq(blocked, focusReqType, callingUid);
+        metricsLogFocusReq(blocked, focusReqType, callingUid, unblockedBySdk);
 
         if (!blocked) {
             return false;
@@ -195,7 +198,16 @@
         return true;
     }
 
-    /*package*/ void metricsLogFocusReq(boolean blocked, int focusReq, int callingUid) {
+    /**
+     * Log metrics for the focus request
+     * @param blocked true if the call blocked
+     * @param focusReq the type of focus request
+     * @param callingUid the UID of the caller
+     * @param unblockedBySdk if blocked is false,
+     *                       true indicates it was unblocked thanks to an older SDK
+     */
+    /*package*/ void metricsLogFocusReq(boolean blocked, int focusReq, int callingUid,
+            boolean unblockedBySdk) {
         final String metricId = blocked ? METRIC_COUNTERS_FOCUS_DENIAL.get(focusReq)
                 : METRIC_COUNTERS_FOCUS_GRANT.get(focusReq);
         if (TextUtils.isEmpty(metricId)) {
@@ -204,6 +216,12 @@
         }
         try {
             Counter.logIncrementWithUid(metricId, callingUid);
+            if (!blocked && unblockedBySdk) {
+                // additional metric to capture focus requests that are currently granted
+                // because the app is on an older SDK, but would have been blocked otherwise
+                Counter.logIncrementWithUid(
+                        "media_audio.value_audio_focus_grant_hardening_waived_by_sdk", callingUid);
+            }
         } catch (Exception e) {
             Slog.e(TAG, "Counter error metricId:" + metricId + " for focus req:" + focusReq
                     + " from uid:" + callingUid, e);
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index e7fd8f7..ae33b83 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -571,6 +571,10 @@
     private final DisplayNotificationManager mDisplayNotificationManager;
     private final ExternalDisplayStatsService mExternalDisplayStatsService;
 
+    // Manages the relative placement of extended displays
+    @Nullable
+    private final DisplayTopologyCoordinator mDisplayTopologyCoordinator;
+
     /**
      * Applications use {@link android.view.Display#getRefreshRate} and
      * {@link android.view.Display.Mode#getRefreshRate} to know what is the display refresh rate.
@@ -644,6 +648,11 @@
         mDisplayNotificationManager = new DisplayNotificationManager(mFlags, mContext,
                 mExternalDisplayStatsService);
         mExternalDisplayPolicy = new ExternalDisplayPolicy(new ExternalDisplayPolicyInjector());
+        if (mFlags.isDisplayTopologyEnabled()) {
+            mDisplayTopologyCoordinator = new DisplayTopologyCoordinator();
+        } else {
+            mDisplayTopologyCoordinator = null;
+        }
     }
 
     public void setupSchedulerPolicies() {
@@ -3474,9 +3483,13 @@
             mSmallAreaDetectionController.dump(pw);
         }
 
+        if (mDisplayTopologyCoordinator != null) {
+            pw.println();
+            mDisplayTopologyCoordinator.dump(pw);
+        }
+
         pw.println();
         mFlags.dump(pw);
-
     }
 
     private static float[] getFloatArray(TypedArray array) {
diff --git a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java
new file mode 100644
index 0000000..631f147
--- /dev/null
+++ b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import android.annotation.Nullable;
+import android.util.IndentingPrintWriter;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class manages the relative placement (topology) of extended displays. It is responsible for
+ * updating and persisting the topology.
+ */
+class DisplayTopologyCoordinator {
+
+    /**
+     * The topology tree
+     */
+    @Nullable
+    private TopologyTreeNode mRoot;
+
+    /**
+     * The logical display ID of the primary display that will show certain UI elements.
+     * This is not necessarily the same as the default display.
+     */
+    private int mPrimaryDisplayId;
+
+    /**
+     * Print the object's state and debug information into the given stream.
+     * @param pw The stream to dump information to.
+     */
+    public void dump(PrintWriter pw) {
+        pw.println("DisplayTopologyCoordinator:");
+        pw.println("--------------------");
+        IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
+        ipw.increaseIndent();
+
+        ipw.println("mPrimaryDisplayId: " + mPrimaryDisplayId);
+
+        ipw.println("Topology tree:");
+        if (mRoot != null) {
+            ipw.increaseIndent();
+            mRoot.dump(ipw);
+            ipw.decreaseIndent();
+        }
+    }
+
+    private static class TopologyTreeNode {
+
+        /**
+         * The logical display ID
+         */
+        private int mDisplayId;
+
+        private final List<TopologyTreeNode> mChildren = new ArrayList<>();
+
+        /**
+         * The position of this display relative to its parent.
+         */
+        private Position mPosition;
+
+        /**
+         * The distance from the top edge of the parent display to the top edge of this display (in
+         * case of POSITION_LEFT or POSITION_RIGHT) or from the left edge of the parent display
+         * to the left edge of this display (in case of POSITION_TOP or POSITION_BOTTOM). The unit
+         * used is density-independent pixels (dp).
+         */
+        private double mOffset;
+
+        /**
+         * Print the object's state and debug information into the given stream.
+         * @param ipw The stream to dump information to.
+         */
+        void dump(IndentingPrintWriter ipw) {
+            ipw.println("Display {id=" + mDisplayId + ", position=" + mPosition
+                    + ", offset=" + mOffset + "}");
+            ipw.increaseIndent();
+            for (TopologyTreeNode child : mChildren) {
+                child.dump(ipw);
+            }
+            ipw.decreaseIndent();
+        }
+
+        private enum Position {
+            POSITION_LEFT, POSITION_TOP, POSITION_RIGHT, POSITION_BOTTOM
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index f600e7f..df66893 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -69,6 +69,10 @@
             Flags.FLAG_ENABLE_MODE_LIMIT_FOR_EXTERNAL_DISPLAY,
             Flags::enableModeLimitForExternalDisplay);
 
+    private final FlagState mDisplayTopology = new FlagState(
+            Flags.FLAG_DISPLAY_TOPOLOGY,
+            Flags::displayTopology);
+
     private final FlagState mConnectedDisplayErrorHandlingFlagState = new FlagState(
             Flags.FLAG_ENABLE_CONNECTED_DISPLAY_ERROR_HANDLING,
             Flags::enableConnectedDisplayErrorHandling);
@@ -266,6 +270,10 @@
         return mExternalDisplayLimitModeState.isEnabled();
     }
 
+    public boolean isDisplayTopologyEnabled() {
+        return mDisplayTopology.isEnabled();
+    }
+
     /**
      * @return Whether displays refresh rate synchronization is enabled.
      */
@@ -441,6 +449,7 @@
         pw.println(" " + mConnectedDisplayManagementFlagState);
         pw.println(" " + mDisplayOffloadFlagState);
         pw.println(" " + mExternalDisplayLimitModeState);
+        pw.println(" " + mDisplayTopology);
         pw.println(" " + mHdrClamperFlagState);
         pw.println(" " + mNbmControllerFlagState);
         pw.println(" " + mPowerThrottlingClamperFlagState);
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index 9968ba5..e3ebe5b 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -92,6 +92,14 @@
 }
 
 flag {
+    name: "display_topology"
+    namespace: "display_manager"
+    description: "Display topology for moving cursors and windows between extended displays"
+    bug: "278199220"
+    is_fixed_read_only: true
+}
+
+flag {
     name: "enable_displays_refresh_rates_synchronization"
     namespace: "display_manager"
     description: "Enables synchronization of refresh rates across displays"
diff --git a/services/core/java/com/android/server/input/KeyRemapper.java b/services/core/java/com/android/server/input/KeyRemapper.java
index 7ba7769..82b36af 100644
--- a/services/core/java/com/android/server/input/KeyRemapper.java
+++ b/services/core/java/com/android/server/input/KeyRemapper.java
@@ -17,27 +17,24 @@
 package com.android.server.input;
 
 import android.content.Context;
-import android.hardware.input.InputManager;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.util.ArrayMap;
 import android.util.FeatureFlagUtils;
-import android.view.InputDevice;
 
 import com.android.internal.annotations.GuardedBy;
 
 import java.util.Map;
-import java.util.Objects;
 
 /**
  * A component of {@link InputManagerService} responsible for managing key remappings.
  *
  * @hide
  */
-final class KeyRemapper implements InputManager.InputDeviceListener {
+final class KeyRemapper {
 
-    private static final int MSG_UPDATE_EXISTING_DEVICES = 1;
+    private static final int MSG_UPDATE_EXISTING_KEY_REMAPPING = 1;
     private static final int MSG_REMAP_KEY = 2;
     private static final int MSG_CLEAR_ALL_REMAPPING = 3;
 
@@ -49,7 +46,7 @@
     private final Handler mHandler;
 
     KeyRemapper(Context context, NativeInputManagerService nativeService,
-            PersistentDataStore dataStore, Looper looper) {
+                PersistentDataStore dataStore, Looper looper) {
         mContext = context;
         mNative = nativeService;
         mDataStore = dataStore;
@@ -57,13 +54,7 @@
     }
 
     public void systemRunning() {
-        InputManager inputManager = Objects.requireNonNull(
-                mContext.getSystemService(InputManager.class));
-        inputManager.registerInputDeviceListener(this, mHandler);
-
-        Message msg = Message.obtain(mHandler, MSG_UPDATE_EXISTING_DEVICES,
-                inputManager.getInputDeviceIds());
-        mHandler.sendMessage(msg);
+        Message.obtain(mHandler, MSG_UPDATE_EXISTING_KEY_REMAPPING).sendToTarget();
     }
 
     public void remapKey(int fromKey, int toKey) {
@@ -91,19 +82,19 @@
         }
     }
 
-    private void addKeyRemapping(int fromKey, int toKey) {
-        InputManager inputManager = Objects.requireNonNull(
-                mContext.getSystemService(InputManager.class));
-        for (int deviceId : inputManager.getInputDeviceIds()) {
-            InputDevice inputDevice = inputManager.getInputDevice(deviceId);
-            if (inputDevice != null && !inputDevice.isVirtual() && inputDevice.isFullKeyboard()) {
-                mNative.addKeyRemapping(deviceId, fromKey, toKey);
-            }
+    private void setKeyRemapping(Map<Integer, Integer> keyRemapping) {
+        int index = 0;
+        int[] fromKeycodesArr = new int[keyRemapping.size()];
+        int[] toKeycodesArr = new int[keyRemapping.size()];
+        for (Map.Entry<Integer, Integer> entry : keyRemapping.entrySet()) {
+            fromKeycodesArr[index] = entry.getKey();
+            toKeycodesArr[index] = entry.getValue();
+            index++;
         }
+        mNative.setKeyRemapping(fromKeycodesArr, toKeycodesArr);
     }
 
     private void remapKeyInternal(int fromKey, int toKey) {
-        addKeyRemapping(fromKey, toKey);
         synchronized (mDataStore) {
             try {
                 if (fromKey == toKey) {
@@ -114,6 +105,7 @@
             } finally {
                 mDataStore.saveIfNeeded();
             }
+            setKeyRemapping(mDataStore.getKeyRemapping());
         }
     }
 
@@ -123,45 +115,25 @@
                 Map<Integer, Integer> keyRemapping = mDataStore.getKeyRemapping();
                 for (int fromKey : keyRemapping.keySet()) {
                     mDataStore.clearMappedKey(fromKey);
-
-                    // Remapping to itself will clear the remapping on native side
-                    addKeyRemapping(fromKey, fromKey);
                 }
             } finally {
                 mDataStore.saveIfNeeded();
             }
+            setKeyRemapping(mDataStore.getKeyRemapping());
         }
     }
 
-    @Override
-    public void onInputDeviceAdded(int deviceId) {
+    public void updateExistingKeyMapping() {
         if (!supportRemapping()) {
             return;
         }
-        InputManager inputManager = Objects.requireNonNull(
-                mContext.getSystemService(InputManager.class));
-        InputDevice inputDevice = inputManager.getInputDevice(deviceId);
-        if (inputDevice != null && !inputDevice.isVirtual() && inputDevice.isFullKeyboard()) {
-            Map<Integer, Integer> remapping = getKeyRemapping();
-            remapping.forEach(
-                    (fromKey, toKey) -> mNative.addKeyRemapping(deviceId, fromKey, toKey));
-        }
-    }
-
-    @Override
-    public void onInputDeviceRemoved(int deviceId) {
-    }
-
-    @Override
-    public void onInputDeviceChanged(int deviceId) {
+        setKeyRemapping(getKeyRemapping());
     }
 
     private boolean handleMessage(Message msg) {
         switch (msg.what) {
-            case MSG_UPDATE_EXISTING_DEVICES:
-                for (int deviceId : (int[]) msg.obj) {
-                    onInputDeviceAdded(deviceId);
-                }
+            case MSG_UPDATE_EXISTING_KEY_REMAPPING:
+                updateExistingKeyMapping();
                 return true;
             case MSG_REMAP_KEY:
                 remapKeyInternal(msg.arg1, msg.arg2);
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 5dd461d..d17e256 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -48,7 +48,7 @@
 
     int getSwitchState(int deviceId, int sourceMask, int sw);
 
-    void addKeyRemapping(int deviceId, int fromKeyCode, int toKeyCode);
+    void setKeyRemapping(int[] fromKeyCodes, int[] toKeyCodes);
 
     boolean hasKeys(int deviceId, int sourceMask, int[] keyCodes, boolean[] keyExists);
 
@@ -311,7 +311,7 @@
         public native int getSwitchState(int deviceId, int sourceMask, int sw);
 
         @Override
-        public native void addKeyRemapping(int deviceId, int fromKeyCode, int toKeyCode);
+        public native void setKeyRemapping(int[] fromKeyCodes, int[] toKeyCodes);
 
         @Override
         public native boolean hasKeys(int deviceId, int sourceMask, int[] keyCodes,
diff --git a/services/core/java/com/android/server/input/debug/TouchpadDebugView.java b/services/core/java/com/android/server/input/debug/TouchpadDebugView.java
index 0e940d2..f351465 100644
--- a/services/core/java/com/android/server/input/debug/TouchpadDebugView.java
+++ b/services/core/java/com/android/server/input/debug/TouchpadDebugView.java
@@ -52,6 +52,8 @@
     private static final float DEFAULT_RES_Y = 45f;
     private static final int TEXT_PADDING_DP = 12;
     private static final int ROUNDED_CORNER_RADIUS_DP = 24;
+    private static final int BUTTON_PRESSED_BACKGROUND_COLOR = Color.rgb(118, 151, 99);
+    private static final int BUTTON_RELEASED_BACKGROUND_COLOR = Color.rgb(84, 85, 169);
 
     /**
      * Input device ID for the touchpad that this debug view is displaying.
@@ -75,6 +77,8 @@
     private int mWindowLocationBeforeDragY;
     private int mLatestGestureType = 0;
     private TextView mGestureInfoView;
+    private TextView mNameView;
+
     @NonNull
     private TouchpadHardwareState mLastTouchpadState =
             new TouchpadHardwareState(0, 0 /* buttonsDown */, 0, 0,
@@ -119,35 +123,34 @@
                 LayoutParams.WRAP_CONTENT));
         setBackgroundColor(Color.TRANSPARENT);
 
-        TextView nameView = new TextView(context);
-        nameView.setBackgroundColor(Color.RED);
-        nameView.setTextSize(TEXT_SIZE_SP);
-        nameView.setText(Objects.requireNonNull(Objects.requireNonNull(
+        mNameView = new TextView(context);
+        mNameView.setBackgroundColor(BUTTON_RELEASED_BACKGROUND_COLOR);
+        mNameView.setTextSize(TEXT_SIZE_SP);
+        mNameView.setText(Objects.requireNonNull(Objects.requireNonNull(
                         mContext.getSystemService(InputManager.class))
                 .getInputDevice(touchpadId)).getName());
-        nameView.setGravity(Gravity.CENTER);
-        nameView.setTextColor(Color.WHITE);
+        mNameView.setGravity(Gravity.CENTER);
+        mNameView.setTextColor(Color.WHITE);
         int paddingInDP = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, TEXT_PADDING_DP,
                 getResources().getDisplayMetrics());
-        nameView.setPadding(paddingInDP, paddingInDP, paddingInDP, paddingInDP);
-        nameView.setLayoutParams(
+        mNameView.setPadding(paddingInDP, paddingInDP, paddingInDP, paddingInDP);
+        mNameView.setLayoutParams(
                 new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
 
         mTouchpadVisualizationView = new TouchpadVisualizationView(context,
                 mTouchpadHardwareProperties);
-        mTouchpadVisualizationView.setBackgroundColor(Color.WHITE);
 
         mGestureInfoView = new TextView(context);
-        mGestureInfoView.setBackgroundColor(Color.BLACK);
         mGestureInfoView.setTextSize(TEXT_SIZE_SP);
         mGestureInfoView.setText("Latest Gesture: ");
         mGestureInfoView.setGravity(Gravity.CENTER);
-        mGestureInfoView.setTextColor(Color.WHITE);
         mGestureInfoView.setPadding(paddingInDP, paddingInDP, paddingInDP, paddingInDP);
         mGestureInfoView.setLayoutParams(
                 new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
 
-        addView(nameView);
+        updateTheme(getResources().getConfiguration().uiMode);
+
+        addView(mNameView);
         addView(mTouchpadVisualizationView);
         addView(mGestureInfoView);
 
@@ -239,6 +242,8 @@
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
+
+        updateTheme(newConfig.uiMode);
         updateScreenDimensions();
         updateViewsDimensions();
 
@@ -250,6 +255,27 @@
         mWindowManager.updateViewLayout(this, mWindowLayoutParams);
     }
 
+    private void updateTheme(int uiMode) {
+        int currentNightMode = uiMode & Configuration.UI_MODE_NIGHT_MASK;
+        if (currentNightMode == Configuration.UI_MODE_NIGHT_YES) {
+            setNightModeTheme();
+        } else {
+            setLightModeTheme();
+        }
+    }
+
+    private void setLightModeTheme() {
+        mTouchpadVisualizationView.setLightModeTheme();
+        mGestureInfoView.setBackgroundColor(Color.WHITE);
+        mGestureInfoView.setTextColor(Color.BLACK);
+    }
+
+    private void setNightModeTheme() {
+        mTouchpadVisualizationView.setNightModeTheme();
+        mGestureInfoView.setBackgroundColor(Color.BLACK);
+        mGestureInfoView.setTextColor(Color.WHITE);
+    }
+
     private boolean isSlopExceeded(float deltaX, float deltaY) {
         return deltaX * deltaX + deltaY * deltaY >= mTouchSlop * mTouchSlop;
     }
@@ -333,12 +359,12 @@
 
     private void onTouchpadButtonPress() {
         Slog.d(TAG, "You clicked me!");
-        getChildAt(0).setBackgroundColor(Color.BLUE);
+        mNameView.setBackgroundColor(BUTTON_PRESSED_BACKGROUND_COLOR);
     }
 
     private void onTouchpadButtonRelease() {
         Slog.d(TAG, "You released the click");
-        getChildAt(0).setBackgroundColor(Color.RED);
+        mNameView.setBackgroundColor(BUTTON_RELEASED_BACKGROUND_COLOR);
     }
 
     /**
diff --git a/services/core/java/com/android/server/input/debug/TouchpadVisualizationView.java b/services/core/java/com/android/server/input/debug/TouchpadVisualizationView.java
index 2eed9ba..96426bb 100644
--- a/services/core/java/com/android/server/input/debug/TouchpadVisualizationView.java
+++ b/services/core/java/com/android/server/input/debug/TouchpadVisualizationView.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.RectF;
 import android.util.Slog;
@@ -58,11 +59,9 @@
         mScaleFactor = 1;
         mOvalStrokePaint = new Paint();
         mOvalStrokePaint.setAntiAlias(true);
-        mOvalStrokePaint.setARGB(255, 0, 0, 0);
         mOvalStrokePaint.setStyle(Paint.Style.STROKE);
         mOvalFillPaint = new Paint();
         mOvalFillPaint.setAntiAlias(true);
-        mOvalFillPaint.setARGB(255, 0, 0, 0);
         mTracePaint = new Paint();
         mTracePaint.setAntiAlias(false);
         mTracePaint.setARGB(255, 0, 0, 255);
@@ -195,6 +194,24 @@
         mScaleFactor = scaleFactor;
     }
 
+    /**
+     * Change the colors of the objects inside the view to light mode theme.
+     */
+    public void setLightModeTheme() {
+        this.setBackgroundColor(Color.rgb(20, 20, 20));
+        mOvalFillPaint.setARGB(255, 255, 255, 255);
+        mOvalStrokePaint.setARGB(255, 255, 255, 255);
+    }
+
+    /**
+     * Change the colors of the objects inside the view to night mode theme.
+     */
+    public void setNightModeTheme() {
+        this.setBackgroundColor(Color.rgb(240, 240, 240));
+        mOvalFillPaint.setARGB(255, 0, 0, 0);
+        mOvalStrokePaint.setARGB(255, 0, 0, 0);
+    }
+
     private float translateX(float x) {
         return translateRange(mTouchpadHardwareProperties.getLeft(),
                 mTouchpadHardwareProperties.getRight(), 0, getWidth(), x);
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 3e70d92..8bab9de 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1101,8 +1101,6 @@
         if (android.multiuser.Flags.cachesNotInvalidatedAtStartReadOnly()) {
             UserManager.invalidateIsUserUnlockedCache();
             UserManager.invalidateQuietModeEnabledCache();
-            UserManager.invalidateStaticUserProperties();
-            UserManager.invalidateUserPropertiesCache();
             UserManager.invalidateUserSerialNumberCache();
         }
     }
diff --git a/services/core/java/com/android/server/pm/UserVisibilityMediator.java b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
index 46207c1..b43ddaa 100644
--- a/services/core/java/com/android/server/pm/UserVisibilityMediator.java
+++ b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
@@ -572,7 +572,7 @@
                 return false;
             }
 
-            // First check if the user started on display
+            // First check if the user is assigned to a display
             int userAssignedToDisplay = getUserStartedOnDisplay(displayId);
             if (userAssignedToDisplay != USER_NULL) {
                 Slogf.w(TAG, "assignUserToExtraDisplay(%d, %d): failed because display was assigned"
@@ -918,10 +918,16 @@
                 if (!isStartedVisibleProfileLocked(userId)) {
                     return userId;
                 } else if (DBG) {
-                    Slogf.d(TAG, "getUserAssignedToDisplay(%d): skipping user %d because it's "
-                            + "a profile", displayId, userId);
+                    Slogf.d(TAG,
+                            "getUserAssignedToDisplay(%d): skipping user %d because it's a profile",
+                            displayId, userId);
                 }
             }
+            int userAssignedToExtraDisplay = mExtraDisplaysAssignedToUsers.get(displayId,
+                    USER_NULL);
+            if (userAssignedToExtraDisplay != USER_NULL) {
+                return userAssignedToExtraDisplay;
+            }
         }
         if (!returnCurrentUserByDefault) {
             if (DBG) {
diff --git a/services/core/java/com/android/server/power/PowerManagerShellCommand.java b/services/core/java/com/android/server/power/PowerManagerShellCommand.java
index 20184e9f..f69a017 100644
--- a/services/core/java/com/android/server/power/PowerManagerShellCommand.java
+++ b/services/core/java/com/android/server/power/PowerManagerShellCommand.java
@@ -16,13 +16,19 @@
 
 package com.android.server.power;
 
+import android.app.AlarmManager;
+import android.app.IAlarmCompleteListener;
+import android.app.IAlarmListener;
+import android.app.IAlarmManager;
 import android.content.Context;
 import android.content.Intent;
 import android.os.PowerManager;
 import android.os.PowerManager.WakeLock;
 import android.os.PowerManagerInternal;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.ShellCommand;
+import android.os.SystemClock;
 import android.util.SparseArray;
 import android.view.Display;
 
@@ -34,12 +40,26 @@
 
     private final Context mContext;
     private final PowerManagerService.BinderService mService;
+    private final IAlarmListener mAlarmListener;
+    private IAlarmManager mAlarmManager;
 
     private SparseArray<WakeLock> mProxWakelocks = new SparseArray<>();
 
     PowerManagerShellCommand(Context context, PowerManagerService.BinderService service) {
         mContext = context;
         mService = service;
+        mAlarmManager =
+            IAlarmManager.Stub.asInterface(ServiceManager.getService(Context.ALARM_SERVICE));
+        mAlarmListener = new IAlarmListener.Stub() {
+            @Override
+            public void doAlarm(IAlarmCompleteListener callback) throws RemoteException {
+                mService.wakeUp(
+                        SystemClock.uptimeMillis(),
+                        PowerManager.WAKE_REASON_APPLICATION,
+                        "PowerManagerShellCommand",
+                        mContext.getOpPackageName());
+            }
+        };
     }
 
     @Override
@@ -65,6 +85,10 @@
                     return runSetProx();
                 case "set-face-down-detector":
                     return runSetFaceDownDetector();
+                case "sleep":
+                    return runSleep();
+                case "wakeup":
+                    return runWakeUp();
                 default:
                     return handleDefaultCommands(cmd);
             }
@@ -194,6 +218,70 @@
         return 0;
     }
 
+    private int runSleep() {
+        try {
+            mService.goToSleep(
+                    SystemClock.uptimeMillis(),
+                    PowerManager.GO_TO_SLEEP_REASON_APPLICATION,
+                    PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE);
+        } catch (Exception e) {
+            final PrintWriter pw = getOutPrintWriter();
+            pw.println("Error: " + e);
+            return -1;
+        }
+        return 0;
+    }
+
+    private int runWakeUp() {
+        final PrintWriter pw = getOutPrintWriter();
+        String delay = getNextArg();
+        if (delay == null) {
+            try {
+                mService.wakeUp(
+                        SystemClock.uptimeMillis(),
+                        PowerManager.WAKE_REASON_APPLICATION,
+                        "PowerManagerShellCommand",
+                        mContext.getOpPackageName());
+            } catch (Exception e) {
+                pw.println("Error: " + e);
+                return -1;
+            }
+        } else {
+            long delayMillis;
+            try {
+                delayMillis = Long.parseLong(delay);
+            } catch (NumberFormatException e) {
+                pw.println("Error: Can't parse arg " + delay + " as a long: " + e);
+                return -1;
+            }
+            if (delayMillis < 0) {
+                pw.println("Error: Can't set a negative delay: " + delayMillis);
+                return -1;
+            }
+            long wakeUpTime = System.currentTimeMillis() + delayMillis;
+            if (mAlarmManager == null) {
+                // PowerManagerShellCommand may be initialized before AlarmManagerService
+                // is brought up. Make sure mAlarmManager exists.
+                mAlarmManager = IAlarmManager.Stub.asInterface(
+                        ServiceManager.getService(Context.ALARM_SERVICE));
+            }
+            try {
+                // This command is called by the shell, which has "com.android.shell" as package
+                // name.
+                pw.println("Schedule an alarm to wakeup in "
+                        + delayMillis + " ms, on behalf of shell.");
+                mAlarmManager.set("com.android.shell",
+                        AlarmManager.RTC_WAKEUP, wakeUpTime,
+                        0, 0, AlarmManager.FLAG_PRIORITIZE,
+                        null, mAlarmListener, "PowerManagerShellCommand", null, null);
+            } catch (Exception e) {
+                pw.println("Error: " + e);
+                return -1;
+            }
+        }
+        return 0;
+    }
+
     @Override
     public void onHelp() {
         final PrintWriter pw = getOutPrintWriter();
@@ -221,6 +309,11 @@
         pw.println("    created by set-prox including their held status.");
         pw.println("  set-face-down-detector [true|false]");
         pw.println("    sets whether we use face down detector timeouts or not");
+        pw.println("  sleep");
+        pw.println("    requests to sleep the device");
+        pw.println("  wakeup <delay>");
+        pw.println("    requests to wake up the device. If a delay of milliseconds is specified,");
+        pw.println("    alarm manager will schedule a wake up after the delay.");
 
         pw.println();
         Intent.printIntentArgsHelp(pw , "");
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index e3d71e4..f78c448 100644
--- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -81,7 +81,7 @@
 public final class RollbackPackageHealthObserver implements PackageHealthObserver {
     private static final String TAG = "RollbackPackageHealthObserver";
     private static final String NAME = "rollback-observer";
-    private static final String ACTION_NAME = RollbackPackageHealthObserver.class.getName();
+    private static final String CLASS_NAME = RollbackPackageHealthObserver.class.getName();
 
     private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT
             | ApplicationInfo.FLAG_SYSTEM;
@@ -610,14 +610,16 @@
                 }
             };
 
+            String intentActionName = CLASS_NAME + rollback.getRollbackId();
             // Register the BroadcastReceiver
             mContext.registerReceiver(rollbackReceiver,
-                    new IntentFilter(ACTION_NAME),
+                    new IntentFilter(intentActionName),
                     Context.RECEIVER_NOT_EXPORTED);
 
-            Intent intentReceiver = new Intent(ACTION_NAME);
+            Intent intentReceiver = new Intent(intentActionName);
             intentReceiver.putExtra("rollbackId", rollback.getRollbackId());
             intentReceiver.setPackage(mContext.getPackageName());
+            intentReceiver.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
 
             PendingIntent rollbackPendingIntent = PendingIntent.getBroadcast(mContext,
                     rollback.getRollbackId(),
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index ccc9b17..12d733f 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2641,9 +2641,15 @@
             return true;
         }
         // Only do transfer after transaction has done when starting window exist.
-        if (mStartingData != null && mStartingData.mWaitForSyncTransactionCommit) {
-            mStartingData.mRemoveAfterTransaction = AFTER_TRANSACTION_COPY_TO_CLIENT;
-            return true;
+        if (mStartingData != null) {
+            final boolean isWaitingForSyncTransactionCommit =
+                    Flags.removeStartingWindowWaitForMultiTransitions()
+                            ? getSyncTransactionCommitCallbackDepth() > 0
+                            : mStartingData.mWaitForSyncTransactionCommit;
+            if (isWaitingForSyncTransactionCommit) {
+                mStartingData.mRemoveAfterTransaction = AFTER_TRANSACTION_COPY_TO_CLIENT;
+                return true;
+            }
         }
         requestCopySplashScreen();
         return isTransferringSplashScreen();
@@ -2847,7 +2853,11 @@
         final boolean animate;
         final boolean hasImeSurface;
         if (mStartingData != null) {
-            if (mStartingData.mWaitForSyncTransactionCommit
+            final boolean isWaitingForSyncTransactionCommit =
+                    Flags.removeStartingWindowWaitForMultiTransitions()
+                            ? getSyncTransactionCommitCallbackDepth() > 0
+                            : mStartingData.mWaitForSyncTransactionCommit;
+            if (isWaitingForSyncTransactionCommit
                     || mSyncState != SYNC_STATE_NONE) {
                 mStartingData.mRemoveAfterTransaction = AFTER_TRANSACTION_REMOVE_DIRECTLY;
                 mStartingData.mPrepareRemoveAnimation = prepareAnimation;
diff --git a/services/core/java/com/android/server/wm/StartingData.java b/services/core/java/com/android/server/wm/StartingData.java
index 24fb207..896612d 100644
--- a/services/core/java/com/android/server/wm/StartingData.java
+++ b/services/core/java/com/android/server/wm/StartingData.java
@@ -68,7 +68,9 @@
      * window.
      * Note this isn't equal to transition playing, the period should be
      * Sync finishNow -> Start transaction apply.
+     * @deprecated TODO(b/362347290): cleanup after fix ramp up
      */
+    @Deprecated
     boolean mWaitForSyncTransactionCommit;
 
     /**
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index a2fda0a..86bb75a 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -35,6 +35,8 @@
 import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
 import static android.content.pm.ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY;
 import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
+import static android.content.pm.ActivityInfo.FORCE_NON_RESIZE_APP;
+import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION;
@@ -49,6 +51,7 @@
 import static android.view.SurfaceControl.METADATA_TASK_ID;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED;
@@ -133,6 +136,7 @@
 import android.app.PictureInPictureParams;
 import android.app.TaskInfo;
 import android.app.WindowConfiguration;
+import android.app.compat.CompatChanges;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -503,6 +507,12 @@
     int mOffsetXForInsets;
     int mOffsetYForInsets;
 
+    /**
+     * Whether the compatibility overrides that change the resizability of the app should be allowed
+     * for the specific app.
+     */
+    boolean mAllowForceResizeOverride = true;
+
     private final AnimatingActivityRegistry mAnimatingActivityRegistry =
             new AnimatingActivityRegistry();
 
@@ -666,6 +676,7 @@
             intent = _intent;
             mMinWidth = minWidth;
             mMinHeight = minHeight;
+            updateAllowForceResizeOverride();
         }
         mAtmService.getTaskChangeNotificationController().notifyTaskCreated(_taskId, realActivity);
         mHandler = new ActivityTaskHandler(mTaskSupervisor.mLooper);
@@ -1028,6 +1039,7 @@
             mTaskSupervisor.mRecentTasks.remove(this);
             mTaskSupervisor.mRecentTasks.add(this);
         }
+        updateAllowForceResizeOverride();
     }
 
     /** Sets the original minimal width and height. */
@@ -1823,6 +1835,17 @@
                 -1 /* don't check PID */, -1 /* don't check UID */, this);
     }
 
+    private void updateAllowForceResizeOverride() {
+        try {
+            mAllowForceResizeOverride = mAtmService.mContext.getPackageManager().getProperty(
+                    PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES,
+                    getBasePackageName()).getBoolean();
+        } catch (PackageManager.NameNotFoundException e) {
+            // Package not found or property not defined, reset to default value.
+            mAllowForceResizeOverride = true;
+        }
+    }
+
     /**
      * Check that a given bounds matches the application requested orientation.
      *
@@ -2812,7 +2835,18 @@
     boolean isResizeable(boolean checkPictureInPictureSupport) {
         final boolean forceResizable = mAtmService.mForceResizableActivities
                 && getActivityType() == ACTIVITY_TYPE_STANDARD;
-        return forceResizable || ActivityInfo.isResizeableMode(mResizeMode)
+        if (forceResizable) return true;
+
+        final UserHandle userHandle = UserHandle.getUserHandleForUid(mUserId);
+        final boolean forceResizableOverride = mAllowForceResizeOverride
+                && CompatChanges.isChangeEnabled(
+                        FORCE_RESIZE_APP, getBasePackageName(), userHandle);
+        final boolean forceNonResizableOverride = mAllowForceResizeOverride
+                && CompatChanges.isChangeEnabled(
+                        FORCE_NON_RESIZE_APP, getBasePackageName(), userHandle);
+
+        if (forceNonResizableOverride) return false;
+        return forceResizableOverride || ActivityInfo.isResizeableMode(mResizeMode)
                 || (mSupportsPictureInPicture && checkPictureInPictureSupport);
     }
 
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 92953e5..83e714d 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -429,7 +429,7 @@
             }
 
             final IBinder activityToken;
-            if (activity.getPid() == mOrganizerPid) {
+            if (activity.getPid() == mOrganizerPid && activity.getUid() == mOrganizerUid) {
                 // We only pass the actual token if the activity belongs to the organizer process.
                 activityToken = activity.token;
             } else {
@@ -458,7 +458,8 @@
                 change.setTaskFragmentToken(lastParentTfToken);
             }
             // Only pass the activity token to the client if it belongs to the same process.
-            if (nextFillTaskActivity != null && nextFillTaskActivity.getPid() == mOrganizerPid) {
+            if (nextFillTaskActivity != null && nextFillTaskActivity.getPid() == mOrganizerPid
+                    && nextFillTaskActivity.getUid() == mOrganizerUid) {
                 change.setOtherActivityToken(nextFillTaskActivity.token);
             }
             return change;
@@ -553,6 +554,10 @@
                         "Replacing existing organizer currently unsupported");
             }
 
+            if (pid <= 0) {
+                throw new IllegalStateException("Cannot register from invalid pid: " + pid);
+            }
+
             if (restoreFromCachedStateIfPossible(organizer, pid, uid, outSavedState)) {
                 return;
             }
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 0a9cb1c..1c03ba5 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -4351,4 +4351,7 @@
         t.merge(mSyncTransaction);
     }
 
+    int getSyncTransactionCommitCallbackDepth() {
+        return mSyncTransactionCommitCallbackDepth;
+    }
 }
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index d2493c5..5cd117b 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -357,6 +357,7 @@
     FloatPoint getMouseCursorPosition(ui::LogicalDisplayId displayId);
     void setStylusPointerIconEnabled(bool enabled);
     void setInputMethodConnectionIsActive(bool isActive);
+    void setKeyRemapping(const std::map<int32_t, int32_t>& keyRemapping);
 
     /* --- InputReaderPolicyInterface implementation --- */
 
@@ -504,6 +505,9 @@
 
         // True if there is an active input method connection.
         bool isInputMethodConnectionActive{false};
+
+        // Keycodes to be remapped.
+        std::map<int32_t /* fromKeyCode */, int32_t /* toKeyCode */> keyRemapping{};
     } mLocked GUARDED_BY(mLock);
 
     std::atomic<bool> mInteractive;
@@ -761,6 +765,8 @@
         outConfig->stylusButtonMotionEventsEnabled = mLocked.stylusButtonMotionEventsEnabled;
 
         outConfig->stylusPointerIconEnabled = mLocked.stylusPointerIconEnabled;
+
+        outConfig->keyRemapping = mLocked.keyRemapping;
     } // release lock
 }
 
@@ -1910,6 +1916,16 @@
     mInputManager->getDispatcher().setInputMethodConnectionIsActive(isActive);
 }
 
+void NativeInputManager::setKeyRemapping(const std::map<int32_t, int32_t>& keyRemapping) {
+    { // acquire lock
+        std::scoped_lock _l(mLock);
+        mLocked.keyRemapping = keyRemapping;
+    } // release lock
+
+    mInputManager->getReader().requestRefreshConfiguration(
+            InputReaderConfiguration::Change::KEY_REMAPPING);
+}
+
 // ----------------------------------------------------------------------------
 
 static NativeInputManager* getNativeInputManager(JNIEnv* env, jobject clazz) {
@@ -1983,10 +1999,19 @@
     return vec;
 }
 
-static void nativeAddKeyRemapping(JNIEnv* env, jobject nativeImplObj, jint deviceId,
-                                  jint fromKeyCode, jint toKeyCode) {
+static void nativeSetKeyRemapping(JNIEnv* env, jobject nativeImplObj, jintArray fromKeyCodesArr,
+                                  jintArray toKeyCodesArr) {
+    const std::vector<int32_t> fromKeycodes = getIntArray(env, fromKeyCodesArr);
+    const std::vector<int32_t> toKeycodes = getIntArray(env, toKeyCodesArr);
+    if (fromKeycodes.size() != toKeycodes.size()) {
+        jniThrowRuntimeException(env, "FromKeycodes and toKeycodes cannot match.");
+    }
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
-    im->getInputManager()->getReader().addKeyRemapping(deviceId, fromKeyCode, toKeyCode);
+    std::map<int32_t, int32_t> keyRemapping;
+    for (int i = 0; i < fromKeycodes.size(); i++) {
+        keyRemapping.insert_or_assign(fromKeycodes[i], toKeycodes[i]);
+    }
+    im->setKeyRemapping(keyRemapping);
 }
 
 static jboolean nativeHasKeys(JNIEnv* env, jobject nativeImplObj, jint deviceId, jint sourceMask,
@@ -2491,7 +2516,7 @@
             jTypeId = env->GetStaticIntField(gLightClassInfo.clazz,
                                              gLightClassInfo.lightTypeKeyboardMicMute);
         } else {
-            ALOGW("Unknown light type %d", lightInfo.type);
+            ALOGW("Unknown light type %s", ftl::enum_string(lightInfo.type).c_str());
             continue;
         }
 
@@ -2955,7 +2980,7 @@
         {"getScanCodeState", "(III)I", (void*)nativeGetScanCodeState},
         {"getKeyCodeState", "(III)I", (void*)nativeGetKeyCodeState},
         {"getSwitchState", "(III)I", (void*)nativeGetSwitchState},
-        {"addKeyRemapping", "(III)V", (void*)nativeAddKeyRemapping},
+        {"setKeyRemapping", "([I[I)V", (void*)nativeSetKeyRemapping},
         {"hasKeys", "(II[I[Z)Z", (void*)nativeHasKeys},
         {"getKeyCodeForKeyLocation", "(II)I", (void*)nativeGetKeyCodeForKeyLocation},
         {"createInputChannel", "(Ljava/lang/String;)Landroid/view/InputChannel;",
diff --git a/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt b/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt
index c05c381..bc64e15 100644
--- a/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt
+++ b/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt
@@ -36,7 +36,6 @@
 import com.android.internal.infra.AndroidFuture
 import com.android.server.appfunctions.FutureAppSearchSession.FutureSearchResults
 import com.google.common.truth.Truth.assertThat
-import com.google.common.util.concurrent.MoreExecutors
 import java.util.concurrent.atomic.AtomicBoolean
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -46,7 +45,6 @@
 class MetadataSyncAdapterTest {
     private val context = InstrumentationRegistry.getInstrumentation().targetContext
     private val appSearchManager = context.getSystemService(AppSearchManager::class.java)
-    private val testExecutor = MoreExecutors.directExecutor()
     private val packageManager = context.packageManager
 
     @Test
@@ -138,8 +136,7 @@
             PutDocumentsRequest.Builder().addGenericDocuments(functionRuntimeMetadata).build()
         runtimeSearchSession.put(putDocumentsRequest).get()
         staticSearchSession.put(putDocumentsRequest).get()
-        val metadataSyncAdapter =
-            MetadataSyncAdapter(testExecutor, packageManager, appSearchManager)
+        val metadataSyncAdapter = MetadataSyncAdapter(packageManager, appSearchManager)
 
         val submitSyncRequest =
             metadataSyncAdapter.trySyncAppFunctionMetadataBlocking(
@@ -180,8 +177,7 @@
         val putDocumentsRequest: PutDocumentsRequest =
             PutDocumentsRequest.Builder().addGenericDocuments(functionRuntimeMetadata).build()
         staticSearchSession.put(putDocumentsRequest).get()
-        val metadataSyncAdapter =
-            MetadataSyncAdapter(testExecutor, packageManager, appSearchManager)
+        val metadataSyncAdapter = MetadataSyncAdapter(packageManager, appSearchManager)
 
         val submitSyncRequest =
             metadataSyncAdapter.trySyncAppFunctionMetadataBlocking(
@@ -236,8 +232,7 @@
         val putDocumentsRequest: PutDocumentsRequest =
             PutDocumentsRequest.Builder().addGenericDocuments(functionRuntimeMetadata).build()
         runtimeSearchSession.put(putDocumentsRequest).get()
-        val metadataSyncAdapter =
-            MetadataSyncAdapter(testExecutor, packageManager, appSearchManager)
+        val metadataSyncAdapter = MetadataSyncAdapter(packageManager, appSearchManager)
 
         val submitSyncRequest =
             metadataSyncAdapter.trySyncAppFunctionMetadataBlocking(
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
index 3dd2f24a..1cad255 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
@@ -509,8 +509,12 @@
 
         mAppStartInfoTracker.handleProcessBroadcastStart(3, app, buildIntent(COMPONENT),
                 false /* isAlarm */);
+        // Add a brief delay between timestamps to make sure the clock, which is in milliseconds has
+        // actually incremented.
+        sleep(1);
         mAppStartInfoTracker.handleProcessBroadcastStart(2, app, buildIntent(COMPONENT),
                 false /* isAlarm */);
+        sleep(1);
         mAppStartInfoTracker.handleProcessBroadcastStart(1, app, buildIntent(COMPONENT),
                 false /* isAlarm */);
 
@@ -557,9 +561,10 @@
         // Now load from disk.
         mAppStartInfoTracker.loadExistingProcessStartInfo();
 
-        // Confirm clock has been set and that its current time is greater than the previous one.
+        // Confirm clock has been set and that its current time is greater than or equal to the
+        // previous one, thereby ensuring it was loaded from disk.
         assertNotNull(mAppStartInfoTracker.mMonotonicClock);
-        assertTrue(mAppStartInfoTracker.mMonotonicClock.monotonicTime() > originalMonotonicTime);
+        assertTrue(mAppStartInfoTracker.mMonotonicClock.monotonicTime() >= originalMonotonicTime);
     }
 
     private static <T> void setFieldValue(Class clazz, Object obj, String fieldName, T val) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index f6ad07d..2107406 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -229,8 +229,10 @@
         doCallRealMethod().when(mService).enqueueOomAdjTargetLocked(any(ProcessRecord.class));
         doCallRealMethod().when(mService).updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_ACTIVITY);
         setFieldValue(AppProfiler.class, profiler, "mProfilerLock", new Object());
-        doReturn(new ActivityManagerService.ProcessChangeItem()).when(pr)
-                .enqueueProcessChangeItemLocked(anyInt(), anyInt());
+        doNothing().when(pr).enqueueProcessChangeItemLocked(anyInt(), anyInt(), anyInt(),
+                anyInt());
+        doNothing().when(pr).enqueueProcessChangeItemLocked(anyInt(), anyInt(), anyInt(),
+                anyBoolean());
         mService.mOomAdjuster = mService.mConstants.ENABLE_NEW_OOMADJ
                 ? new OomAdjusterModernImpl(mService, mService.mProcessList,
                         new ActiveUids(mService, false), mInjector)
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 7ff2e50..4b03483 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -29,6 +29,8 @@
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
 import static android.content.pm.ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
@@ -75,6 +77,7 @@
 import android.app.ActivityOptions;
 import android.app.TaskInfo;
 import android.app.WindowConfiguration;
+import android.compat.testing.PlatformCompatChangeRule;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -97,9 +100,13 @@
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 
+import libcore.junit.util.compat.CoreCompatChangeRule;
+
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
 import org.mockito.Mockito;
 import org.xmlpull.v1.XmlPullParser;
@@ -122,6 +129,9 @@
 @RunWith(WindowTestRunner.class)
 public class TaskTests extends WindowTestsBase {
 
+    @Rule
+    public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
     private static final String TASK_TAG = "task";
 
     private Rect mParentBounds;
@@ -404,6 +414,85 @@
     }
 
     @Test
+    @CoreCompatChangeRule.EnableCompatChanges({ActivityInfo.FORCE_RESIZE_APP})
+    public void testIsResizeable_nonResizeable_forceResize_overridesEnabled_Resizeable() {
+        final Task task = new TaskBuilder(mSupervisor)
+                .setCreateActivity(true)
+                .setComponent(
+                        ComponentName.createRelative(mContext, SizeCompatTests.class.getName()))
+                .build();
+        task.setResizeMode(RESIZE_MODE_UNRESIZEABLE);
+        // Override should take effect and task should be resizeable.
+        assertTrue(task.getTaskInfo().isResizeable);
+    }
+
+    @Test
+    @CoreCompatChangeRule.EnableCompatChanges({ActivityInfo.FORCE_RESIZE_APP})
+    public void testIsResizeable_nonResizeable_forceResize_overridesDisabled_nonResizeable() {
+        final Task task = new TaskBuilder(mSupervisor)
+                .setCreateActivity(true)
+                .setComponent(
+                        ComponentName.createRelative(mContext, SizeCompatTests.class.getName()))
+                .build();
+        task.setResizeMode(RESIZE_MODE_UNRESIZEABLE);
+
+        // Disallow resize overrides.
+        task.mAllowForceResizeOverride = false;
+
+        // Override should not take effect and task should be un-resizeable.
+        assertFalse(task.getTaskInfo().isResizeable);
+    }
+
+    @Test
+    @CoreCompatChangeRule.EnableCompatChanges({ActivityInfo.FORCE_NON_RESIZE_APP})
+    public void testIsResizeable_resizeable_forceNonResize_overridesEnabled_nonResizeable() {
+        final Task task = new TaskBuilder(mSupervisor)
+                .setCreateActivity(true)
+                .setComponent(
+                        ComponentName.createRelative(mContext, SizeCompatTests.class.getName()))
+                .build();
+        task.setResizeMode(RESIZE_MODE_RESIZEABLE);
+
+        // Override should take effect and task should be un-resizeable.
+        assertFalse(task.getTaskInfo().isResizeable);
+    }
+
+    @Test
+    @CoreCompatChangeRule.EnableCompatChanges({ActivityInfo.FORCE_NON_RESIZE_APP})
+    public void testIsResizeable_resizeable_forceNonResize_overridesDisabled_Resizeable() {
+        final Task task = new TaskBuilder(mSupervisor)
+                .setCreateActivity(true)
+                .setComponent(
+                        ComponentName.createRelative(mContext, SizeCompatTests.class.getName()))
+                .build();
+        task.setResizeMode(RESIZE_MODE_RESIZEABLE);
+
+        // Disallow resize overrides.
+        task.mAllowForceResizeOverride = false;
+
+        // Override should not take effect and task should be resizeable.
+        assertTrue(task.getTaskInfo().isResizeable);
+    }
+
+    @Test
+    @CoreCompatChangeRule.EnableCompatChanges({ActivityInfo.FORCE_NON_RESIZE_APP})
+    public void testIsResizeable_systemWideForceResize_compatForceNonResize__Resizeable() {
+        final Task task = new TaskBuilder(mSupervisor)
+                .setCreateActivity(true)
+                .setComponent(
+                        ComponentName.createRelative(mContext, SizeCompatTests.class.getName()))
+                .build();
+        task.setResizeMode(RESIZE_MODE_RESIZEABLE);
+
+        // Set system-wide force resizeable override.
+        task.mAtmService.mForceResizableActivities = true;
+
+        // System wide override should tak priority over app compat override so the task should
+        // remain resizeable.
+        assertTrue(task.getTaskInfo().isResizeable);
+    }
+
+    @Test
     public void testResolveNonResizableTaskWindowingMode() {
         // Test with no support non-resizable in multi window regardless the screen size.
         mAtm.mSupportsNonResizableMultiWindow = -1;
diff --git a/tests/FlickerTests/ActivityEmbedding/OWNERS b/tests/FlickerTests/ActivityEmbedding/OWNERS
new file mode 100644
index 0000000..981b316
--- /dev/null
+++ b/tests/FlickerTests/ActivityEmbedding/OWNERS
@@ -0,0 +1 @@
+# Bug component: 1168918
diff --git a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
index b3a998e..9459070 100644
--- a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
+++ b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
@@ -321,26 +321,26 @@
                 new TouchpadHardwareState(0, 1 /* buttonsDown */, 0, 0,
                         new TouchpadFingerState[0]), TOUCHPAD_DEVICE_ID);
 
-        assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.BLUE);
+        assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.rgb(118, 151, 99));
 
         mTouchpadDebugView.updateHardwareState(
                 new TouchpadHardwareState(0, 0 /* buttonsDown */, 0, 0,
                         new TouchpadFingerState[0]), TOUCHPAD_DEVICE_ID);
 
-        assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.RED);
+        assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.rgb(84, 85, 169));
 
         mTouchpadDebugView.updateHardwareState(
                 new TouchpadHardwareState(0, 1 /* buttonsDown */, 0, 0,
                         new TouchpadFingerState[0]), TOUCHPAD_DEVICE_ID);
 
-        assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.BLUE);
+        assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.rgb(118, 151, 99));
 
         // Color should not change because hardware state of a different touchpad
         mTouchpadDebugView.updateHardwareState(
                 new TouchpadHardwareState(0, 0 /* buttonsDown */, 0, 0,
                         new TouchpadFingerState[0]), TOUCHPAD_DEVICE_ID + 1);
 
-        assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.BLUE);
+        assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.rgb(118, 151, 99));
     }
 
     @Test